wip: migrate to ark
This commit is contained in:
@@ -7,7 +7,7 @@ import { ChatsScreen } from '@app/chats';
|
|||||||
import { ErrorScreen } from '@app/error';
|
import { ErrorScreen } from '@app/error';
|
||||||
import { ExploreScreen } from '@app/explore';
|
import { ExploreScreen } from '@app/explore';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
import { AppLayout } from '@shared/layouts/app';
|
import { AppLayout } from '@shared/layouts/app';
|
||||||
@@ -19,12 +19,12 @@ import { SettingsLayout } from '@shared/layouts/settings';
|
|||||||
import './app.css';
|
import './app.css';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
|
|
||||||
const accountLoader = async () => {
|
const accountLoader = async () => {
|
||||||
try {
|
try {
|
||||||
// redirect to welcome screen if none user exist
|
// redirect to welcome screen if none user exist
|
||||||
const totalAccount = await db.checkAccount();
|
const totalAccount = await ark.checkAccount();
|
||||||
if (totalAccount === 0) return redirect('/auth/welcome');
|
if (totalAccount === 0) return redirect('/auth/welcome');
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import { isPermissionGranted, requestPermission } from '@tauri-apps/plugin-notif
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { InfoIcon } from '@shared/icons';
|
import { InfoIcon } from '@shared/icons';
|
||||||
|
|
||||||
export function OnboardingScreen() {
|
export function OnboardingScreen() {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [settings, setSettings] = useState({
|
const [settings, setSettings] = useState({
|
||||||
@@ -18,19 +18,19 @@ export function OnboardingScreen() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const next = () => {
|
const next = () => {
|
||||||
if (!db.account.contacts.length) return navigate('/auth/follow');
|
if (!ark.account.contacts.length) return navigate('/auth/follow');
|
||||||
return navigate('/auth/finish');
|
return navigate('/auth/finish');
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleOutbox = async () => {
|
const toggleOutbox = async () => {
|
||||||
await db.createSetting('outbox', String(+!settings.outbox));
|
await ark.createSetting('outbox', String(+!settings.outbox));
|
||||||
// update state
|
// update state
|
||||||
setSettings((prev) => ({ ...prev, outbox: !settings.outbox }));
|
setSettings((prev) => ({ ...prev, outbox: !settings.outbox }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleAutoupdate = async () => {
|
const toggleAutoupdate = async () => {
|
||||||
await db.createSetting('autoupdate', String(+!settings.autoupdate));
|
await ark.createSetting('autoupdate', String(+!settings.autoupdate));
|
||||||
db.settings.autoupdate = !settings.autoupdate;
|
ark.settings.autoupdate = !settings.autoupdate;
|
||||||
// update state
|
// update state
|
||||||
setSettings((prev) => ({ ...prev, autoupdate: !settings.autoupdate }));
|
setSettings((prev) => ({ ...prev, autoupdate: !settings.autoupdate }));
|
||||||
};
|
};
|
||||||
@@ -46,7 +46,7 @@ export function OnboardingScreen() {
|
|||||||
const permissionGranted = await isPermissionGranted();
|
const permissionGranted = await isPermissionGranted();
|
||||||
setSettings((prev) => ({ ...prev, notification: permissionGranted }));
|
setSettings((prev) => ({ ...prev, notification: permissionGranted }));
|
||||||
|
|
||||||
const data = await db.getAllSettings();
|
const data = await ark.getAllSettings();
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||||
import { Dispatch, SetStateAction, useState } from 'react';
|
import { Dispatch, SetStateAction, useState } from 'react';
|
||||||
|
|
||||||
import { LoaderIcon, MediaIcon } from '@shared/icons';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
import { LoaderIcon, MediaIcon } from '@shared/icons';
|
||||||
|
|
||||||
export function MediaUploader({
|
export function MediaUploader({
|
||||||
setState,
|
setState,
|
||||||
}: {
|
}: {
|
||||||
setState: Dispatch<SetStateAction<string>>;
|
setState: Dispatch<SetStateAction<string>>;
|
||||||
}) {
|
}) {
|
||||||
const { upload } = useNostr();
|
const { ark } = useArk();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const uploadMedia = async () => {
|
const uploadMedia = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const image = await upload(['mp4', 'mp3', 'webm', 'mkv', 'avi', 'mov']);
|
const image = await ark.upload({
|
||||||
|
fileExts: ['mp4', 'mp3', 'webm', 'mkv', 'avi', 'mov'],
|
||||||
|
});
|
||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
setState((prev: string) => `${prev}\n${image}`);
|
setState((prev: string) => `${prev}\n${image}`);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { writeTextFile } from '@tauri-apps/plugin-fs';
|
|||||||
import { relaunch } from '@tauri-apps/plugin-process';
|
import { relaunch } from '@tauri-apps/plugin-process';
|
||||||
import { useRouteError } from 'react-router-dom';
|
import { useRouteError } from 'react-router-dom';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
interface RouteError {
|
interface RouteError {
|
||||||
statusText: string;
|
statusText: string;
|
||||||
@@ -12,7 +12,7 @@ interface RouteError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ErrorScreen() {
|
export function ErrorScreen() {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
const error = useRouteError() as RouteError;
|
const error = useRouteError() as RouteError;
|
||||||
|
|
||||||
const restart = async () => {
|
const restart = async () => {
|
||||||
@@ -26,18 +26,18 @@ export function ErrorScreen() {
|
|||||||
const filePath = await save({
|
const filePath = await save({
|
||||||
defaultPath: downloadPath + '/' + fileName,
|
defaultPath: downloadPath + '/' + fileName,
|
||||||
});
|
});
|
||||||
const nsec = await db.secureLoad(db.account.pubkey);
|
const nsec = await ark.loadPrivkey(ark.account.pubkey);
|
||||||
|
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
if (nsec) {
|
if (nsec) {
|
||||||
await writeTextFile(
|
await writeTextFile(
|
||||||
filePath,
|
filePath,
|
||||||
`Nostr account, generated by Lume (lume.nu)\nPublic key: ${db.account.id}\nPrivate key: ${nsec}`
|
`Nostr account, generated by Lume (lume.nu)\nPublic key: ${ark.account.id}\nPrivate key: ${nsec}`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await writeTextFile(
|
await writeTextFile(
|
||||||
filePath,
|
filePath,
|
||||||
`Nostr account, generated by Lume (lume.nu)\nPublic key: ${db.account.id}`
|
`Nostr account, generated by Lume (lume.nu)\nPublic key: ${ark.account.id}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} // else { user cancel action }
|
} // else { user cancel action }
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { useCallback, useRef, useState } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import { VList, VListHandle } from 'virtua';
|
import { VList, VListHandle } from 'virtua';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
import {
|
import {
|
||||||
@@ -28,11 +28,11 @@ export function HomeScreen() {
|
|||||||
const ref = useRef<VListHandle>(null);
|
const ref = useRef<VListHandle>(null);
|
||||||
const [selectedIndex, setSelectedIndex] = useState(-1);
|
const [selectedIndex, setSelectedIndex] = useState(-1);
|
||||||
|
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
const { status, data } = useQuery({
|
const { status, data } = useQuery({
|
||||||
queryKey: ['widgets'],
|
queryKey: ['widgets'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const dbWidgets = await db.getWidgets();
|
const dbWidgets = await ark.getWidgets();
|
||||||
const defaultWidgets = [
|
const defaultWidgets = [
|
||||||
{
|
{
|
||||||
id: '9999',
|
id: '9999',
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { message } from '@tauri-apps/plugin-dialog';
|
|||||||
import { Editor } from '@tiptap/react';
|
import { Editor } from '@tiptap/react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { MediaIcon } from '@shared/icons';
|
import { MediaIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
|
||||||
|
|
||||||
export function MediaUploader({ editor }: { editor: Editor }) {
|
export function MediaUploader({ editor }: { editor: Editor }) {
|
||||||
const { upload } = useNostr();
|
const { ark } = useArk();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const uploadToNostrBuild = async () => {
|
const uploadToNostrBuild = async () => {
|
||||||
@@ -15,7 +15,9 @@ export function MediaUploader({ editor }: { editor: Editor }) {
|
|||||||
// start loading
|
// start loading
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const image = await upload(['mp4', 'mp3', 'webm', 'mkv', 'avi', 'mov']);
|
const image = await ark.upload({
|
||||||
|
fileExts: ['mp4', 'mp3', 'webm', 'mkv', 'avi', 'mov'],
|
||||||
|
});
|
||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
editor.commands.setImage({ src: image });
|
editor.commands.setImage({ src: image });
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import { nip19 } from 'nostr-tools';
|
|||||||
|
|
||||||
import { MentionPopupItem } from '@app/new/components';
|
import { MentionPopupItem } from '@app/new/components';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { MentionIcon } from '@shared/icons';
|
import { MentionIcon } from '@shared/icons';
|
||||||
|
|
||||||
export function MentionPopup({ editor }: { editor: Editor }) {
|
export function MentionPopup({ editor }: { editor: Editor }) {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
|
|
||||||
const insertMention = (pubkey: string) => {
|
const insertMention = (pubkey: string) => {
|
||||||
editor.commands.insertContent(`nostr:${nip19.npubEncode(pubkey)}`);
|
editor.commands.insertContent(`nostr:${nip19.npubEncode(pubkey)}`);
|
||||||
@@ -32,8 +32,8 @@ export function MentionPopup({ editor }: { editor: Editor }) {
|
|||||||
className="h-full max-h-[200px] w-[250px] overflow-hidden overflow-y-auto rounded-lg border border-neutral-200 bg-neutral-100 focus:outline-none dark:border-neutral-800 dark:bg-neutral-900"
|
className="h-full max-h-[200px] w-[250px] overflow-hidden overflow-y-auto rounded-lg border border-neutral-200 bg-neutral-100 focus:outline-none dark:border-neutral-800 dark:bg-neutral-900"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-1 py-1">
|
<div className="flex flex-col gap-1 py-1">
|
||||||
{db.account.contacts.length > 0 ? (
|
{ark.account.contacts.length ? (
|
||||||
db.account.contacts.map((item) => (
|
ark.account.contacts.map((item) => (
|
||||||
<button key={item} type="button" onClick={() => insertMention(item)}>
|
<button key={item} type="button" onClick={() => insertMention(item)}>
|
||||||
<MentionPopupItem pubkey={item} />
|
<MentionPopupItem pubkey={item} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { useRef, useState } from 'react';
|
|||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { ArrowLeftIcon, CheckCircleIcon, ReplyIcon, ShareIcon } from '@shared/icons';
|
import { ArrowLeftIcon, CheckCircleIcon, ReplyIcon, ShareIcon } from '@shared/icons';
|
||||||
import {
|
import {
|
||||||
ChildNote,
|
ChildNote,
|
||||||
@@ -18,15 +20,14 @@ import { ReplyList } from '@shared/notes/replies/list';
|
|||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
import { useEvent } from '@utils/hooks/useEvent';
|
import { useEvent } from '@utils/hooks/useEvent';
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
|
||||||
|
|
||||||
export function TextNoteScreen() {
|
export function TextNoteScreen() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const replyRef = useRef(null);
|
const replyRef = useRef(null);
|
||||||
|
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
|
const { ark } = useArk();
|
||||||
const { status, data } = useEvent(id);
|
const { status, data } = useEvent(id);
|
||||||
const { getEventThread } = useNostr();
|
|
||||||
|
|
||||||
const [isCopy, setIsCopy] = useState(false);
|
const [isCopy, setIsCopy] = useState(false);
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ export function TextNoteScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderKind = (event: NDKEvent) => {
|
const renderKind = (event: NDKEvent) => {
|
||||||
const thread = getEventThread(event.tags);
|
const thread = ark.getEventThread({ tags: event.tags });
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case NDKKind.Text:
|
case NDKKind.Text:
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
export function NWCForm({ setWalletConnectURL }) {
|
export function NWCForm({ setWalletConnectURL }) {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
|
|
||||||
const [uri, setUri] = useState('');
|
const [uri, setUri] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -27,7 +27,7 @@ export function NWCForm({ setWalletConnectURL }) {
|
|||||||
const params = new URLSearchParams(uriObj.search);
|
const params = new URLSearchParams(uriObj.search);
|
||||||
|
|
||||||
if (params.has('relay') && params.has('secret')) {
|
if (params.has('relay') && params.has('secret')) {
|
||||||
await db.secureSave(`${db.account.pubkey}-nwc`, uri);
|
await ark.createPrivkey(`${ark.account.pubkey}-nwc`, uri);
|
||||||
setWalletConnectURL(uri);
|
setWalletConnectURL(uri);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,22 +2,22 @@ import { useEffect, useState } from 'react';
|
|||||||
|
|
||||||
import { NWCForm } from '@app/nwc/components/form';
|
import { NWCForm } from '@app/nwc/components/form';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { CheckCircleIcon } from '@shared/icons';
|
import { CheckCircleIcon } from '@shared/icons';
|
||||||
|
|
||||||
export function NWCScreen() {
|
export function NWCScreen() {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
const [walletConnectURL, setWalletConnectURL] = useState<null | string>(null);
|
const [walletConnectURL, setWalletConnectURL] = useState<null | string>(null);
|
||||||
|
|
||||||
const remove = async () => {
|
const remove = async () => {
|
||||||
await db.secureRemove(`${db.account.pubkey}-nwc`);
|
await ark.removePrivkey(`${ark.account.pubkey}-nwc`);
|
||||||
setWalletConnectURL(null);
|
setWalletConnectURL(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getNWC() {
|
async function getNWC() {
|
||||||
const nwc = await db.secureLoad(`${db.account.pubkey}-nwc`);
|
const nwc = await ark.loadPrivkey(`${ark.account.pubkey}-nwc`);
|
||||||
if (nwc) setWalletConnectURL(nwc);
|
if (nwc) setWalletConnectURL(nwc);
|
||||||
}
|
}
|
||||||
getNWC();
|
getNWC();
|
||||||
|
|||||||
@@ -2,19 +2,20 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { VList } from 'virtua';
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { LoaderIcon, PlusIcon, ShareIcon } from '@shared/icons';
|
import { LoaderIcon, PlusIcon, ShareIcon } from '@shared/icons';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
|
||||||
import { useRelay } from '@utils/hooks/useRelay';
|
import { useRelay } from '@utils/hooks/useRelay';
|
||||||
|
|
||||||
export function RelayList() {
|
export function RelayList() {
|
||||||
const { getAllRelaysByUsers } = useNostr();
|
const { ark } = useArk();
|
||||||
const { connectRelay } = useRelay();
|
const { connectRelay } = useRelay();
|
||||||
const { status, data } = useQuery({
|
const { status, data } = useQuery({
|
||||||
queryKey: ['relays'],
|
queryKey: ['relays'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return await getAllRelaysByUsers();
|
return await ark.getAllRelaysFromContacts();
|
||||||
},
|
},
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
|
|||||||
@@ -1,29 +1,26 @@
|
|||||||
import { NDKKind, NDKSubscriptionCacheUsage } from '@nostr-dev-kit/ndk';
|
import { NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { RelayForm } from '@app/relays/components/relayForm';
|
import { RelayForm } from '@app/relays/components/relayForm';
|
||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
import { useArk } from '@libs/ark';
|
||||||
import { useStorage } from '@libs/storage/provider';
|
|
||||||
|
|
||||||
import { CancelIcon, RefreshIcon } from '@shared/icons';
|
import { CancelIcon, RefreshIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { useRelay } from '@utils/hooks/useRelay';
|
import { useRelay } from '@utils/hooks/useRelay';
|
||||||
|
|
||||||
export function UserRelayList() {
|
export function UserRelayList() {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
const { ndk } = useNDK();
|
|
||||||
const { removeRelay } = useRelay();
|
const { removeRelay } = useRelay();
|
||||||
const { status, data, refetch } = useQuery({
|
const { status, data, refetch } = useQuery({
|
||||||
queryKey: ['relays', db.account.pubkey],
|
queryKey: ['relays', ark.account.pubkey],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const event = await ndk.fetchEvent(
|
const event = await ark.getEventByFilter({
|
||||||
{
|
filter: {
|
||||||
kinds: [NDKKind.RelayList],
|
kinds: [NDKKind.RelayList],
|
||||||
authors: [db.account.pubkey],
|
authors: [ark.account.pubkey],
|
||||||
},
|
},
|
||||||
{ cacheUsage: NDKSubscriptionCacheUsage.ONLY_RELAY }
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (!event) return [];
|
if (!event) return [];
|
||||||
return event.tags;
|
return event.tags;
|
||||||
@@ -31,7 +28,7 @@ export function UserRelayList() {
|
|||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentRelays = new Set([...ndk.pool.relays.values()].map((item) => item.url));
|
const currentRelays = new Set([...ark.relays]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-span-1">
|
<div className="col-span-1">
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useStorage } from '@libs/storage/provider';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
export function AdvancedSettingScreen() {
|
export function AdvancedSettingScreen() {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
|
|
||||||
const clearCache = async () => {
|
const clearCache = async () => {
|
||||||
await db.clearCache();
|
await ark.clearCache();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import { nip19 } from 'nostr-tools';
|
import { nip19 } from 'nostr-tools';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { EyeOffIcon } from '@shared/icons';
|
import { EyeOffIcon } from '@shared/icons';
|
||||||
|
|
||||||
export function BackupSettingScreen() {
|
export function BackupSettingScreen() {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
|
|
||||||
const [privkey, setPrivkey] = useState(null);
|
const [privkey, setPrivkey] = useState(null);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
const removePrivkey = async () => {
|
const removePrivkey = async () => {
|
||||||
await db.secureRemove(db.account.pubkey);
|
await ark.removePrivkey(ark.account.pubkey);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function loadPrivkey() {
|
async function loadPrivkey() {
|
||||||
const key = await db.secureLoad(db.account.pubkey);
|
const key = await ark.loadPrivkey(ark.account.pubkey);
|
||||||
if (key) setPrivkey(key);
|
if (key) setPrivkey(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,19 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { fetch } from '@tauri-apps/plugin-http';
|
import { fetch } from '@tauri-apps/plugin-http';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { compactNumber } from '@utils/number';
|
import { compactNumber } from '@utils/number';
|
||||||
|
|
||||||
export function PostCard() {
|
export function PostCard() {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
const { status, data } = useQuery({
|
const { status, data } = useQuery({
|
||||||
queryKey: ['user-stats', db.account.pubkey],
|
queryKey: ['user-stats', ark.account.pubkey],
|
||||||
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`https://api.nostr.band/v0/stats/profile/${db.account.pubkey}`,
|
`https://api.nostr.band/v0/stats/profile/${ark.account.pubkey}`,
|
||||||
{
|
{
|
||||||
signal,
|
signal,
|
||||||
}
|
}
|
||||||
@@ -41,14 +41,14 @@ export function PostCard() {
|
|||||||
) : (
|
) : (
|
||||||
<div className="flex h-full w-full flex-col justify-between p-4">
|
<div className="flex h-full w-full flex-col justify-between p-4">
|
||||||
<h3 className="pt-1 text-5xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-100">
|
<h3 className="pt-1 text-5xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-100">
|
||||||
{compactNumber.format(data.stats[db.account.pubkey].pub_note_count)}
|
{compactNumber.format(data.stats[ark.account.pubkey].pub_note_count)}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="mt-auto flex h-6 w-full items-center justify-between">
|
<div className="mt-auto flex h-6 w-full items-center justify-between">
|
||||||
<p className="text-xl font-medium leading-none text-neutral-600 dark:text-neutral-400">
|
<p className="text-xl font-medium leading-none text-neutral-600 dark:text-neutral-400">
|
||||||
Posts
|
Posts
|
||||||
</p>
|
</p>
|
||||||
<Link
|
<Link
|
||||||
to={`/users/${db.account.pubkey}`}
|
to={`/users/${ark.account.pubkey}`}
|
||||||
className="inline-flex h-6 w-max items-center gap-1 rounded-full bg-neutral-200 px-2.5 text-sm font-medium hover:bg-neutral-300 dark:bg-neutral-800 dark:hover:bg-neutral-700"
|
className="inline-flex h-6 w-max items-center gap-1 rounded-full bg-neutral-200 px-2.5 text-sm font-medium hover:bg-neutral-300 dark:bg-neutral-800 dark:hover:bg-neutral-700"
|
||||||
>
|
>
|
||||||
View
|
View
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as Avatar from '@radix-ui/react-avatar';
|
|||||||
import { minidenticon } from 'minidenticons';
|
import { minidenticon } from 'minidenticons';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { EditIcon, LoaderIcon } from '@shared/icons';
|
import { EditIcon, LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
@@ -10,12 +10,12 @@ import { useProfile } from '@utils/hooks/useProfile';
|
|||||||
import { displayNpub } from '@utils/shortenKey';
|
import { displayNpub } from '@utils/shortenKey';
|
||||||
|
|
||||||
export function ProfileCard() {
|
export function ProfileCard() {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
const { isLoading, user } = useProfile(db.account.pubkey);
|
const { isLoading, user } = useProfile(ark.account.pubkey);
|
||||||
|
|
||||||
const svgURI =
|
const svgURI =
|
||||||
'data:image/svg+xml;utf8,' +
|
'data:image/svg+xml;utf8,' +
|
||||||
encodeURIComponent(minidenticon(db.account.pubkey, 90, 50));
|
encodeURIComponent(minidenticon(ark.account.pubkey, 90, 50));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-4 h-56 w-full rounded-2xl bg-neutral-100 transition-all duration-150 ease-smooth hover:scale-105 dark:bg-neutral-900">
|
<div className="mb-4 h-56 w-full rounded-2xl bg-neutral-100 transition-all duration-150 ease-smooth hover:scale-105 dark:bg-neutral-900">
|
||||||
@@ -38,7 +38,7 @@ export function ProfileCard() {
|
|||||||
<Avatar.Root className="shrink-0">
|
<Avatar.Root className="shrink-0">
|
||||||
<Avatar.Image
|
<Avatar.Image
|
||||||
src={user?.picture || user?.image}
|
src={user?.picture || user?.image}
|
||||||
alt={db.account.pubkey}
|
alt={ark.account.pubkey}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
style={{ contentVisibility: 'auto' }}
|
style={{ contentVisibility: 'auto' }}
|
||||||
@@ -47,7 +47,7 @@ export function ProfileCard() {
|
|||||||
<Avatar.Fallback delayMs={300}>
|
<Avatar.Fallback delayMs={300}>
|
||||||
<img
|
<img
|
||||||
src={svgURI}
|
src={svgURI}
|
||||||
alt={db.account.pubkey}
|
alt={ark.account.pubkey}
|
||||||
className="h-16 w-16 rounded-xl bg-black dark:bg-white"
|
className="h-16 w-16 rounded-xl bg-black dark:bg-white"
|
||||||
/>
|
/>
|
||||||
</Avatar.Fallback>
|
</Avatar.Fallback>
|
||||||
@@ -57,7 +57,7 @@ export function ProfileCard() {
|
|||||||
{user?.display_name || user?.name}
|
{user?.display_name || user?.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-lg text-neutral-700 dark:text-neutral-300">
|
<p className="text-lg text-neutral-700 dark:text-neutral-300">
|
||||||
{user?.nip05 || displayNpub(db.account.pubkey, 16)}
|
{user?.nip05 || displayNpub(ark.account.pubkey, 16)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { fetch } from '@tauri-apps/plugin-http';
|
import { fetch } from '@tauri-apps/plugin-http';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
import { compactNumber } from '@utils/number';
|
import { compactNumber } from '@utils/number';
|
||||||
|
|
||||||
export function ZapCard() {
|
export function ZapCard() {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
const { status, data } = useQuery({
|
const { status, data } = useQuery({
|
||||||
queryKey: ['user-stats', db.account.pubkey],
|
queryKey: ['user-stats', ark.account.pubkey],
|
||||||
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
queryFn: async ({ signal }: { signal: AbortSignal }) => {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`https://api.nostr.band/v0/stats/profile/${db.account.pubkey}`,
|
`https://api.nostr.band/v0/stats/profile/${ark.account.pubkey}`,
|
||||||
{
|
{
|
||||||
signal,
|
signal,
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ export function ZapCard() {
|
|||||||
<div className="flex h-full w-full flex-col justify-between p-4">
|
<div className="flex h-full w-full flex-col justify-between p-4">
|
||||||
<h3 className="pt-1 text-5xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-100">
|
<h3 className="pt-1 text-5xl font-semibold tabular-nums text-neutral-900 dark:text-neutral-100">
|
||||||
{compactNumber.format(
|
{compactNumber.format(
|
||||||
data?.stats[db.account.pubkey]?.zaps_received?.msats / 1000 || 0
|
data?.stats[ark.account.pubkey]?.zaps_received?.msats / 1000 || 0
|
||||||
)}
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="mt-auto flex h-6 items-center text-xl font-medium leading-none text-neutral-600 dark:text-neutral-400">
|
<div className="mt-auto flex h-6 items-center text-xl font-medium leading-none text-neutral-600 dark:text-neutral-400">
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import { isPermissionGranted, requestPermission } from '@tauri-apps/plugin-notif
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { DarkIcon, LightIcon, SystemModeIcon } from '@shared/icons';
|
import { DarkIcon, LightIcon, SystemModeIcon } from '@shared/icons';
|
||||||
|
|
||||||
export function GeneralSettingScreen() {
|
export function GeneralSettingScreen() {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
const [settings, setSettings] = useState({
|
const [settings, setSettings] = useState({
|
||||||
autoupdate: false,
|
autoupdate: false,
|
||||||
autolaunch: false,
|
autolaunch: false,
|
||||||
@@ -41,28 +41,28 @@ export function GeneralSettingScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const toggleOutbox = async () => {
|
const toggleOutbox = async () => {
|
||||||
await db.createSetting('outbox', String(+!settings.outbox));
|
await ark.createSetting('outbox', String(+!settings.outbox));
|
||||||
// update state
|
// update state
|
||||||
setSettings((prev) => ({ ...prev, outbox: !settings.outbox }));
|
setSettings((prev) => ({ ...prev, outbox: !settings.outbox }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleMedia = async () => {
|
const toggleMedia = async () => {
|
||||||
await db.createSetting('media', String(+!settings.media));
|
await ark.createSetting('media', String(+!settings.media));
|
||||||
db.settings.media = !settings.media;
|
ark.settings.media = !settings.media;
|
||||||
// update state
|
// update state
|
||||||
setSettings((prev) => ({ ...prev, media: !settings.media }));
|
setSettings((prev) => ({ ...prev, media: !settings.media }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleHashtag = async () => {
|
const toggleHashtag = async () => {
|
||||||
await db.createSetting('hashtag', String(+!settings.hashtag));
|
await ark.createSetting('hashtag', String(+!settings.hashtag));
|
||||||
db.settings.hashtag = !settings.hashtag;
|
ark.settings.hashtag = !settings.hashtag;
|
||||||
// update state
|
// update state
|
||||||
setSettings((prev) => ({ ...prev, hashtag: !settings.hashtag }));
|
setSettings((prev) => ({ ...prev, hashtag: !settings.hashtag }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleAutoupdate = async () => {
|
const toggleAutoupdate = async () => {
|
||||||
await db.createSetting('autoupdate', String(+!settings.autoupdate));
|
await ark.createSetting('autoupdate', String(+!settings.autoupdate));
|
||||||
db.settings.autoupdate = !settings.autoupdate;
|
ark.settings.autoupdate = !settings.autoupdate;
|
||||||
// update state
|
// update state
|
||||||
setSettings((prev) => ({ ...prev, autoupdate: !settings.autoupdate }));
|
setSettings((prev) => ({ ...prev, autoupdate: !settings.autoupdate }));
|
||||||
};
|
};
|
||||||
@@ -86,7 +86,7 @@ export function GeneralSettingScreen() {
|
|||||||
const permissionGranted = await isPermissionGranted();
|
const permissionGranted = await isPermissionGranted();
|
||||||
setSettings((prev) => ({ ...prev, notification: permissionGranted }));
|
setSettings((prev) => ({ ...prev, notification: permissionGranted }));
|
||||||
|
|
||||||
const data = await db.getAllSettings();
|
const data = await ark.getAllSettings();
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
|
||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { NDKEvent, NDKKind, NDKUser } from '@nostr-dev-kit/ndk';
|
|
||||||
import * as Avatar from '@radix-ui/react-avatar';
|
import * as Avatar from '@radix-ui/react-avatar';
|
||||||
import { minidenticon } from 'minidenticons';
|
import { minidenticon } from 'minidenticons';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -7,8 +6,7 @@ import { toast } from 'sonner';
|
|||||||
|
|
||||||
import { UserStats } from '@app/users/components/stats';
|
import { UserStats } from '@app/users/components/stats';
|
||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
import { useArk } from '@libs/ark';
|
||||||
import { useStorage } from '@libs/storage/provider';
|
|
||||||
|
|
||||||
import { NIP05 } from '@shared/nip05';
|
import { NIP05 } from '@shared/nip05';
|
||||||
|
|
||||||
@@ -16,8 +14,7 @@ import { useProfile } from '@utils/hooks/useProfile';
|
|||||||
import { displayNpub } from '@utils/shortenKey';
|
import { displayNpub } from '@utils/shortenKey';
|
||||||
|
|
||||||
export function UserProfile({ pubkey }: { pubkey: string }) {
|
export function UserProfile({ pubkey }: { pubkey: string }) {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
const { ndk } = useNDK();
|
|
||||||
const { user } = useProfile(pubkey);
|
const { user } = useProfile(pubkey);
|
||||||
|
|
||||||
const [followed, setFollowed] = useState(false);
|
const [followed, setFollowed] = useState(false);
|
||||||
@@ -28,12 +25,10 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
|
|||||||
|
|
||||||
const follow = async () => {
|
const follow = async () => {
|
||||||
try {
|
try {
|
||||||
if (!ndk.signer) return navigate('/new/privkey');
|
if (!ark.readyToSign) return navigate('/new/privkey');
|
||||||
setFollowed(true);
|
setFollowed(true);
|
||||||
|
|
||||||
const user = ndk.getUser({ pubkey: db.account.pubkey });
|
const add = await ark.createContact({ pubkey });
|
||||||
const contacts = await user.follows();
|
|
||||||
const add = await user.follow(new NDKUser({ pubkey: pubkey }), contacts);
|
|
||||||
|
|
||||||
if (!add) {
|
if (!add) {
|
||||||
toast.success('You already follow this user');
|
toast.success('You already follow this user');
|
||||||
@@ -47,32 +42,17 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
|
|||||||
|
|
||||||
const unfollow = async () => {
|
const unfollow = async () => {
|
||||||
try {
|
try {
|
||||||
if (!ndk.signer) return navigate('/new/privkey');
|
if (!ark.readyToSign) return navigate('/new/privkey');
|
||||||
setFollowed(false);
|
setFollowed(false);
|
||||||
|
|
||||||
const user = ndk.getUser({ pubkey: db.account.pubkey });
|
await ark.deleteContact({ pubkey });
|
||||||
const contacts = await user.follows();
|
|
||||||
contacts.delete(new NDKUser({ pubkey: pubkey }));
|
|
||||||
|
|
||||||
const list = [...contacts].map((item) => [
|
|
||||||
'p',
|
|
||||||
item.pubkey,
|
|
||||||
item.relayUrls?.[0] || '',
|
|
||||||
'',
|
|
||||||
]);
|
|
||||||
const event = new NDKEvent(ndk);
|
|
||||||
event.content = '';
|
|
||||||
event.kind = NDKKind.Contacts;
|
|
||||||
event.tags = list;
|
|
||||||
|
|
||||||
await event.publish();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(e);
|
toast.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (db.account.contacts.includes(pubkey)) {
|
if (ark.account.contacts.includes(pubkey)) {
|
||||||
setFollowed(true);
|
setFollowed(true);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -241,10 +241,26 @@ export class Ark {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Save private key to OS secure storage
|
* Save private key to OS secure storage
|
||||||
* @deprecated this method will be marked as private in the next update
|
* @deprecated this method will be remove in the next update
|
||||||
*/
|
*/
|
||||||
public async createPrivkey(name: string, privkey: string) {
|
public async createPrivkey(name: string, privkey: string) {
|
||||||
await this.#keyring_save(name, privkey);
|
return await this.#keyring_save(name, privkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load private key from OS secure storage
|
||||||
|
* @deprecated this method will be remove in the next update
|
||||||
|
*/
|
||||||
|
public async loadPrivkey(name: string) {
|
||||||
|
return await this.#keyring_load(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove private key from OS secure storage
|
||||||
|
* @deprecated this method will be remove in the next update
|
||||||
|
*/
|
||||||
|
public async removePrivkey(name: string) {
|
||||||
|
return await this.#keyring_remove(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateAccount(column: string, value: string) {
|
public async updateAccount(column: string, value: string) {
|
||||||
@@ -458,7 +474,19 @@ export class Ark {
|
|||||||
public async deleteContact({ pubkey }: { pubkey: string }) {
|
public async deleteContact({ pubkey }: { pubkey: string }) {
|
||||||
const user = this.#ndk.getUser({ pubkey: this.account.pubkey });
|
const user = this.#ndk.getUser({ pubkey: this.account.pubkey });
|
||||||
const contacts = await user.follows();
|
const contacts = await user.follows();
|
||||||
return await user.follow(new NDKUser({ pubkey: pubkey }), contacts);
|
contacts.delete(new NDKUser({ pubkey: pubkey }));
|
||||||
|
|
||||||
|
const event = new NDKEvent(this.#ndk);
|
||||||
|
event.content = '';
|
||||||
|
event.kind = NDKKind.Contacts;
|
||||||
|
event.tags = [...contacts].map((item) => [
|
||||||
|
'p',
|
||||||
|
item.pubkey,
|
||||||
|
item.relayUrls?.[0] || '',
|
||||||
|
'',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return await event.publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllEvents({ filter }: { filter: NDKFilter }) {
|
public async getAllEvents({ filter }: { filter: NDKFilter }) {
|
||||||
@@ -476,6 +504,15 @@ export class Ark {
|
|||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getEventByFilter({ filter }: { filter: NDKFilter }) {
|
||||||
|
const event = await this.#ndk.fetchEvent(filter, {
|
||||||
|
cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!event) return null;
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
public getEventThread({ tags }: { tags: NDKTag[] }) {
|
public getEventThread({ tags }: { tags: NDKTag[] }) {
|
||||||
let rootEventId: string = null;
|
let rootEventId: string = null;
|
||||||
let replyEventId: string = null;
|
let replyEventId: string = null;
|
||||||
@@ -551,6 +588,32 @@ export class Ark {
|
|||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getAllRelaysFromContacts() {
|
||||||
|
const LIMIT = 1;
|
||||||
|
const relayMap = new Map<string, string[]>();
|
||||||
|
const relayEvents = this.#fetcher.fetchLatestEventsPerAuthor(
|
||||||
|
{
|
||||||
|
authors: this.account.contacts,
|
||||||
|
relayUrls: this.relays,
|
||||||
|
},
|
||||||
|
{ kinds: [NDKKind.RelayList] },
|
||||||
|
LIMIT
|
||||||
|
);
|
||||||
|
|
||||||
|
for await (const { author, events } of relayEvents) {
|
||||||
|
if (events[0]) {
|
||||||
|
events[0].tags.forEach((tag) => {
|
||||||
|
const users = relayMap.get(tag[1]);
|
||||||
|
|
||||||
|
if (!users) return relayMap.set(tag[1], [author]);
|
||||||
|
return users.push(author);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return relayMap;
|
||||||
|
}
|
||||||
|
|
||||||
public async getInfiniteEvents({
|
public async getInfiniteEvents({
|
||||||
filter,
|
filter,
|
||||||
limit,
|
limit,
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { message } from '@tauri-apps/plugin-dialog';
|
import { message } from '@tauri-apps/plugin-dialog';
|
||||||
import { Dispatch, SetStateAction, useState } from 'react';
|
import { Dispatch, SetStateAction, useState } from 'react';
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
|
|
||||||
export function AvatarUploader({
|
export function AvatarUploader({
|
||||||
setPicture,
|
setPicture,
|
||||||
}: {
|
}: {
|
||||||
setPicture: Dispatch<SetStateAction<string>>;
|
setPicture: Dispatch<SetStateAction<string>>;
|
||||||
}) {
|
}) {
|
||||||
const { upload } = useNostr();
|
const { ark } = useArk();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const uploadAvatar = async () => {
|
const uploadAvatar = async () => {
|
||||||
@@ -18,7 +18,7 @@ export function AvatarUploader({
|
|||||||
// start loading
|
// start loading
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const image = await upload();
|
const image = await ark.upload({});
|
||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
setPicture(image);
|
setPicture(image);
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { message } from '@tauri-apps/plugin-dialog';
|
import { message } from '@tauri-apps/plugin-dialog';
|
||||||
import { Dispatch, SetStateAction, useState } from 'react';
|
import { Dispatch, SetStateAction, useState } from 'react';
|
||||||
|
|
||||||
import { LoaderIcon, PlusIcon } from '@shared/icons';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
import { LoaderIcon, PlusIcon } from '@shared/icons';
|
||||||
|
|
||||||
export function BannerUploader({
|
export function BannerUploader({
|
||||||
setBanner,
|
setBanner,
|
||||||
}: {
|
}: {
|
||||||
setBanner: Dispatch<SetStateAction<string>>;
|
setBanner: Dispatch<SetStateAction<string>>;
|
||||||
}) {
|
}) {
|
||||||
const { upload } = useNostr();
|
const { ark } = useArk();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const uploadBanner = async () => {
|
const uploadBanner = async () => {
|
||||||
@@ -18,7 +18,7 @@ export function BannerUploader({
|
|||||||
// start loading
|
// start loading
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const image = await upload();
|
const image = await ark.upload({});
|
||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
setBanner(image);
|
setBanner(image);
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { ReplyIcon, RepostIcon } from '@shared/icons';
|
import { ReplyIcon, RepostIcon } from '@shared/icons';
|
||||||
import { ChildNote, TextKind } from '@shared/notes';
|
import { ChildNote, TextKind } from '@shared/notes';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
import { WIDGET_KIND } from '@utils/constants';
|
import { WIDGET_KIND } from '@utils/constants';
|
||||||
import { formatCreatedAt } from '@utils/createdAt';
|
import { formatCreatedAt } from '@utils/createdAt';
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
|
||||||
import { useWidget } from '@utils/hooks/useWidget';
|
import { useWidget } from '@utils/hooks/useWidget';
|
||||||
|
|
||||||
export function NotifyNote({ event }: { event: NDKEvent }) {
|
export function NotifyNote({ event }: { event: NDKEvent }) {
|
||||||
const { getEventThread } = useNostr();
|
const { ark } = useArk();
|
||||||
const { addWidget } = useWidget();
|
const { addWidget } = useWidget();
|
||||||
|
|
||||||
const thread = getEventThread(event.tags);
|
const thread = ark.getEventThread({ tags: event.tags });
|
||||||
const createdAt = formatCreatedAt(event.created_at, false);
|
const createdAt = formatCreatedAt(event.created_at, false);
|
||||||
|
|
||||||
if (event.kind === NDKKind.Reaction) {
|
if (event.kind === NDKKind.Reaction) {
|
||||||
|
|||||||
@@ -1,38 +1,42 @@
|
|||||||
|
import { NDKSubscription } from '@nostr-dev-kit/ndk';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
import { Reply } from '@shared/notes';
|
import { Reply } from '@shared/notes';
|
||||||
|
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
|
||||||
import { NDKEventWithReplies } from '@utils/types';
|
import { NDKEventWithReplies } from '@utils/types';
|
||||||
|
|
||||||
export function ReplyList({ eventId }: { eventId: string }) {
|
export function ReplyList({ eventId }: { eventId: string }) {
|
||||||
const { fetchAllReplies, sub } = useNostr();
|
const { ark } = useArk();
|
||||||
const [data, setData] = useState<null | NDKEventWithReplies[]>(null);
|
const [data, setData] = useState<null | NDKEventWithReplies[]>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let sub: NDKSubscription;
|
||||||
let isCancelled = false;
|
let isCancelled = false;
|
||||||
|
|
||||||
async function fetchRepliesAndSub() {
|
async function fetchRepliesAndSub() {
|
||||||
const events = await fetchAllReplies(eventId);
|
const events = await ark.getThreads({ id: eventId });
|
||||||
if (!isCancelled) {
|
if (!isCancelled) {
|
||||||
setData(events);
|
setData(events);
|
||||||
}
|
}
|
||||||
// subscribe for new replies
|
// subscribe for new replies
|
||||||
sub(
|
sub = ark.subscribe({
|
||||||
{
|
filter: {
|
||||||
'#e': [eventId],
|
'#e': [eventId],
|
||||||
since: Math.floor(Date.now() / 1000),
|
since: Math.floor(Date.now() / 1000),
|
||||||
},
|
},
|
||||||
(event: NDKEventWithReplies) => setData((prev) => [event, ...prev]),
|
closeOnEose: false,
|
||||||
false
|
cb: (event: NDKEventWithReplies) => setData((prev) => [event, ...prev]),
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchRepliesAndSub();
|
fetchRepliesAndSub();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
isCancelled = true;
|
isCancelled = true;
|
||||||
|
if (sub) sub.stop();
|
||||||
};
|
};
|
||||||
}, [eventId]);
|
}, [eventId]);
|
||||||
|
|
||||||
@@ -59,7 +63,7 @@ export function ReplyList({ eventId }: { eventId: string }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
data.map((event) => <Reply key={event.id} event={event} root={eventId} />)
|
data.map((event) => <Reply key={event.id} event={event} />)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,20 +2,21 @@ import { NDKEvent } from '@nostr-dev-kit/ndk';
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { ChildNote, NoteActions } from '@shared/notes';
|
import { ChildNote, NoteActions } from '@shared/notes';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
|
|
||||||
import { WIDGET_KIND } from '@utils/constants';
|
import { WIDGET_KIND } from '@utils/constants';
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
|
||||||
import { useRichContent } from '@utils/hooks/useRichContent';
|
import { useRichContent } from '@utils/hooks/useRichContent';
|
||||||
import { useWidget } from '@utils/hooks/useWidget';
|
import { useWidget } from '@utils/hooks/useWidget';
|
||||||
|
|
||||||
export function TextNote({ event, className }: { event: NDKEvent; className?: string }) {
|
export function TextNote({ event, className }: { event: NDKEvent; className?: string }) {
|
||||||
const { parsedContent } = useRichContent(event.content);
|
const { parsedContent } = useRichContent(event.content);
|
||||||
const { addWidget } = useWidget();
|
const { addWidget } = useWidget();
|
||||||
const { getEventThread } = useNostr();
|
const { ark } = useArk();
|
||||||
|
|
||||||
const thread = getEventThread(event.tags);
|
const thread = ark.getEventThread({ tags: event.tags });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={twMerge('mb-3 h-min w-full px-3', className)}>
|
<div className={twMerge('mb-3 h-min w-full px-3', className)}>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useStorage } from '@libs/storage/provider';
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { CancelIcon } from '@shared/icons';
|
import { CancelIcon } from '@shared/icons';
|
||||||
import { User } from '@shared/user';
|
import { User } from '@shared/user';
|
||||||
@@ -14,7 +14,7 @@ export function TitleBar({
|
|||||||
title?: string;
|
title?: string;
|
||||||
isLive?: boolean;
|
isLive?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
const { removeWidget } = useWidget();
|
const { removeWidget } = useWidget();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -33,13 +33,13 @@ export function TitleBar({
|
|||||||
<div className="col-span-1 flex justify-center">
|
<div className="col-span-1 flex justify-center">
|
||||||
{id === '9999' ? (
|
{id === '9999' ? (
|
||||||
<div className="isolate flex -space-x-2">
|
<div className="isolate flex -space-x-2">
|
||||||
{db.account.contacts
|
{ark.account.contacts
|
||||||
?.slice(0, 8)
|
?.slice(0, 8)
|
||||||
.map((item) => <User key={item} pubkey={item} variant="ministacked" />)}
|
.map((item) => <User key={item} pubkey={item} variant="ministacked" />)}
|
||||||
{db.account.contacts?.length > 8 ? (
|
{ark.account.contacts?.length > 8 ? (
|
||||||
<div className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-neutral-300 text-neutral-900 ring-1 ring-white dark:bg-neutral-700 dark:text-neutral-100 dark:ring-black">
|
<div className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-neutral-300 text-neutral-900 ring-1 ring-white dark:bg-neutral-700 dark:text-neutral-100 dark:ring-black">
|
||||||
<span className="text-[8px] font-medium">
|
<span className="text-[8px] font-medium">
|
||||||
+{db.account.contacts?.length - 8}
|
+{ark.account.contacts?.length - 8}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useInfiniteQuery, useQueryClient } from '@tanstack/react-query';
|
|||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import { VList } from 'virtua';
|
import { VList } from 'virtua';
|
||||||
|
|
||||||
|
import { useArk } from '@libs/ark';
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
import { useNDK } from '@libs/ndk/provider';
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
@@ -12,15 +13,12 @@ import { TitleBar } from '@shared/titleBar';
|
|||||||
import { WidgetWrapper } from '@shared/widgets';
|
import { WidgetWrapper } from '@shared/widgets';
|
||||||
|
|
||||||
import { FETCH_LIMIT } from '@utils/constants';
|
import { FETCH_LIMIT } from '@utils/constants';
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
|
||||||
import { sendNativeNotification } from '@utils/notification';
|
import { sendNativeNotification } from '@utils/notification';
|
||||||
|
|
||||||
export function NotificationWidget() {
|
export function NotificationWidget() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { db } = useStorage();
|
const { ark } = useArk();
|
||||||
const { sub } = useNostr();
|
|
||||||
const { ndk, relayUrls, fetcher } = useNDK();
|
|
||||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||||
useInfiniteQuery({
|
useInfiniteQuery({
|
||||||
queryKey: ['notification'],
|
queryKey: ['notification'],
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { WVList } from 'virtua';
|
import { WVList } from 'virtua';
|
||||||
|
|
||||||
|
import { useArk } from '@libs/ark';
|
||||||
|
|
||||||
import { LoaderIcon } from '@shared/icons';
|
import { LoaderIcon } from '@shared/icons';
|
||||||
import {
|
import {
|
||||||
ChildNote,
|
ChildNote,
|
||||||
@@ -17,16 +19,15 @@ import { User } from '@shared/user';
|
|||||||
import { WidgetWrapper } from '@shared/widgets';
|
import { WidgetWrapper } from '@shared/widgets';
|
||||||
|
|
||||||
import { useEvent } from '@utils/hooks/useEvent';
|
import { useEvent } from '@utils/hooks/useEvent';
|
||||||
import { useNostr } from '@utils/hooks/useNostr';
|
|
||||||
import { Widget } from '@utils/types';
|
import { Widget } from '@utils/types';
|
||||||
|
|
||||||
export function ThreadWidget({ widget }: { widget: Widget }) {
|
export function ThreadWidget({ widget }: { widget: Widget }) {
|
||||||
const { isFetching, isError, data } = useEvent(widget.content);
|
const { isFetching, isError, data } = useEvent(widget.content);
|
||||||
const { getEventThread } = useNostr();
|
const { ark } = useArk();
|
||||||
|
|
||||||
const renderKind = useCallback(
|
const renderKind = useCallback(
|
||||||
(event: NDKEvent) => {
|
(event: NDKEvent) => {
|
||||||
const thread = getEventThread(event.tags);
|
const thread = ark.getEventThread({ tags: event.tags });
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case NDKKind.Text:
|
case NDKKind.Text:
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,299 +0,0 @@
|
|||||||
import {
|
|
||||||
NDKEvent,
|
|
||||||
NDKFilter,
|
|
||||||
NDKKind,
|
|
||||||
NDKSubscription,
|
|
||||||
NDKTag,
|
|
||||||
} from '@nostr-dev-kit/ndk';
|
|
||||||
import { open } from '@tauri-apps/plugin-dialog';
|
|
||||||
import { readBinaryFile } from '@tauri-apps/plugin-fs';
|
|
||||||
import { fetch } from '@tauri-apps/plugin-http';
|
|
||||||
import { LRUCache } from 'lru-cache';
|
|
||||||
import { NostrEventExt } from 'nostr-fetch';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
|
||||||
import { useStorage } from '@libs/storage/provider';
|
|
||||||
|
|
||||||
import { nHoursAgo } from '@utils/date';
|
|
||||||
import { getMultipleRandom } from '@utils/transform';
|
|
||||||
import { NDKEventWithReplies } from '@utils/types';
|
|
||||||
|
|
||||||
export function useNostr() {
|
|
||||||
const { db } = useStorage();
|
|
||||||
const { ndk, relayUrls, fetcher } = useNDK();
|
|
||||||
|
|
||||||
const subManager = useMemo(
|
|
||||||
() =>
|
|
||||||
new LRUCache<string, NDKSubscription, void>({
|
|
||||||
max: 4,
|
|
||||||
dispose: (sub) => sub.stop(),
|
|
||||||
}),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const sub = async (
|
|
||||||
filter: NDKFilter,
|
|
||||||
callback: (event: NDKEvent) => void,
|
|
||||||
groupable?: boolean,
|
|
||||||
subKey?: string
|
|
||||||
) => {
|
|
||||||
if (!ndk) throw new Error('NDK instance not found');
|
|
||||||
|
|
||||||
const key = subKey ?? JSON.stringify(filter);
|
|
||||||
if (!subManager.get(key)) {
|
|
||||||
const subEvent = ndk.subscribe(filter, {
|
|
||||||
closeOnEose: false,
|
|
||||||
groupable: groupable ?? true,
|
|
||||||
});
|
|
||||||
|
|
||||||
subEvent.addListener('event', (event: NDKEvent) => {
|
|
||||||
callback(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
subManager.set(JSON.stringify(filter), subEvent);
|
|
||||||
console.log('sub: ', key);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getEventThread = (tags: NDKTag[]) => {
|
|
||||||
let rootEventId: string = null;
|
|
||||||
let replyEventId: string = null;
|
|
||||||
|
|
||||||
const events = tags.filter((el) => el[0] === 'e');
|
|
||||||
|
|
||||||
if (!events.length) return null;
|
|
||||||
|
|
||||||
if (events.length === 1)
|
|
||||||
return {
|
|
||||||
rootEventId: events[0][1],
|
|
||||||
replyEventId: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (events.length > 1) {
|
|
||||||
rootEventId = events.find((el) => el[3] === 'root')?.[1];
|
|
||||||
replyEventId = events.find((el) => el[3] === 'reply')?.[1];
|
|
||||||
|
|
||||||
if (!rootEventId && !replyEventId) {
|
|
||||||
rootEventId = events[0][1];
|
|
||||||
replyEventId = events[1][1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
rootEventId,
|
|
||||||
replyEventId,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAllActivities = async (limit?: number) => {
|
|
||||||
try {
|
|
||||||
const events = await ndk.fetchEvents({
|
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
|
|
||||||
'#p': [db.account.pubkey],
|
|
||||||
limit: limit ?? 50,
|
|
||||||
});
|
|
||||||
|
|
||||||
return [...events];
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error fetching activities', e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchNIP04Messages = async (sender: string) => {
|
|
||||||
let senderMessages: NostrEventExt<false>[] = [];
|
|
||||||
|
|
||||||
if (sender !== db.account.pubkey) {
|
|
||||||
senderMessages = await fetcher.fetchAllEvents(
|
|
||||||
relayUrls,
|
|
||||||
{
|
|
||||||
kinds: [NDKKind.EncryptedDirectMessage],
|
|
||||||
authors: [sender],
|
|
||||||
'#p': [db.account.pubkey],
|
|
||||||
},
|
|
||||||
{ since: 0 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const userMessages = await fetcher.fetchAllEvents(
|
|
||||||
relayUrls,
|
|
||||||
{
|
|
||||||
kinds: [NDKKind.EncryptedDirectMessage],
|
|
||||||
authors: [db.account.pubkey],
|
|
||||||
'#p': [sender],
|
|
||||||
},
|
|
||||||
{ since: 0 }
|
|
||||||
);
|
|
||||||
|
|
||||||
const all = [...senderMessages, ...userMessages].sort(
|
|
||||||
(a, b) => a.created_at - b.created_at
|
|
||||||
);
|
|
||||||
|
|
||||||
return all as unknown as NDKEvent[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchAllReplies = async (id: string, data?: NDKEventWithReplies[]) => {
|
|
||||||
let events = data || null;
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
events = (await fetcher.fetchAllEvents(
|
|
||||||
relayUrls,
|
|
||||||
{
|
|
||||||
kinds: [NDKKind.Text],
|
|
||||||
'#e': [id],
|
|
||||||
},
|
|
||||||
{ since: 0 },
|
|
||||||
{ sort: true }
|
|
||||||
)) as unknown as NDKEventWithReplies[];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (events.length > 0) {
|
|
||||||
const replies = new Set();
|
|
||||||
events.forEach((event) => {
|
|
||||||
const tags = event.tags.filter((el) => el[0] === 'e' && el[1] !== id);
|
|
||||||
if (tags.length > 0) {
|
|
||||||
tags.forEach((tag) => {
|
|
||||||
const rootIndex = events.findIndex((el) => el.id === tag[1]);
|
|
||||||
if (rootIndex !== -1) {
|
|
||||||
const rootEvent = events[rootIndex];
|
|
||||||
if (rootEvent && rootEvent.replies) {
|
|
||||||
rootEvent.replies.push(event);
|
|
||||||
} else {
|
|
||||||
rootEvent.replies = [event];
|
|
||||||
}
|
|
||||||
replies.add(event.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const cleanEvents = events.filter((ev) => !replies.has(ev.id));
|
|
||||||
return cleanEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
return events;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAllNIP04Chats = async () => {
|
|
||||||
const events = await fetcher.fetchAllEvents(
|
|
||||||
relayUrls,
|
|
||||||
{
|
|
||||||
kinds: [NDKKind.EncryptedDirectMessage],
|
|
||||||
'#p': [db.account.pubkey],
|
|
||||||
},
|
|
||||||
{ since: 0 }
|
|
||||||
);
|
|
||||||
|
|
||||||
const dedup: NDKEvent[] = Object.values(
|
|
||||||
events.reduce((ev, { id, content, pubkey, created_at, tags }) => {
|
|
||||||
if (ev[pubkey]) {
|
|
||||||
if (ev[pubkey].created_at < created_at) {
|
|
||||||
ev[pubkey] = { id, content, pubkey, created_at, tags };
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ev[pubkey] = { id, content, pubkey, created_at, tags };
|
|
||||||
}
|
|
||||||
return ev;
|
|
||||||
}, {})
|
|
||||||
);
|
|
||||||
|
|
||||||
return dedup;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getContactsByPubkey = async (pubkey: string) => {
|
|
||||||
const user = ndk.getUser({ pubkey: pubkey });
|
|
||||||
const follows = [...(await user.follows())].map((user) => user.hexpubkey);
|
|
||||||
return getMultipleRandom([...follows], 10);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getEventsByPubkey = async (pubkey: string) => {
|
|
||||||
const events = await fetcher.fetchAllEvents(
|
|
||||||
relayUrls,
|
|
||||||
{ authors: [pubkey], kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Article] },
|
|
||||||
{ since: nHoursAgo(24) },
|
|
||||||
{ sort: true }
|
|
||||||
);
|
|
||||||
return events as unknown as NDKEvent[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAllRelaysByUsers = async () => {
|
|
||||||
const relayMap = new Map<string, string[]>();
|
|
||||||
const relayEvents = fetcher.fetchLatestEventsPerAuthor(
|
|
||||||
{
|
|
||||||
authors: db.account.contacts,
|
|
||||||
relayUrls: relayUrls,
|
|
||||||
},
|
|
||||||
{ kinds: [NDKKind.RelayList] },
|
|
||||||
5
|
|
||||||
);
|
|
||||||
|
|
||||||
for await (const { author, events } of relayEvents) {
|
|
||||||
if (events[0]) {
|
|
||||||
events[0].tags.forEach((tag) => {
|
|
||||||
const users = relayMap.get(tag[1]);
|
|
||||||
|
|
||||||
if (!users) return relayMap.set(tag[1], [author]);
|
|
||||||
return users.push(author);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return relayMap;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createZap = async (event: NDKEvent, amount: number, message?: string) => {
|
|
||||||
// @ts-expect-error, NostrEvent to NDKEvent
|
|
||||||
const ndkEvent = new NDKEvent(ndk, event);
|
|
||||||
const res = await ndkEvent.zap(amount, message ?? 'zap from lume');
|
|
||||||
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
const upload = async (ext: string[] = []) => {
|
|
||||||
const defaultExts = ['png', 'jpeg', 'jpg', 'gif'].concat(ext);
|
|
||||||
|
|
||||||
const selected = await open({
|
|
||||||
multiple: false,
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
name: 'Image',
|
|
||||||
extensions: defaultExts,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!selected) return null;
|
|
||||||
|
|
||||||
const file = await readBinaryFile(selected.path);
|
|
||||||
const blob = new Blob([file]);
|
|
||||||
|
|
||||||
const data = new FormData();
|
|
||||||
data.append('fileToUpload', blob);
|
|
||||||
data.append('submit', 'Upload Image');
|
|
||||||
|
|
||||||
const res = await fetch('https://nostr.build/api/v2/upload/files', {
|
|
||||||
method: 'POST',
|
|
||||||
body: data,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) return null;
|
|
||||||
|
|
||||||
const json = await res.json();
|
|
||||||
const content = json.data[0];
|
|
||||||
|
|
||||||
return content.url as string;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
sub,
|
|
||||||
getEventThread,
|
|
||||||
getAllNIP04Chats,
|
|
||||||
getContactsByPubkey,
|
|
||||||
getEventsByPubkey,
|
|
||||||
getAllRelaysByUsers,
|
|
||||||
getAllActivities,
|
|
||||||
fetchNIP04Messages,
|
|
||||||
fetchAllReplies,
|
|
||||||
createZap,
|
|
||||||
upload,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user