wip: migrate to zustand
This commit is contained in:
@@ -37,7 +37,8 @@
|
|||||||
"swr": "^2.1.5",
|
"swr": "^2.1.5",
|
||||||
"tailwind-merge": "^1.12.0",
|
"tailwind-merge": "^1.12.0",
|
||||||
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql",
|
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql",
|
||||||
"vidstack": "^0.4.5"
|
"vidstack": "^0.4.5",
|
||||||
|
"zustand": "^4.3.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
|
|||||||
19
pnpm-lock.yaml
generated
19
pnpm-lock.yaml
generated
@@ -76,6 +76,9 @@ dependencies:
|
|||||||
vidstack:
|
vidstack:
|
||||||
specifier: ^0.4.5
|
specifier: ^0.4.5
|
||||||
version: 0.4.5
|
version: 0.4.5
|
||||||
|
zustand:
|
||||||
|
specifier: ^4.3.8
|
||||||
|
version: 4.3.8(react@18.2.0)
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@tailwindcss/typography':
|
'@tailwindcss/typography':
|
||||||
@@ -3082,6 +3085,22 @@ packages:
|
|||||||
engines: {node: '>= 14', npm: '>= 7'}
|
engines: {node: '>= 14', npm: '>= 7'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/zustand@4.3.8(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-4h28KCkHg5ii/wcFFJ5Fp+k1J3gJoasaIbppdgZFO4BPJnsNxL0mQXBSFgOgAdCdBj35aDTPvdAJReTMntFPGg==}
|
||||||
|
engines: {node: '>=12.7.0'}
|
||||||
|
peerDependencies:
|
||||||
|
immer: '>=9.0'
|
||||||
|
react: '>=16.8'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
immer:
|
||||||
|
optional: true
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/zwitch@2.0.4:
|
/zwitch@2.0.4:
|
||||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
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 { DEFAULT_AVATAR, WRITEONLY_RELAYS } from "@stores/constants";
|
import { DEFAULT_AVATAR, WRITEONLY_RELAYS } from "@stores/constants";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
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";
|
||||||
@@ -11,8 +11,10 @@ import { navigate } from "vite-plugin-ssr/client/router";
|
|||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
|
|
||||||
const { account } = useActiveAccount();
|
const [account, fetchAccount] = useActiveAccount((state: any) => [
|
||||||
|
state.account,
|
||||||
|
state.fetch,
|
||||||
|
]);
|
||||||
const [image, setImage] = useState(DEFAULT_AVATAR);
|
const [image, setImage] = useState(DEFAULT_AVATAR);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
@@ -50,6 +52,10 @@ export function Page() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchAccount();
|
||||||
|
}, [fetchAccount]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue("picture", image);
|
setValue("picture", image);
|
||||||
}, [setValue, image]);
|
}, [setValue, image]);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { User } from "@app/auth/components/user";
|
import { User } from "@app/auth/components/user";
|
||||||
import CheckCircleIcon from "@icons/checkCircle";
|
import CheckCircleIcon from "@icons/checkCircle";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
import { updateAccount } from "@utils/storage";
|
import { updateAccount } from "@utils/storage";
|
||||||
import { arrayToNIP02 } from "@utils/transform";
|
import { arrayToNIP02 } from "@utils/transform";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
@@ -111,8 +111,10 @@ const initialList = [
|
|||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
|
|
||||||
const { account } = useActiveAccount();
|
const [account, updateFollows] = useActiveAccount((state: any) => [
|
||||||
|
state.account,
|
||||||
|
state.updateFollows,
|
||||||
|
]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [follows, setFollows] = useState([]);
|
const [follows, setFollows] = useState([]);
|
||||||
|
|
||||||
@@ -128,9 +130,12 @@ export function Page() {
|
|||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// update account follows
|
// update account follows in database
|
||||||
updateAccount("follows", follows, account.pubkey);
|
updateAccount("follows", follows, account.pubkey);
|
||||||
|
|
||||||
|
// update account follows in state
|
||||||
|
updateFollows(JSON.stringify(follows));
|
||||||
|
|
||||||
const tags = arrayToNIP02(follows);
|
const tags = arrayToNIP02(follows);
|
||||||
|
|
||||||
const event: any = {
|
const event: any = {
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
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 { READONLY_RELAYS } from "@stores/constants";
|
import { READONLY_RELAYS } from "@stores/constants";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
import { updateAccount } from "@utils/storage";
|
import { updateAccount } from "@utils/storage";
|
||||||
import { nip02ToArray } from "@utils/transform";
|
import { nip02ToArray } from "@utils/transform";
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import useSWRSubscription from "swr/subscription";
|
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 pool: any = useContext(RelayContext);
|
||||||
|
|
||||||
const { account } = useActiveAccount();
|
const [account, fetchAccount, updateFollows] = useActiveAccount(
|
||||||
|
(state: any) => [state.account, state.fetch, state.updateFollows],
|
||||||
|
);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [follows, setFollows] = useState([]);
|
const [follows, setFollows] = useState([]);
|
||||||
|
|
||||||
@@ -42,9 +43,12 @@ export function Page() {
|
|||||||
// follows as list
|
// follows as list
|
||||||
const followsList = nip02ToArray(follows);
|
const followsList = nip02ToArray(follows);
|
||||||
|
|
||||||
// update account follows
|
// update account follows in database
|
||||||
updateAccount("follows", followsList, account.pubkey);
|
updateAccount("follows", followsList, account.pubkey);
|
||||||
|
|
||||||
|
// update account follows in store
|
||||||
|
updateFollows(JSON.stringify(followsList));
|
||||||
|
|
||||||
// redirect to home
|
// redirect to home
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => navigate("/app/prefetch", { overwriteLastHistoryEntry: true }),
|
() => navigate("/app/prefetch", { overwriteLastHistoryEntry: true }),
|
||||||
@@ -52,6 +56,10 @@ export function Page() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchAccount();
|
||||||
|
}, [fetchAccount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<div className="mx-auto w-full max-w-md">
|
<div className="mx-auto w-full max-w-md">
|
||||||
|
|||||||
@@ -1,28 +1,21 @@
|
|||||||
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
import CancelIcon from "@icons/cancel";
|
||||||
|
import PlusIcon from "@icons/plus";
|
||||||
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 CancelIcon from "@icons/cancel";
|
|
||||||
import PlusIcon from "@icons/plus";
|
|
||||||
|
|
||||||
import { DEFAULT_AVATAR, WRITEONLY_RELAYS } from "@stores/constants";
|
import { DEFAULT_AVATAR, WRITEONLY_RELAYS } from "@stores/constants";
|
||||||
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
import { createChannel } from "@utils/storage";
|
import { createChannel } from "@utils/storage";
|
||||||
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
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 { useSWRConfig } from "swr";
|
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
export default function ChannelCreateModal() {
|
export default function ChannelCreateModal() {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
const { account, isError, isLoading } = useActiveAccount();
|
|
||||||
const { mutate } = useSWRConfig();
|
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [image, setImage] = useState(DEFAULT_AVATAR);
|
const [image, setImage] = useState(DEFAULT_AVATAR);
|
||||||
@@ -47,7 +40,7 @@ export default function ChannelCreateModal() {
|
|||||||
const onSubmit = (data: any) => {
|
const onSubmit = (data: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
if (!isError && !isLoading && account) {
|
if (account) {
|
||||||
const event: any = {
|
const event: any = {
|
||||||
content: JSON.stringify(data),
|
content: JSON.stringify(data),
|
||||||
created_at: dateToUnix(),
|
created_at: dateToUnix(),
|
||||||
@@ -62,8 +55,6 @@ export default function ChannelCreateModal() {
|
|||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
// 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);
|
||||||
// update channe llist
|
|
||||||
mutate("channels");
|
|
||||||
// reset form
|
// reset form
|
||||||
reset();
|
reset();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import ChannelCreateModal from "@app/channel/components/createModal";
|
import ChannelCreateModal from "@app/channel/components/createModal";
|
||||||
import ChannelsListItem from "@app/channel/components/item";
|
import ChannelsListItem from "@app/channel/components/item";
|
||||||
|
import { useChannels } from "@stores/channels";
|
||||||
import { getChannels } from "@utils/storage";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
import useSWR from "swr";
|
|
||||||
|
|
||||||
const fetcher = () => getChannels(10, 0);
|
|
||||||
|
|
||||||
export default function ChannelsList() {
|
export default function ChannelsList() {
|
||||||
const { data, error }: any = useSWR("channels", fetcher);
|
const channels = useChannels((state: any) => state.channels);
|
||||||
|
const fetchChannels = useChannels((state: any) => state.fetch);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchChannels();
|
||||||
|
}, [fetchChannels]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
{!data || error ? (
|
{!channels ? (
|
||||||
<>
|
<>
|
||||||
<div className="inline-flex h-8 items-center gap-2 rounded-md px-2.5">
|
<div className="inline-flex h-8 items-center gap-2 rounded-md px-2.5">
|
||||||
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800" />
|
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800" />
|
||||||
@@ -24,7 +25,7 @@ export default function ChannelsList() {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
data.map((item: { event_id: string }) => (
|
channels.map((item: { event_id: string }) => (
|
||||||
<ChannelsListItem key={item.event_id} data={item} />
|
<ChannelsListItem key={item.event_id} data={item} />
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import UserReply from "@app/channel/components/messages/userReply";
|
import UserReply from "@app/channel/components/messages/userReply";
|
||||||
|
import CancelIcon from "@icons/cancel";
|
||||||
import { ImagePicker } from "@shared/form/imagePicker";
|
import { ImagePicker } from "@shared/form/imagePicker";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import CancelIcon from "@icons/cancel";
|
|
||||||
|
|
||||||
import { channelContentAtom, channelReplyAtom } from "@stores/channel";
|
import { channelContentAtom, channelReplyAtom } from "@stores/channel";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||||
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
|
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import { useResetAtom } from "jotai/utils";
|
import { useResetAtom } from "jotai/utils";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
@@ -20,7 +15,7 @@ export default function ChannelMessageForm({
|
|||||||
channelID,
|
channelID,
|
||||||
}: { channelID: string | string[] }) {
|
}: { channelID: string | string[] }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [value, setValue] = useAtom(channelContentAtom);
|
const [value, setValue] = useAtom(channelContentAtom);
|
||||||
const resetValue = useResetAtom(channelContentAtom);
|
const resetValue = useResetAtom(channelContentAtom);
|
||||||
@@ -41,7 +36,7 @@ export default function ChannelMessageForm({
|
|||||||
tags = [["e", channelID, "", "root"]];
|
tags = [["e", channelID, "", "root"]];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isError && !isLoading && account) {
|
if (account) {
|
||||||
const event: any = {
|
const event: any = {
|
||||||
content: value,
|
content: value,
|
||||||
created_at: dateToUnix(),
|
created_at: dateToUnix(),
|
||||||
@@ -49,11 +44,13 @@ export default function ChannelMessageForm({
|
|||||||
pubkey: account.pubkey,
|
pubkey: account.pubkey,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
};
|
};
|
||||||
|
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = getSignature(event, account.privkey);
|
event.sig = getSignature(event, account.privkey);
|
||||||
|
|
||||||
// publish note
|
// publish note
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
|
|
||||||
// reset state
|
// reset state
|
||||||
resetValue();
|
resetValue();
|
||||||
// reset channel reply
|
// reset channel reply
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
import { RelayContext } from "@shared/relayProvider";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { Tooltip } from "@shared/tooltip";
|
|
||||||
|
|
||||||
import CancelIcon from "@icons/cancel";
|
import CancelIcon from "@icons/cancel";
|
||||||
import HideIcon from "@icons/hide";
|
import HideIcon from "@icons/hide";
|
||||||
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
|
import { Tooltip } from "@shared/tooltip";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { channelMessagesAtom } from "@stores/channel";
|
import { channelMessagesAtom } from "@stores/channel";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||||
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { Fragment, useContext, useState } from "react";
|
import { Fragment, useContext, useState } from "react";
|
||||||
|
|
||||||
export default function MessageHideButton({ id }: { id: string }) {
|
export default function MessageHideButton({ id }: { id: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
const { account, isError, isLoading } = useActiveAccount();
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [messages, setMessages] = useAtom(channelMessagesAtom);
|
const [messages, setMessages] = useAtom(channelMessagesAtom);
|
||||||
@@ -31,7 +27,7 @@ export default function MessageHideButton({ id }: { id: string }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const hideMessage = () => {
|
const hideMessage = () => {
|
||||||
if (!isError && !isLoading && account) {
|
if (account) {
|
||||||
const event: any = {
|
const event: any = {
|
||||||
content: "",
|
content: "",
|
||||||
created_at: dateToUnix(),
|
created_at: dateToUnix(),
|
||||||
@@ -44,13 +40,16 @@ export default function MessageHideButton({ id }: { id: string }) {
|
|||||||
|
|
||||||
// publish note
|
// publish note
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
|
|
||||||
// update local state
|
// update local state
|
||||||
const cloneMessages = [...messages];
|
const cloneMessages = [...messages];
|
||||||
const targetMessage = cloneMessages.findIndex(
|
const targetMessage = cloneMessages.findIndex(
|
||||||
(message) => message.id === id,
|
(message) => message.id === id,
|
||||||
);
|
);
|
||||||
|
|
||||||
cloneMessages[targetMessage]["hide"] = true;
|
cloneMessages[targetMessage]["hide"] = true;
|
||||||
setMessages(cloneMessages);
|
setMessages(cloneMessages);
|
||||||
|
|
||||||
// close modal
|
// close modal
|
||||||
closeModal();
|
closeModal();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
import { RelayContext } from "@shared/relayProvider";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { Tooltip } from "@shared/tooltip";
|
|
||||||
|
|
||||||
import CancelIcon from "@icons/cancel";
|
import CancelIcon from "@icons/cancel";
|
||||||
import MuteIcon from "@icons/mute";
|
import MuteIcon from "@icons/mute";
|
||||||
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
|
import { Tooltip } from "@shared/tooltip";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { channelMessagesAtom } from "@stores/channel";
|
import { channelMessagesAtom } from "@stores/channel";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||||
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { Fragment, useContext, useState } from "react";
|
import { Fragment, useContext, useState } from "react";
|
||||||
|
|
||||||
export default function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
export default function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
const { account, isError, isLoading } = useActiveAccount();
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [messages, setMessages] = useAtom(channelMessagesAtom);
|
const [messages, setMessages] = useAtom(channelMessagesAtom);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
@@ -31,7 +27,7 @@ export default function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const muteUser = () => {
|
const muteUser = () => {
|
||||||
if (!isError && !isLoading && account) {
|
if (account) {
|
||||||
const event: any = {
|
const event: any = {
|
||||||
content: "",
|
content: "",
|
||||||
created_at: dateToUnix(),
|
created_at: dateToUnix(),
|
||||||
@@ -44,6 +40,7 @@ export default function MessageMuteButton({ pubkey }: { pubkey: string }) {
|
|||||||
|
|
||||||
// publish note
|
// publish note
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
|
|
||||||
// update local state
|
// update local state
|
||||||
const cloneMessages = [...messages];
|
const cloneMessages = [...messages];
|
||||||
const finalMessages = cloneMessages.filter(
|
const finalMessages = cloneMessages.filter(
|
||||||
|
|||||||
@@ -1,24 +1,20 @@
|
|||||||
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
import CancelIcon from "@icons/cancel";
|
||||||
|
import EditIcon from "@icons/edit";
|
||||||
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 CancelIcon from "@icons/cancel";
|
|
||||||
import EditIcon from "@icons/edit";
|
|
||||||
|
|
||||||
import { DEFAULT_AVATAR, WRITEONLY_RELAYS } from "@stores/constants";
|
import { DEFAULT_AVATAR, WRITEONLY_RELAYS } from "@stores/constants";
|
||||||
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
import { getChannel } from "@utils/storage";
|
import { getChannel } from "@utils/storage";
|
||||||
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
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 default function ChannelUpdateModal({ id }: { id: string }) {
|
export default function ChannelUpdateModal({ id }: { id: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
const { account, isError, isLoading } = useActiveAccount();
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [image, setImage] = useState(DEFAULT_AVATAR);
|
const [image, setImage] = useState(DEFAULT_AVATAR);
|
||||||
@@ -52,7 +48,7 @@ export default function ChannelUpdateModal({ id }: { id: string }) {
|
|||||||
const onSubmit = (data: any) => {
|
const onSubmit = (data: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
if (!isError && !isLoading && account) {
|
if (account) {
|
||||||
const event: any = {
|
const event: any = {
|
||||||
content: JSON.stringify(data),
|
content: JSON.stringify(data),
|
||||||
created_at: dateToUnix(),
|
created_at: dateToUnix(),
|
||||||
@@ -60,11 +56,13 @@ export default function ChannelUpdateModal({ id }: { id: string }) {
|
|||||||
pubkey: account.pubkey,
|
pubkey: account.pubkey,
|
||||||
tags: [["e", id]],
|
tags: [["e", id]],
|
||||||
};
|
};
|
||||||
|
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = getSignature(event, account.privkey);
|
event.sig = getSignature(event, account.privkey);
|
||||||
|
|
||||||
// publish channel
|
// publish channel
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
|
|
||||||
// reset form
|
// reset form
|
||||||
reset();
|
reset();
|
||||||
// close modal
|
// close modal
|
||||||
|
|||||||
@@ -3,18 +3,14 @@ import ChannelMembers from "@app/channel/components/members";
|
|||||||
import ChannelMessageForm from "@app/channel/components/messages/form";
|
import ChannelMessageForm from "@app/channel/components/messages/form";
|
||||||
import ChannelMetadata from "@app/channel/components/metadata";
|
import ChannelMetadata from "@app/channel/components/metadata";
|
||||||
import ChannelUpdateModal from "@app/channel/components/updateModal";
|
import ChannelUpdateModal from "@app/channel/components/updateModal";
|
||||||
|
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { channelMessagesAtom, channelReplyAtom } from "@stores/channel";
|
import { channelMessagesAtom, channelReplyAtom } from "@stores/channel";
|
||||||
import { READONLY_RELAYS } from "@stores/constants";
|
import { READONLY_RELAYS } from "@stores/constants";
|
||||||
|
|
||||||
import { dateToUnix, getHourAgo } from "@utils/date";
|
import { dateToUnix, getHourAgo } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
import { usePageContext } from "@utils/hooks/usePageContext";
|
import { usePageContext } from "@utils/hooks/usePageContext";
|
||||||
import { getActiveBlacklist, getBlacklist } from "@utils/storage";
|
import { getActiveBlacklist, getBlacklist } from "@utils/storage";
|
||||||
import { arrayObjToPureArr } from "@utils/transform";
|
import { arrayObjToPureArr } from "@utils/transform";
|
||||||
|
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import { useResetAtom } from "jotai/utils";
|
import { useResetAtom } from "jotai/utils";
|
||||||
import { Suspense, lazy, useContext, useEffect, useRef } from "react";
|
import { Suspense, lazy, useContext, useEffect, useRef } from "react";
|
||||||
@@ -39,19 +35,20 @@ const ChannelMessageList = lazy(
|
|||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
|
const account: any = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const pageContext = usePageContext();
|
const pageContext = usePageContext();
|
||||||
const searchParams: any = pageContext.urlParsed.search;
|
const searchParams: any = pageContext.urlParsed.search;
|
||||||
|
|
||||||
const channelID = searchParams.id;
|
const channelID = searchParams.id;
|
||||||
const channelPubkey = searchParams.channelpub;
|
const channelPubkey = searchParams.channelpub;
|
||||||
|
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
|
||||||
const { data: muted } = useSWR(
|
const { data: muted } = useSWR(
|
||||||
!isLoading && !isError && account ? ["muted", account.id] : null,
|
account ? ["muted", account.id] : null,
|
||||||
fetchMuted,
|
fetchMuted,
|
||||||
);
|
);
|
||||||
const { data: hided } = useSWR(
|
const { data: hided } = useSWR(
|
||||||
!isLoading && !isError && account ? ["hided", account.id] : null,
|
account ? ["hided", account.id] : null,
|
||||||
fetchHided,
|
fetchHided,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -118,7 +115,7 @@ export function Page() {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<ChannelMembers />
|
<ChannelMembers />
|
||||||
{!muted ? <></> : <ChannelBlackList blacklist={muted.original} />}
|
{!muted ? <></> : <ChannelBlackList blacklist={muted.original} />}
|
||||||
{!isLoading && !isError && account ? (
|
{account ? (
|
||||||
account.pubkey === channelPubkey && (
|
account.pubkey === channelPubkey && (
|
||||||
<ChannelUpdateModal id={channelID} />
|
<ChannelUpdateModal id={channelID} />
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,25 +1,23 @@
|
|||||||
import ChatsListItem from "@app/chat/components/item";
|
import ChatsListItem from "@app/chat/components/item";
|
||||||
import ChatsListSelfItem from "@app/chat/components/self";
|
import ChatsListSelfItem from "@app/chat/components/self";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
import { useChats } from "@stores/chats";
|
||||||
import { getChatsByPubkey } from "@utils/storage";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
import useSWR from "swr";
|
|
||||||
|
|
||||||
const fetcher = ([, pubkey]) => getChatsByPubkey(pubkey);
|
|
||||||
|
|
||||||
export default function ChatsList() {
|
export default function ChatsList() {
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
const chats = useChats((state: any) => state.chats);
|
||||||
|
const fetchChats = useChats((state: any) => state.fetch);
|
||||||
|
|
||||||
const { data: chats, error }: any = useSWR(
|
useEffect(() => {
|
||||||
!isLoading && !isError && account ? ["chats", account.pubkey] : null,
|
if (!account) return;
|
||||||
fetcher,
|
fetchChats(account.pubkey);
|
||||||
);
|
}, [fetchChats]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<ChatsListSelfItem />
|
<ChatsListSelfItem />
|
||||||
{!chats || error ? (
|
{!chats ? (
|
||||||
<>
|
<>
|
||||||
<div className="inline-flex h-8 items-center gap-2 rounded-md px-2.5">
|
<div className="inline-flex h-8 items-center gap-2 rounded-md px-2.5">
|
||||||
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800" />
|
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800" />
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import { ChatMessageItem } from "@app/chat/components/messages/item";
|
import { ChatMessageItem } from "@app/chat/components/messages/item";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { sortedChatMessagesAtom } from "@stores/chat";
|
import { sortedChatMessagesAtom } from "@stores/chat";
|
||||||
|
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
|
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { useCallback, useRef } from "react";
|
import { useCallback, useRef } from "react";
|
||||||
import { Virtuoso } from "react-virtuoso";
|
import { Virtuoso } from "react-virtuoso";
|
||||||
|
|
||||||
export default function ChatMessageList() {
|
export default function ChatMessageList() {
|
||||||
const { account } = useActiveAccount();
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const virtuosoRef = useRef(null);
|
const virtuosoRef = useRef(null);
|
||||||
const data = useAtomValue(sortedChatMessagesAtom);
|
const data = useAtomValue(sortedChatMessagesAtom);
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { ImagePicker } from "@shared/form/imagePicker";
|
import { ImagePicker } from "@shared/form/imagePicker";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { chatContentAtom } from "@stores/chat";
|
import { chatContentAtom } from "@stores/chat";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||||
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
|
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useResetAtom } from "jotai/utils";
|
import { useResetAtom } from "jotai/utils";
|
||||||
import { getEventHash, getSignature, nip04 } from "nostr-tools";
|
import { getEventHash, getSignature, nip04 } from "nostr-tools";
|
||||||
@@ -16,7 +13,7 @@ export default function ChatMessageForm({
|
|||||||
receiverPubkey,
|
receiverPubkey,
|
||||||
}: { receiverPubkey: string }) {
|
}: { receiverPubkey: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [value, setValue] = useAtom(chatContentAtom);
|
const [value, setValue] = useAtom(chatContentAtom);
|
||||||
const resetValue = useResetAtom(chatContentAtom);
|
const resetValue = useResetAtom(chatContentAtom);
|
||||||
@@ -29,25 +26,23 @@ export default function ChatMessageForm({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const submitEvent = () => {
|
const submitEvent = () => {
|
||||||
if (!isError && !isLoading && account) {
|
encryptMessage(account.privkey)
|
||||||
encryptMessage(account.privkey)
|
.then((encryptedContent) => {
|
||||||
.then((encryptedContent) => {
|
const event: any = {
|
||||||
const event: any = {
|
content: encryptedContent,
|
||||||
content: encryptedContent,
|
created_at: dateToUnix(),
|
||||||
created_at: dateToUnix(),
|
kind: 4,
|
||||||
kind: 4,
|
pubkey: account.pubkey,
|
||||||
pubkey: account.pubkey,
|
tags: [["p", receiverPubkey]],
|
||||||
tags: [["p", receiverPubkey]],
|
};
|
||||||
};
|
event.id = getEventHash(event);
|
||||||
event.id = getEventHash(event);
|
event.sig = getSignature(event, account.privkey);
|
||||||
event.sig = getSignature(event, account.privkey);
|
// publish note
|
||||||
// publish note
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
// reset state
|
||||||
// reset state
|
resetValue();
|
||||||
resetValue();
|
})
|
||||||
})
|
.catch(console.error);
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEnterPress = (e) => {
|
const handleEnterPress = (e) => {
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { Image } from "@shared/image";
|
import { Image } from "@shared/image";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||||
|
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
import { usePageContext } from "@utils/hooks/usePageContext";
|
import { usePageContext } from "@utils/hooks/usePageContext";
|
||||||
import { shortenKey } from "@utils/shortenKey";
|
import { shortenKey } from "@utils/shortenKey";
|
||||||
|
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
export default function ChatsListSelfItem() {
|
export default function ChatsListSelfItem() {
|
||||||
@@ -14,12 +11,11 @@ export default function ChatsListSelfItem() {
|
|||||||
const searchParams: any = pageContext.urlParsed.search;
|
const searchParams: any = pageContext.urlParsed.search;
|
||||||
const pagePubkey = searchParams.pubkey;
|
const pagePubkey = searchParams.pubkey;
|
||||||
|
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isError && <div>error</div>}
|
{!account ? (
|
||||||
{isLoading && !account ? (
|
|
||||||
<div className="inline-flex h-8 items-center gap-2.5 rounded-md px-2.5">
|
<div className="inline-flex h-8 items-center gap-2.5 rounded-md px-2.5">
|
||||||
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800" />
|
<div className="relative h-5 w-5 shrink-0 animate-pulse rounded bg-zinc-800" />
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import ChatMessageForm from "@app/chat/components/messages/form";
|
import ChatMessageForm from "@app/chat/components/messages/form";
|
||||||
|
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { chatMessagesAtom } from "@stores/chat";
|
import { chatMessagesAtom } from "@stores/chat";
|
||||||
import { READONLY_RELAYS } from "@stores/constants";
|
import { READONLY_RELAYS } from "@stores/constants";
|
||||||
|
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
import { usePageContext } from "@utils/hooks/usePageContext";
|
import { usePageContext } from "@utils/hooks/usePageContext";
|
||||||
|
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import { useResetAtom } from "jotai/utils";
|
import { useResetAtom } from "jotai/utils";
|
||||||
import { Suspense, lazy, useContext, useEffect } from "react";
|
import { Suspense, lazy, useContext, useEffect } from "react";
|
||||||
@@ -17,13 +13,12 @@ const ChatMessageList = lazy(() => import("@app/chat/components/messageList"));
|
|||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const pageContext = usePageContext();
|
const pageContext = usePageContext();
|
||||||
const searchParams: any = pageContext.urlParsed.search;
|
const searchParams: any = pageContext.urlParsed.search;
|
||||||
|
|
||||||
const pubkey = searchParams.pubkey;
|
const pubkey = searchParams.pubkey;
|
||||||
|
|
||||||
const { account } = useActiveAccount();
|
|
||||||
|
|
||||||
const setChatMessages = useSetAtom(chatMessagesAtom);
|
const setChatMessages = useSetAtom(chatMessagesAtom);
|
||||||
const resetChatMessages = useResetAtom(chatMessagesAtom);
|
const resetChatMessages = useResetAtom(chatMessagesAtom);
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
|
import { useEffect } from "react";
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const { account, isLoading } = useActiveAccount();
|
const fetchAccount = useActiveAccount((state: any) => state.fetch);
|
||||||
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
if (!isLoading && !account) {
|
if (!account) {
|
||||||
navigate("/app/auth", { overwriteLastHistoryEntry: true });
|
navigate("/app/auth", { overwriteLastHistoryEntry: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isLoading && account) {
|
if (account) {
|
||||||
navigate("/app/prefetch", { overwriteLastHistoryEntry: true });
|
navigate("/app/prefetch", { overwriteLastHistoryEntry: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchAccount();
|
||||||
|
}, [fetchAccount]);
|
||||||
|
|
||||||
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" />
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import { RelayContext } from "@shared/relayProvider";
|
|
||||||
|
|
||||||
import LikeIcon from "@icons/like";
|
import LikeIcon from "@icons/like";
|
||||||
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||||
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
|
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
@@ -16,33 +12,31 @@ export default function NoteLike({
|
|||||||
likes,
|
likes,
|
||||||
}: { id: string; pubkey: string; likes: number }) {
|
}: { id: string; pubkey: string; likes: number }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [count, setCount] = useState(0);
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
const submitEvent = (e: any) => {
|
const submitEvent = (e: any) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
if (!isLoading && !isError && account) {
|
const event: any = {
|
||||||
const event: any = {
|
content: "+",
|
||||||
content: "+",
|
kind: 7,
|
||||||
kind: 7,
|
tags: [
|
||||||
tags: [
|
["e", id],
|
||||||
["e", id],
|
["p", pubkey],
|
||||||
["p", pubkey],
|
],
|
||||||
],
|
created_at: dateToUnix(),
|
||||||
created_at: dateToUnix(),
|
pubkey: account.pubkey,
|
||||||
pubkey: account.pubkey,
|
};
|
||||||
};
|
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = getSignature(event, account.privkey);
|
event.sig = getSignature(event, account.privkey);
|
||||||
// publish event to all relays
|
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
// publish event to all relays
|
||||||
// update state
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
setCount(count + 1);
|
// update state
|
||||||
} else {
|
setCount(count + 1);
|
||||||
console.log("error");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
import ReplyIcon from "@icons/reply";
|
||||||
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 ReplyIcon from "@icons/reply";
|
|
||||||
|
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||||
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
import { compactNumber } from "@utils/number";
|
import { compactNumber } from "@utils/number";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { Fragment, useContext, useEffect, useState } from "react";
|
import { Fragment, useContext, useEffect, useState } from "react";
|
||||||
@@ -18,13 +14,12 @@ export default function NoteReply({
|
|||||||
replies,
|
replies,
|
||||||
}: { id: string; replies: number }) {
|
}: { id: string; replies: number }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [count, setCount] = useState(0);
|
const [count, setCount] = useState(0);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
|
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
};
|
};
|
||||||
@@ -34,25 +29,24 @@ export default function NoteReply({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const submitEvent = () => {
|
const submitEvent = () => {
|
||||||
if (!isLoading && !isError && account) {
|
const event: any = {
|
||||||
const event: any = {
|
content: value,
|
||||||
content: value,
|
created_at: dateToUnix(),
|
||||||
created_at: dateToUnix(),
|
kind: 1,
|
||||||
kind: 1,
|
pubkey: account.pubkey,
|
||||||
pubkey: account.pubkey,
|
tags: [["e", id]],
|
||||||
tags: [["e", id]],
|
};
|
||||||
};
|
|
||||||
event.id = getEventHash(event);
|
|
||||||
event.sig = getSignature(event, account.privkey);
|
|
||||||
|
|
||||||
// publish event
|
event.id = getEventHash(event);
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
event.sig = getSignature(event, account.privkey);
|
||||||
// close modal
|
|
||||||
setIsOpen(false);
|
// publish event
|
||||||
setCount(count + 1);
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
} else {
|
|
||||||
console.log("error");
|
// close modal
|
||||||
}
|
setIsOpen(false);
|
||||||
|
// increment replies
|
||||||
|
setCount(count + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
import { RelayContext } from "@shared/relayProvider";
|
|
||||||
|
|
||||||
import RepostIcon from "@icons/repost";
|
import RepostIcon from "@icons/repost";
|
||||||
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { WRITEONLY_RELAYS } from "@stores/constants";
|
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||||
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
|
|
||||||
import { compactNumber } from "@utils/number";
|
import { compactNumber } from "@utils/number";
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
@@ -17,33 +13,32 @@ export default function NoteRepost({
|
|||||||
reposts,
|
reposts,
|
||||||
}: { id: string; pubkey: string; reposts: number }) {
|
}: { id: string; pubkey: string; reposts: number }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const [count, setCount] = useState(0);
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
const submitEvent = (e: any) => {
|
const submitEvent = (e: any) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
if (!isLoading && !isError && account) {
|
const event: any = {
|
||||||
const event: any = {
|
content: "",
|
||||||
content: "",
|
kind: 6,
|
||||||
kind: 6,
|
tags: [
|
||||||
tags: [
|
["e", id],
|
||||||
["e", id],
|
["p", pubkey],
|
||||||
["p", pubkey],
|
],
|
||||||
],
|
created_at: dateToUnix(),
|
||||||
created_at: dateToUnix(),
|
pubkey: account.pubkey,
|
||||||
pubkey: account.pubkey,
|
};
|
||||||
};
|
|
||||||
event.id = getEventHash(event);
|
event.id = getEventHash(event);
|
||||||
event.sig = getSignature(event, account.privkey);
|
event.sig = getSignature(event, account.privkey);
|
||||||
// publish event to all relays
|
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
// publish event to all relays
|
||||||
// update state
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
setCount(count + 1);
|
|
||||||
} else {
|
// update state
|
||||||
console.log("error");
|
setCount(count + 1);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,39 +1,34 @@
|
|||||||
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 { WRITEONLY_RELAYS } from "@stores/constants";
|
import { WRITEONLY_RELAYS } from "@stores/constants";
|
||||||
|
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
|
|
||||||
import { getEventHash, getSignature } from "nostr-tools";
|
import { getEventHash, getSignature } from "nostr-tools";
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
|
|
||||||
export default function NoteReplyForm({ id }: { id: string }) {
|
export default function NoteReplyForm({ id }: { id: string }) {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
|
|
||||||
const submitEvent = () => {
|
const submitEvent = () => {
|
||||||
if (!isLoading && !isError && account) {
|
const event: any = {
|
||||||
const event: any = {
|
content: value,
|
||||||
content: value,
|
created_at: dateToUnix(),
|
||||||
created_at: dateToUnix(),
|
kind: 1,
|
||||||
kind: 1,
|
pubkey: account.pubkey,
|
||||||
pubkey: account.pubkey,
|
tags: [["e", id]],
|
||||||
tags: [["e", id]],
|
};
|
||||||
};
|
|
||||||
event.id = getEventHash(event);
|
|
||||||
event.sig = getSignature(event, account.privkey);
|
|
||||||
|
|
||||||
// publish note
|
event.id = getEventHash(event);
|
||||||
pool.publish(event, WRITEONLY_RELAYS);
|
event.sig = getSignature(event, account.privkey);
|
||||||
// reset form
|
|
||||||
setValue("");
|
// publish note
|
||||||
} else {
|
pool.publish(event, WRITEONLY_RELAYS);
|
||||||
console.log("error");
|
|
||||||
}
|
// reset form
|
||||||
|
setValue("");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import LumeIcon from "@icons/lume";
|
import LumeIcon from "@icons/lume";
|
||||||
import { RelayContext } from "@shared/relayProvider";
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { READONLY_RELAYS } from "@stores/constants";
|
import { READONLY_RELAYS } from "@stores/constants";
|
||||||
import { dateToUnix, getHourAgo } from "@utils/date";
|
import { dateToUnix, getHourAgo } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
import {
|
import {
|
||||||
addToBlacklist,
|
addToBlacklist,
|
||||||
countTotalNotes,
|
countTotalNotes,
|
||||||
@@ -15,15 +15,6 @@ import { useCallback, useContext, useRef } from "react";
|
|||||||
import useSWRSubscription from "swr/subscription";
|
import useSWRSubscription from "swr/subscription";
|
||||||
import { navigate } from "vite-plugin-ssr/client/router";
|
import { navigate } from "vite-plugin-ssr/client/router";
|
||||||
|
|
||||||
function isJSON(str: string) {
|
|
||||||
try {
|
|
||||||
JSON.parse(str);
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lastLogin: string;
|
let lastLogin: string;
|
||||||
let totalNotes: number;
|
let totalNotes: number;
|
||||||
|
|
||||||
@@ -34,12 +25,11 @@ if (typeof window !== "undefined") {
|
|||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const now = useRef(new Date());
|
const now = useRef(new Date());
|
||||||
const eose = useRef(0);
|
const eose = useRef(0);
|
||||||
|
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
|
||||||
|
|
||||||
const getQuery = useCallback(() => {
|
const getQuery = useCallback(() => {
|
||||||
const query = [];
|
const query = [];
|
||||||
const follows = JSON.parse(account.follows);
|
const follows = JSON.parse(account.follows);
|
||||||
@@ -79,98 +69,95 @@ export function Page() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
}, [account.follows]);
|
}, [account]);
|
||||||
|
|
||||||
useSWRSubscription(
|
useSWRSubscription(account ? "prefetch" : null, () => {
|
||||||
!isLoading && !isError && account ? "prefetch" : null,
|
const query = getQuery();
|
||||||
() => {
|
const unsubscribe = pool.subscribe(
|
||||||
const query = getQuery();
|
query,
|
||||||
const unsubscribe = pool.subscribe(
|
READONLY_RELAYS,
|
||||||
query,
|
(event: any) => {
|
||||||
READONLY_RELAYS,
|
switch (event.kind) {
|
||||||
(event: any) => {
|
// short text note
|
||||||
switch (event.kind) {
|
case 1: {
|
||||||
// short text note
|
const parentID = getParentID(event.tags, event.id);
|
||||||
case 1: {
|
// insert event to local database
|
||||||
const parentID = getParentID(event.tags, event.id);
|
createNote(
|
||||||
// insert event to local database
|
event.id,
|
||||||
createNote(
|
account.id,
|
||||||
event.id,
|
event.pubkey,
|
||||||
account.id,
|
event.kind,
|
||||||
event.pubkey,
|
event.tags,
|
||||||
event.kind,
|
event.content,
|
||||||
event.tags,
|
event.created_at,
|
||||||
event.content,
|
parentID,
|
||||||
event.created_at,
|
);
|
||||||
parentID,
|
break;
|
||||||
);
|
}
|
||||||
break;
|
// chat
|
||||||
|
case 4:
|
||||||
|
createChat(
|
||||||
|
event.id,
|
||||||
|
account.pubkey,
|
||||||
|
event.pubkey,
|
||||||
|
event.content,
|
||||||
|
event.created_at,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
// repost
|
||||||
|
case 6:
|
||||||
|
createNote(
|
||||||
|
event.id,
|
||||||
|
account.id,
|
||||||
|
event.pubkey,
|
||||||
|
event.kind,
|
||||||
|
event.tags,
|
||||||
|
event.content,
|
||||||
|
event.created_at,
|
||||||
|
event.id,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
// hide message (channel only)
|
||||||
|
case 43:
|
||||||
|
if (event.tags[0][0] === "e") {
|
||||||
|
addToBlacklist(account.id, event.tags[0][1], 43, 1);
|
||||||
}
|
}
|
||||||
// chat
|
break;
|
||||||
case 4:
|
// mute user (channel only)
|
||||||
createChat(
|
case 44:
|
||||||
event.id,
|
if (event.tags[0][0] === "p") {
|
||||||
account.pubkey,
|
addToBlacklist(account.id, event.tags[0][1], 44, 1);
|
||||||
event.pubkey,
|
}
|
||||||
event.content,
|
break;
|
||||||
event.created_at,
|
case 1063:
|
||||||
);
|
createNote(
|
||||||
break;
|
event.id,
|
||||||
// repost
|
account.id,
|
||||||
case 6:
|
event.pubkey,
|
||||||
createNote(
|
event.kind,
|
||||||
event.id,
|
event.tags,
|
||||||
account.id,
|
event.content,
|
||||||
event.pubkey,
|
event.created_at,
|
||||||
event.kind,
|
"",
|
||||||
event.tags,
|
);
|
||||||
event.content,
|
break;
|
||||||
event.created_at,
|
default:
|
||||||
event.id,
|
break;
|
||||||
);
|
}
|
||||||
break;
|
},
|
||||||
// hide message (channel only)
|
undefined,
|
||||||
case 43:
|
() => {
|
||||||
if (event.tags[0][0] === "e") {
|
eose.current += 1;
|
||||||
addToBlacklist(account.id, event.tags[0][1], 43, 1);
|
if (eose.current === READONLY_RELAYS.length) {
|
||||||
}
|
navigate("/app/space", { overwriteLastHistoryEntry: true });
|
||||||
break;
|
}
|
||||||
// mute user (channel only)
|
},
|
||||||
case 44:
|
);
|
||||||
if (event.tags[0][0] === "p") {
|
|
||||||
addToBlacklist(account.id, event.tags[0][1], 44, 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 1063:
|
|
||||||
createNote(
|
|
||||||
event.id,
|
|
||||||
account.id,
|
|
||||||
event.pubkey,
|
|
||||||
event.kind,
|
|
||||||
event.tags,
|
|
||||||
event.content,
|
|
||||||
event.created_at,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
() => {
|
|
||||||
eose.current += 1;
|
|
||||||
if (eose.current === READONLY_RELAYS.length) {
|
|
||||||
navigate("/app/space", { overwriteLastHistoryEntry: true });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
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">
|
||||||
|
|||||||
@@ -3,19 +3,15 @@ import { Dialog, Transition } from "@headlessui/react";
|
|||||||
import CancelIcon from "@icons/cancel";
|
import CancelIcon from "@icons/cancel";
|
||||||
import PlusIcon from "@icons/plus";
|
import PlusIcon from "@icons/plus";
|
||||||
import { Image } from "@shared/image";
|
import { Image } from "@shared/image";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { DEFAULT_AVATAR } from "@stores/constants";
|
import { DEFAULT_AVATAR } from "@stores/constants";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
import { createBlock } from "@utils/storage";
|
||||||
import { createBlock, getPlebs } from "@utils/storage";
|
|
||||||
import { Fragment, useEffect, useState } from "react";
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import useSWR from "swr";
|
|
||||||
|
|
||||||
const fetcher = () => getPlebs();
|
|
||||||
|
|
||||||
export function CreateBlockModal() {
|
export function CreateBlockModal() {
|
||||||
const { account } = useActiveAccount();
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
const { register, handleSubmit, reset, watch, setValue } = useForm();
|
const { register, handleSubmit, reset, watch, setValue } = useForm();
|
||||||
const { data: plebs } = useSWR("plebs", fetcher);
|
|
||||||
|
|
||||||
const [image, setImage] = useState(DEFAULT_AVATAR);
|
const [image, setImage] = useState(DEFAULT_AVATAR);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|||||||
@@ -1,24 +1,18 @@
|
|||||||
import { Post } from "@shared/composer/types/post";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { User } from "@shared/composer/user";
|
|
||||||
|
|
||||||
import CancelIcon from "@icons/cancel";
|
import CancelIcon from "@icons/cancel";
|
||||||
import ChevronDownIcon from "@icons/chevronDown";
|
import ChevronDownIcon from "@icons/chevronDown";
|
||||||
import ChevronRightIcon from "@icons/chevronRight";
|
import ChevronRightIcon from "@icons/chevronRight";
|
||||||
import ComposeIcon from "@icons/compose";
|
import ComposeIcon from "@icons/compose";
|
||||||
|
import { Post } from "@shared/composer/types/post";
|
||||||
import { composerAtom } from "@stores/composer";
|
import { User } from "@shared/composer/user";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
|
||||||
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
import { useAtom } from "jotai";
|
|
||||||
import { Fragment, useState } from "react";
|
import { Fragment, useState } from "react";
|
||||||
|
|
||||||
export function ComposerModal() {
|
export function ComposerModal() {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [composer] = useAtom(composerAtom);
|
const [composer] = useState({ type: "post" });
|
||||||
|
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
@@ -64,11 +58,7 @@ export function ComposerModal() {
|
|||||||
<Dialog.Panel className="relative h-min w-full max-w-xl rounded-lg border border-zinc-800 bg-zinc-900">
|
<Dialog.Panel className="relative h-min w-full max-w-xl rounded-lg border border-zinc-800 bg-zinc-900">
|
||||||
<div className="flex items-center justify-between px-4 py-4">
|
<div className="flex items-center justify-between px-4 py-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div>
|
<div>{account && <User data={account} />}</div>
|
||||||
{!isLoading && !isError && account && (
|
|
||||||
<User data={account} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<span>
|
<span>
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon
|
||||||
width={14}
|
width={14}
|
||||||
|
|||||||
@@ -1,141 +1,119 @@
|
|||||||
import { RelayContext } from "@shared/relayProvider";
|
|
||||||
|
|
||||||
import HeartBeatIcon from "@icons/heartbeat";
|
import HeartBeatIcon from "@icons/heartbeat";
|
||||||
|
import { RelayContext } from "@shared/relayProvider";
|
||||||
|
import { useActiveAccount } from "@stores/accounts";
|
||||||
import { READONLY_RELAYS } from "@stores/constants";
|
import { READONLY_RELAYS } from "@stores/constants";
|
||||||
import { hasNewerNoteAtom } from "@stores/note";
|
import { hasNewerNoteAtom } from "@stores/note";
|
||||||
|
import { TauriEvent } from "@tauri-apps/api/event";
|
||||||
|
import { appWindow, getCurrent } from "@tauri-apps/api/window";
|
||||||
import { dateToUnix } from "@utils/date";
|
import { dateToUnix } from "@utils/date";
|
||||||
import { useActiveAccount } from "@utils/hooks/useActiveAccount";
|
import {
|
||||||
import { createChat, createNote, updateAccount } from "@utils/storage";
|
createChat,
|
||||||
|
createNote,
|
||||||
|
updateAccount,
|
||||||
|
updateLastLogin,
|
||||||
|
} from "@utils/storage";
|
||||||
import { getParentID, nip02ToArray } from "@utils/transform";
|
import { getParentID, nip02ToArray } from "@utils/transform";
|
||||||
|
|
||||||
import { useSetAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import { useContext, useRef } from "react";
|
import { useContext, useEffect, useRef } from "react";
|
||||||
import useSWRSubscription from "swr/subscription";
|
import useSWRSubscription from "swr/subscription";
|
||||||
|
|
||||||
function isJSON(str: string) {
|
|
||||||
try {
|
|
||||||
JSON.parse(str);
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function EventCollector() {
|
export default function EventCollector() {
|
||||||
const pool: any = useContext(RelayContext);
|
const pool: any = useContext(RelayContext);
|
||||||
|
|
||||||
const setHasNewerNote = useSetAtom(hasNewerNoteAtom);
|
const setHasNewerNote = useSetAtom(hasNewerNoteAtom);
|
||||||
|
|
||||||
|
const account = useActiveAccount((state: any) => state.account);
|
||||||
const now = useRef(new Date());
|
const now = useRef(new Date());
|
||||||
|
|
||||||
const { account, isLoading, isError } = useActiveAccount();
|
useSWRSubscription(account ? "eventCollector" : null, () => {
|
||||||
|
const follows = JSON.parse(account.follows);
|
||||||
useSWRSubscription(
|
const unsubscribe = pool.subscribe(
|
||||||
!isLoading && !isError && account ? ["eventCollector", account] : null,
|
[
|
||||||
([, key]) => {
|
{
|
||||||
const follows = JSON.parse(key.follows);
|
kinds: [1, 6],
|
||||||
const followsAsArray = nip02ToArray(follows);
|
authors: follows,
|
||||||
const unsubscribe = pool.subscribe(
|
since: dateToUnix(now.current),
|
||||||
[
|
|
||||||
{
|
|
||||||
kinds: [1, 6],
|
|
||||||
authors: followsAsArray,
|
|
||||||
since: dateToUnix(now.current),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kinds: [3],
|
|
||||||
authors: [key.pubkey],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kinds: [4],
|
|
||||||
"#p": [key.pubkey],
|
|
||||||
since: dateToUnix(now.current),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
kinds: [30023],
|
|
||||||
since: dateToUnix(now.current),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
READONLY_RELAYS,
|
|
||||||
(event: any) => {
|
|
||||||
switch (event.kind) {
|
|
||||||
// short text note
|
|
||||||
case 1: {
|
|
||||||
const parentID = getParentID(event.tags, event.id);
|
|
||||||
createNote(
|
|
||||||
event.id,
|
|
||||||
account.id,
|
|
||||||
event.pubkey,
|
|
||||||
event.kind,
|
|
||||||
event.tags,
|
|
||||||
event.content,
|
|
||||||
event.created_at,
|
|
||||||
parentID,
|
|
||||||
);
|
|
||||||
// notify user reload to get newer note
|
|
||||||
setHasNewerNote(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// contacts
|
|
||||||
case 3: {
|
|
||||||
const follows = nip02ToArray(event.tags);
|
|
||||||
// update account's folllows with NIP-02 tag list
|
|
||||||
updateAccount("follows", follows, event.pubkey);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// chat
|
|
||||||
case 4:
|
|
||||||
createChat(
|
|
||||||
event.id,
|
|
||||||
key.pubkey,
|
|
||||||
event.pubkey,
|
|
||||||
event.content,
|
|
||||||
event.created_at,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
// repost
|
|
||||||
case 6:
|
|
||||||
createNote(
|
|
||||||
event.id,
|
|
||||||
key.id,
|
|
||||||
event.pubkey,
|
|
||||||
event.kind,
|
|
||||||
event.tags,
|
|
||||||
event.content,
|
|
||||||
event.created_at,
|
|
||||||
event.id,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
// long post
|
|
||||||
case 30023: {
|
|
||||||
const verifyMetadata = isJSON(event.tags);
|
|
||||||
if (verifyMetadata) {
|
|
||||||
// insert event to local database
|
|
||||||
createNote(
|
|
||||||
event.id,
|
|
||||||
account.id,
|
|
||||||
event.pubkey,
|
|
||||||
event.kind,
|
|
||||||
event.tags,
|
|
||||||
event.content,
|
|
||||||
event.created_at,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
{
|
||||||
|
kinds: [3],
|
||||||
|
authors: [account.pubkey],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kinds: [4],
|
||||||
|
"#p": [account.pubkey],
|
||||||
|
since: dateToUnix(now.current),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
READONLY_RELAYS,
|
||||||
|
(event: any) => {
|
||||||
|
switch (event.kind) {
|
||||||
|
// short text note
|
||||||
|
case 1: {
|
||||||
|
const parentID = getParentID(event.tags, event.id);
|
||||||
|
createNote(
|
||||||
|
event.id,
|
||||||
|
account.id,
|
||||||
|
event.pubkey,
|
||||||
|
event.kind,
|
||||||
|
event.tags,
|
||||||
|
event.content,
|
||||||
|
event.created_at,
|
||||||
|
parentID,
|
||||||
|
);
|
||||||
|
// notify user reload to get newer note
|
||||||
|
setHasNewerNote(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// contacts
|
||||||
|
case 3: {
|
||||||
|
const follows = nip02ToArray(event.tags);
|
||||||
|
// update account's folllows with NIP-02 tag list
|
||||||
|
updateAccount("follows", follows, event.pubkey);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// chat
|
||||||
|
case 4:
|
||||||
|
createChat(
|
||||||
|
event.id,
|
||||||
|
account.pubkey,
|
||||||
|
event.pubkey,
|
||||||
|
event.content,
|
||||||
|
event.created_at,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
// repost
|
||||||
|
case 6:
|
||||||
|
createNote(
|
||||||
|
event.id,
|
||||||
|
account.id,
|
||||||
|
event.pubkey,
|
||||||
|
event.kind,
|
||||||
|
event.tags,
|
||||||
|
event.content,
|
||||||
|
event.created_at,
|
||||||
|
event.id,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
useEffect(() => {
|
||||||
|
// listen window close event
|
||||||
|
getCurrent().listen(TauriEvent.WINDOW_CLOSE_REQUESTED, () => {
|
||||||
|
// update last login time
|
||||||
|
updateLastLogin(dateToUnix(now.current));
|
||||||
|
// close window
|
||||||
|
appWindow.close();
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inline-flex h-6 w-6 items-center justify-center rounded text-zinc-500 hover:bg-zinc-900 hover:text-green-500">
|
<div className="inline-flex h-6 w-6 items-center justify-center rounded text-zinc-500 hover:bg-zinc-900 hover:text-green-500">
|
||||||
|
|||||||
13
src/stores/accounts.tsx
Normal file
13
src/stores/accounts.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { getActiveAccount } from "@utils/storage";
|
||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
export const useActiveAccount = create((set) => ({
|
||||||
|
account: null,
|
||||||
|
fetch: async () => {
|
||||||
|
const response = await getActiveAccount();
|
||||||
|
set({ account: response });
|
||||||
|
},
|
||||||
|
updateFollows: (list: any) => {
|
||||||
|
set((state: any) => ({ account: { ...state.account, follows: list } }));
|
||||||
|
},
|
||||||
|
}));
|
||||||
17
src/stores/channels.tsx
Normal file
17
src/stores/channels.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { getChannels } from "@utils/storage";
|
||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
export const useChannels = create((set) => ({
|
||||||
|
channels: [],
|
||||||
|
fetch: async () => {
|
||||||
|
const response = await getChannels(10, 0);
|
||||||
|
set({ channels: response });
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const useChannelMessage = create((set) => ({
|
||||||
|
messages: [],
|
||||||
|
add: (message: any) => {
|
||||||
|
set((state: any) => ({ messages: [...state.messages, message] }));
|
||||||
|
},
|
||||||
|
}));
|
||||||
21
src/stores/chats.tsx
Normal file
21
src/stores/chats.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { getChatMessages, getChatsByPubkey } from "@utils/storage";
|
||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
export const useChats = create((set) => ({
|
||||||
|
chats: [],
|
||||||
|
fetch: async (pubkey: string) => {
|
||||||
|
const response = await getChatsByPubkey(pubkey);
|
||||||
|
set({ chats: response });
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const useChatMessages = create((set) => ({
|
||||||
|
messages: [],
|
||||||
|
fetch: async (receiver_pubkey: string, sender_pubkey: string) => {
|
||||||
|
const response = await getChatMessages(receiver_pubkey, sender_pubkey);
|
||||||
|
set({ messages: response });
|
||||||
|
},
|
||||||
|
add: (message: any) => {
|
||||||
|
set((state: any) => ({ messages: [...state.messages, message] }));
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { atom } from "jotai";
|
|
||||||
|
|
||||||
export const composerAtom = atom({ type: "post" });
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { atom } from "jotai";
|
|
||||||
import { atomWithReset } from "jotai/utils";
|
|
||||||
|
|
||||||
// note content
|
|
||||||
export const noteContentAtom = atomWithReset("");
|
|
||||||
|
|
||||||
// notify user that connector has receive newer note
|
|
||||||
export const hasNewerNoteAtom = atom(false);
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { atom } from "jotai";
|
|
||||||
|
|
||||||
export const onboardingAtom = atom({
|
|
||||||
pubkey: null,
|
|
||||||
privkey: null,
|
|
||||||
metadata: null,
|
|
||||||
follows: null,
|
|
||||||
});
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { getActiveAccount } from "@utils/storage";
|
|
||||||
import useSWR from "swr";
|
|
||||||
|
|
||||||
const fetcher = () => getActiveAccount();
|
|
||||||
|
|
||||||
export function useActiveAccount() {
|
|
||||||
const { data, error, isLoading } = useSWR("activeAcount", fetcher);
|
|
||||||
|
|
||||||
return {
|
|
||||||
account: data,
|
|
||||||
isLoading,
|
|
||||||
isError: error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -272,7 +272,7 @@ export async function updateChannelMetadata(event_id: string, value: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all chats
|
// get all chats by pubkey
|
||||||
export async function getChatsByPubkey(pubkey: string) {
|
export async function getChatsByPubkey(pubkey: string) {
|
||||||
const db = await connect();
|
const db = await connect();
|
||||||
return await db.select(
|
return await db.select(
|
||||||
@@ -280,6 +280,17 @@ export async function getChatsByPubkey(pubkey: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get chat messages
|
||||||
|
export async function getChatMessages(
|
||||||
|
receiver_pubkey: string,
|
||||||
|
sender_pubkey: string,
|
||||||
|
) {
|
||||||
|
const db = await connect();
|
||||||
|
return await db.select(
|
||||||
|
`SELECT * FROM chats WHERE receiver_pubkey = "${receiver_pubkey}" AND sender_pubkey = "${sender_pubkey}" ORDER BY created_at ASC;`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// create chat
|
// create chat
|
||||||
export async function createChat(
|
export async function createChat(
|
||||||
event_id: string,
|
event_id: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user