diff --git a/src/components/FileEventEditor/FileEventEditor.tsx b/src/components/FileEventEditor/FileEventEditor.tsx index a2d9d53..4fce8b1 100644 --- a/src/components/FileEventEditor/FileEventEditor.tsx +++ b/src/components/FileEventEditor/FileEventEditor.tsx @@ -181,12 +181,7 @@ const FileEventEditor = ({
{fileEventData.thumbnails.map((t, i) => (
- +
))}
diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index 5543944..6b5d933 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -76,7 +76,8 @@ export const Layout = () => { {user && (
- { setAutoLogin(false); logout(); diff --git a/src/components/Layout/Login.tsx b/src/components/Layout/Login.tsx index efe1b59..3e7733c 100644 --- a/src/components/Layout/Login.tsx +++ b/src/components/Layout/Login.tsx @@ -1,11 +1,12 @@ - import React from 'react'; import { useNDK } from '../../utils/ndk'; import useLocalStorageState from '../../utils/useLocalStorageState'; +import { useUserServers } from '../../utils/useUserServers'; const Login: React.FC = () => { const { loginWithExtension } = useNDK(); - const [_, setAutoLogin] = useLocalStorageState('autologin', {defaultValue: false}); + const [_, setAutoLogin] = useLocalStorageState('autologin', { defaultValue: false }); + const userServers = useUserServers(); const handleLogin = async () => { try { @@ -17,11 +18,13 @@ const Login: React.FC = () => { } }; + console.log(userServers); + return (
- logo -

bouquet

-

organize assets your way

+ logo +

bouquet

+

organize assets your way

diff --git a/src/components/ServerList/ServerList.tsx b/src/components/ServerList/ServerList.tsx index 64497ac..6e07149 100644 --- a/src/components/ServerList/ServerList.tsx +++ b/src/components/ServerList/ServerList.tsx @@ -1,14 +1,10 @@ import { ArrowPathRoundedSquareIcon, Cog8ToothIcon } from '@heroicons/react/24/outline'; import { ServerInfo, useServerInfo } from '../../utils/useServerInfo'; -import { Server as ServerType } from '../../utils/useUserServers'; +import { Server as ServerType, useUserServers } from '../../utils/useUserServers'; import Server from './Server'; import './ServerList.css'; import ServerListPopup from '../ServerListPopup'; import { useMemo, useState } from 'react'; -import { useNDK } from '../../utils/ndk'; -import { NDKEvent } from '@nostr-dev-kit/ndk'; -import dayjs from 'dayjs'; -import { USER_BLOSSOM_SERVER_LIST_KIND } from 'blossom-client-sdk'; import { useQueryClient } from '@tanstack/react-query'; type ServerListProps = { @@ -33,7 +29,7 @@ export const ServerList = ({ manageServers = false, withVirtualServers = false, }: ServerListProps) => { - const { ndk, user } = useNDK(); + const { storeUserServers } = useUserServers(); const { distribution } = useServerInfo(); const queryClient = useQueryClient(); const blobsWithOnlyOneOccurance = Object.values(distribution) @@ -51,16 +47,7 @@ export const ServerList = ({ }; const handleSaveServers = async (newServers: ServerType[]) => { - const ev = new NDKEvent(ndk, { - kind: USER_BLOSSOM_SERVER_LIST_KIND, - created_at: dayjs().unix(), - content: '', - pubkey: user?.pubkey || '', - tags: newServers.filter(s => s.type == 'blossom').map(s => ['server', `${s.url}`]), - }); - await ev.sign(); - console.log(ev.rawEvent()); - await ev.publish(); + await storeUserServers(newServers); }; const serversToList = useMemo( diff --git a/src/components/ServerListPopup/index.tsx b/src/components/ServerListPopup/index.tsx index 52d56c8..f601087 100644 --- a/src/components/ServerListPopup/index.tsx +++ b/src/components/ServerListPopup/index.tsx @@ -12,6 +12,7 @@ interface ServerListPopupProps { const ServerListPopup: React.FC = ({ isOpen, onClose, onSave, initialServers }) => { const [servers, setServers] = useState([]); const [newServer, setNewServer] = useState(''); + const [newServerType, setNewServerType] = useState<'blossom' | 'nip96'>('blossom'); const dialogRef = useRef(null); useEffect(() => { @@ -28,7 +29,7 @@ const ServerListPopup: React.FC = ({ isOpen, onClose, onSa const handleAddServer = () => { if (newServer.trim()) { - setServers([...servers, { name: newServer.trim(), url: newServer.trim(), type: 'blossom' }]); + setServers([...servers, { name: newServer.trim(), url: newServer.trim(), type: newServerType }]); setNewServer(''); } }; @@ -72,25 +73,13 @@ const ServerListPopup: React.FC = ({ isOpen, onClose, onSa {server.url}
{server.type}
- - -
@@ -98,7 +87,7 @@ const ServerListPopup: React.FC = ({ isOpen, onClose, onSa ))} -
+
= ({ isOpen, onClose, onSa value={newServer} onChange={e => setNewServer(e.target.value)} /> +
+ + +
diff --git a/src/components/UploadOboarding.tsx b/src/components/UploadOboarding.tsx new file mode 100644 index 0000000..df999aa --- /dev/null +++ b/src/components/UploadOboarding.tsx @@ -0,0 +1,92 @@ +import { ServerIcon } from '@heroicons/react/24/outline'; +import { useState } from 'react'; +import { Server, useUserServers } from '../utils/useUserServers'; + +const defaultServers: (Server & { description: string; buyUrl?: string })[] = [ + { + name: 'nostr.build', + url: 'https://nostr.build', + buyUrl: 'https://nostr.build/plans/', + description: 'Free tier is limited to 20MB upload size.', + type: 'nip96', + }, + { + name: 'nostrcheck.me', + url: 'https://nostrcheck.me', + description: 'A server for checking Nostr keys and addresses.', + type: 'nip96', + }, + { + name: 'satellite.earth', + url: 'https://cdn.satellite.earth', + description: 'A payed server with cheap prices 0.05 USD per GB.', + buyUrl: 'https://cdn.satellite.earth/', + type: 'blossom', + }, +]; +export default function UploadOnboarding() { + const [checkedState, setCheckedState] = useState(new Array(defaultServers.length).fill(true)); + const { storeUserServers } = useUserServers(); + + const handleCheckboxChange = (index: number) => { + const updatedCheckedState = checkedState.map((item, pos) => (pos === index ? !item : item)); + setCheckedState(updatedCheckedState); + }; + + return ( +
+ ); +} diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index a36e24f..050688f 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -71,25 +71,21 @@ function Home() { withVirtualServers={true} > - {selectedServer && - serverInfo[selectedServer] && - selectedServerBlobs && - selectedServerBlobs.length > 0 && - ( - { - for (const blob of blobs) { - await deleteBlob.mutateAsync({ - server: serverInfo[selectedServer], - hash: blob.sha256, - }); - } - }} - > - )} + {selectedServer && serverInfo[selectedServer] && selectedServerBlobs && selectedServerBlobs.length > 0 && ( + { + for (const blob of blobs) { + await deleteBlob.mutateAsync({ + server: serverInfo[selectedServer], + hash: blob.sha256, + }); + } + }} + > + )}
); } diff --git a/src/pages/Upload.tsx b/src/pages/Upload.tsx index d5df90d..0c66fed 100644 --- a/src/pages/Upload.tsx +++ b/src/pages/Upload.tsx @@ -20,9 +20,10 @@ import { useNavigate } from 'react-router-dom'; import { NostrEvent } from '@nostr-dev-kit/ndk'; import UploadPublished from '../components/UploadPublished'; import { InformationCircleIcon } from '@heroicons/react/24/outline'; +import UploadOnboarding from '../components/UploadOboarding'; function Upload() { - const servers = useUserServers(); + const { servers } = useUserServers(); const { signEventTemplate } = useNDK(); const { serverInfo } = useServerInfo(); const queryClient = useQueryClient(); @@ -59,7 +60,7 @@ function Upload() { } async function createThumbnailForImage(file: File, width: number, height: number) { - const thumbnailFile = (width > 300 || height > 300) ? await resizeImage(file, 300, 300) : undefined + const thumbnailFile = width > 300 || height > 300 ? await resizeImage(file, 300, 300) : undefined; return thumbnailFile && URL.createObjectURL(thumbnailFile); } @@ -308,7 +309,7 @@ function Upload() { setFileEventsToPublish(prev => prev.map(f => (f.x === fe.x ? { ...f, events: [...f.events, publishedEvent] } : f)) ); - } + } } if (fe.publish.audio) { if (!fe.publishedThumbnail) { @@ -347,7 +348,7 @@ function Upload() { const newData: Partial = { publishedThumbnail: selfHostedThumbnail.url, thumbnails: [selfHostedThumbnail.url], - }; + }; const publishedEvent = await publishVideoEvent({ ...fe, ...newData }); setFileEventsToPublish(prev => prev.map(f => @@ -391,75 +392,81 @@ function Upload() { return (
-
    -
  • = 0 ? 'step-primary' : ''}`}>Choose files
  • -
  • = 1 ? 'step-primary' : ''}`}>Upload
  • -
  • = 2 ? 'step-primary' : ''}`}>Add metadata
  • -
  • = 3 ? 'step-primary' : ''}`}>Publish to NOSTR
  • -
- {uploadStep <= 1 && ( -
- {uploadStep == 0 && ( - - )} + {!servers || servers.length == 0 ? ( + + ) : ( + <> +
    +
  • = 0 ? 'step-primary' : ''}`}>Choose files
  • +
  • = 1 ? 'step-primary' : ''}`}>Upload
  • +
  • = 2 ? 'step-primary' : ''}`}>Add metadata
  • +
  • = 3 ? 'step-primary' : ''}`}>Publish to NOSTR
  • +
+ {uploadStep <= 1 && ( +
+ {uploadStep == 0 && ( + + )} - {uploadStep == 1 && } -
- )} - {uploadStep == 2 && fileEventsToPublish.length > 0 && ( -
+ {uploadStep == 2 && fileEventsToPublish.length > 0 && ( +
+

Publish events

+
+ {fileEventsToPublish.map(fe => ( + + setFileEventsToPublish(prev => prev.map(f => (f.x === fe.x ? updatedFe : f)) as FileEventData[]) + } + /> + ))} +
+ {audioCount > 0 && ( +
+ + Audio events are not widely supported yet. Currently they are only used by{' '} + + stemstr.app + +
+ )} +
+ + {publishCount > 0 && ( + + )} +
+
+ )} + {uploadStep == 3 && } + )} - {uploadStep == 3 && }
); } diff --git a/src/utils/blossom.ts b/src/utils/blossom.ts index 4dcc0c6..3cee802 100644 --- a/src/utils/blossom.ts +++ b/src/utils/blossom.ts @@ -4,7 +4,6 @@ import dayjs from 'dayjs'; const blossomUrlRegex = /https?:\/\/(?:www\.)?[^\s/]+\/([a-fA-F0-9]{64})(?:\.[a-zA-Z0-9]+)?/g; - export function extractHashesFromContent(text: string) { let match; const hashes = []; diff --git a/src/utils/useLocalStorageState.ts b/src/utils/useLocalStorageState.ts index 03e4bf7..85c1479 100644 --- a/src/utils/useLocalStorageState.ts +++ b/src/utils/useLocalStorageState.ts @@ -1,210 +1,200 @@ -import type { Dispatch, SetStateAction } from 'react' -import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react' +import type { Dispatch, SetStateAction } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'; // in memory fallback used when `localStorage` throws an error -export const inMemoryData = new Map() +export const inMemoryData = new Map(); export type LocalStorageOptions = { - defaultValue?: T | (() => T) - storageSync?: boolean - serializer?: { - stringify: (value: unknown) => string - parse: (value: string) => unknown - } -} + defaultValue?: T | (() => T); + storageSync?: boolean; + serializer?: { + stringify: (value: unknown) => string; + parse: (value: string) => unknown; + }; +}; // - `useLocalStorageState()` return type // - first two values are the same as `useState` export type LocalStorageState = [ - T, - Dispatch>, - { - isPersistent: boolean - removeItem: () => void - }, -] + T, + Dispatch>, + { + isPersistent: boolean; + removeItem: () => void; + }, +]; export default function useLocalStorageState( - key: string, - options?: LocalStorageOptions, -): LocalStorageState + key: string, + options?: LocalStorageOptions +): LocalStorageState; export default function useLocalStorageState( - key: string, - options?: Omit, 'defaultValue'>, -): LocalStorageState -export default function useLocalStorageState( - key: string, - options?: LocalStorageOptions, -): LocalStorageState + key: string, + options?: Omit, 'defaultValue'> +): LocalStorageState; +export default function useLocalStorageState(key: string, options?: LocalStorageOptions): LocalStorageState; export default function useLocalStorageState( - key: string, - options?: LocalStorageOptions, + key: string, + options?: LocalStorageOptions ): LocalStorageState { - const serializer = options?.serializer - const [defaultValue] = useState(options?.defaultValue) - return useLocalStorage( - key, - defaultValue, - options?.storageSync, - serializer?.parse, - serializer?.stringify, - ) + const serializer = options?.serializer; + const [defaultValue] = useState(options?.defaultValue); + return useLocalStorage(key, defaultValue, options?.storageSync, serializer?.parse, serializer?.stringify); } function useLocalStorage( - key: string, - defaultValue: T | undefined, - storageSync: boolean = true, - parse: (value: string) => unknown = parseJSON, - stringify: (value: unknown) => string = JSON.stringify, + key: string, + defaultValue: T | undefined, + storageSync: boolean = true, + parse: (value: string) => unknown = parseJSON, + stringify: (value: unknown) => string = JSON.stringify ): LocalStorageState { - // we keep the `parsed` value in a ref because `useSyncExternalStore` requires a cached version - const storageItem = useRef<{ string: string | null; parsed: T | undefined }>({ - string: null, - parsed: undefined, - }) + // we keep the `parsed` value in a ref because `useSyncExternalStore` requires a cached version + const storageItem = useRef<{ string: string | null; parsed: T | undefined }>({ + string: null, + parsed: undefined, + }); - const value = useSyncExternalStore( - // useSyncExternalStore.subscribe - useCallback( - (onStoreChange) => { - const onChange = (localKey: string): void => { - if (key === localKey) { - onStoreChange() - } - } - callbacks.add(onChange) - return (): void => { - callbacks.delete(onChange) - } - }, - [key], - ), + const value = useSyncExternalStore( + // useSyncExternalStore.subscribe + useCallback( + onStoreChange => { + const onChange = (localKey: string): void => { + if (key === localKey) { + onStoreChange(); + } + }; + callbacks.add(onChange); + return (): void => { + callbacks.delete(onChange); + }; + }, + [key] + ), - // useSyncExternalStore.getSnapshot - () => { - const string = goodTry(() => localStorage.getItem(key)) ?? null + // useSyncExternalStore.getSnapshot + () => { + const string = goodTry(() => localStorage.getItem(key)) ?? null; - if (inMemoryData.has(key)) { - storageItem.current.parsed = inMemoryData.get(key) as T | undefined - } else if (string !== storageItem.current.string) { - let parsed: T | undefined + if (inMemoryData.has(key)) { + storageItem.current.parsed = inMemoryData.get(key) as T | undefined; + } else if (string !== storageItem.current.string) { + let parsed: T | undefined; - try { - parsed = string === null ? defaultValue : (parse(string) as T) - } catch { - parsed = defaultValue - } - - storageItem.current.parsed = parsed - } - - storageItem.current.string = string - - // store default value in localStorage: - // - initial issue: https://github.com/astoilkov/use-local-storage-state/issues/26 - // issues that were caused by incorrect initial and secondary implementations: - // - https://github.com/astoilkov/use-local-storage-state/issues/30 - // - https://github.com/astoilkov/use-local-storage-state/issues/33 - if (defaultValue !== undefined && string === null) { - // reasons for `localStorage` to throw an error: - // - maximum quota is exceeded - // - under Mobile Safari (since iOS 5) when the user enters private mode - // `localStorage.setItem()` will throw - // - trying to access localStorage object when cookies are disabled in Safari throws - // "SecurityError: The operation is insecure." - // eslint-disable-next-line no-console - goodTry(() => { - const string = stringify(defaultValue) - localStorage.setItem(key, string) - storageItem.current = { string, parsed: defaultValue } - }) - } - - return storageItem.current.parsed - }, - - // useSyncExternalStore.getServerSnapshot - () => defaultValue, - ) - const setState = useCallback( - (newValue: SetStateAction): void => { - const value = - newValue instanceof Function ? newValue(storageItem.current.parsed) : newValue - - // reasons for `localStorage` to throw an error: - // - maximum quota is exceeded - // - under Mobile Safari (since iOS 5) when the user enters private mode - // `localStorage.setItem()` will throw - // - trying to access `localStorage` object when cookies are disabled in Safari throws - // "SecurityError: The operation is insecure." - try { - localStorage.setItem(key, stringify(value)) - - inMemoryData.delete(key) - } catch { - inMemoryData.set(key, value) - } - - triggerCallbacks(key) - }, - [key, stringify], - ) - - // - syncs change across tabs, windows, iframes - // - the `storage` event is called only in all tabs, windows, iframe's except the one that - // triggered the change - useEffect(() => { - if (!storageSync) { - return undefined + try { + parsed = string === null ? defaultValue : (parse(string) as T); + } catch { + parsed = defaultValue; } - const onStorage = (e: StorageEvent): void => { - if (e.key === key && e.storageArea === goodTry(() => localStorage)) { - triggerCallbacks(key) - } - } + storageItem.current.parsed = parsed; + } - window.addEventListener('storage', onStorage) + storageItem.current.string = string; - return (): void => window.removeEventListener('storage', onStorage) - }, [key, storageSync]) + // store default value in localStorage: + // - initial issue: https://github.com/astoilkov/use-local-storage-state/issues/26 + // issues that were caused by incorrect initial and secondary implementations: + // - https://github.com/astoilkov/use-local-storage-state/issues/30 + // - https://github.com/astoilkov/use-local-storage-state/issues/33 + if (defaultValue !== undefined && string === null) { + // reasons for `localStorage` to throw an error: + // - maximum quota is exceeded + // - under Mobile Safari (since iOS 5) when the user enters private mode + // `localStorage.setItem()` will throw + // - trying to access localStorage object when cookies are disabled in Safari throws + // "SecurityError: The operation is insecure." + // eslint-disable-next-line no-console + goodTry(() => { + const string = stringify(defaultValue); + localStorage.setItem(key, string); + storageItem.current = { string, parsed: defaultValue }; + }); + } - return useMemo( - () => [ - value, - setState, - { - isPersistent: value === defaultValue || !inMemoryData.has(key), - removeItem(): void { - goodTry(() => localStorage.removeItem(key)) + return storageItem.current.parsed; + }, - inMemoryData.delete(key) + // useSyncExternalStore.getServerSnapshot + () => defaultValue + ); + const setState = useCallback( + (newValue: SetStateAction): void => { + const value = newValue instanceof Function ? newValue(storageItem.current.parsed) : newValue; - triggerCallbacks(key) - }, - }, - ], - [key, setState, value, defaultValue], - ) + // reasons for `localStorage` to throw an error: + // - maximum quota is exceeded + // - under Mobile Safari (since iOS 5) when the user enters private mode + // `localStorage.setItem()` will throw + // - trying to access `localStorage` object when cookies are disabled in Safari throws + // "SecurityError: The operation is insecure." + try { + localStorage.setItem(key, stringify(value)); + + inMemoryData.delete(key); + } catch { + inMemoryData.set(key, value); + } + + triggerCallbacks(key); + }, + [key, stringify] + ); + + // - syncs change across tabs, windows, iframes + // - the `storage` event is called only in all tabs, windows, iframe's except the one that + // triggered the change + useEffect(() => { + if (!storageSync) { + return undefined; + } + + const onStorage = (e: StorageEvent): void => { + if (e.key === key && e.storageArea === goodTry(() => localStorage)) { + triggerCallbacks(key); + } + }; + + window.addEventListener('storage', onStorage); + + return (): void => window.removeEventListener('storage', onStorage); + }, [key, storageSync]); + + return useMemo( + () => [ + value, + setState, + { + isPersistent: value === defaultValue || !inMemoryData.has(key), + removeItem(): void { + goodTry(() => localStorage.removeItem(key)); + + inMemoryData.delete(key); + + triggerCallbacks(key); + }, + }, + ], + [key, setState, value, defaultValue] + ); } // notifies all instances using the same `key` to update -const callbacks = new Set<(key: string) => void>() +const callbacks = new Set<(key: string) => void>(); function triggerCallbacks(key: string): void { - for (const callback of [...callbacks]) { - callback(key) - } + for (const callback of [...callbacks]) { + callback(key); + } } // a wrapper for `JSON.parse()` that supports "undefined" value. otherwise, // `JSON.parse(JSON.stringify(undefined))` returns the string "undefined" not the value `undefined` function parseJSON(value: string): unknown { - return value === 'undefined' ? undefined : JSON.parse(value) + return value === 'undefined' ? undefined : JSON.parse(value); } function goodTry(tryFn: () => T): T | undefined { - try { - return tryFn() - } catch {} -} \ No newline at end of file + try { + return tryFn(); + } catch {} +} diff --git a/src/utils/useServerInfo.ts b/src/utils/useServerInfo.ts index 5a8a3d2..43b8b5a 100644 --- a/src/utils/useServerInfo.ts +++ b/src/utils/useServerInfo.ts @@ -42,7 +42,7 @@ const mergeBlobs = ( }; export const useServerInfo = () => { - const servers = useUserServers(); + const { servers } = useUserServers(); const { user, signEventTemplate } = useNDK(); const [features, setFeatures] = useState({}); diff --git a/src/utils/useUserServers.ts b/src/utils/useUserServers.ts index 1563184..b45cc14 100644 --- a/src/utils/useUserServers.ts +++ b/src/utils/useUserServers.ts @@ -1,11 +1,12 @@ import { useMemo } from 'react'; import { useNDK } from '../utils/ndk'; import { nip19 } from 'nostr-tools'; -import { NDKKind } from '@nostr-dev-kit/ndk'; +import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk'; import { USER_BLOSSOM_SERVER_LIST_KIND } from 'blossom-client-sdk'; import useEvent from './useEvent'; import { useQueries } from '@tanstack/react-query'; import { Nip96ServerConfig, fetchNip96ServerConfig } from './nip96'; +import dayjs from 'dayjs'; type ServerType = 'blossom' | 'nip96'; @@ -17,12 +18,40 @@ export type Server = { nip96?: Nip96ServerConfig; }; -const USER_NIP96_SERVER_LIST_KIND = 10096; +export const USER_NIP96_SERVER_LIST_KIND = 10096; -export const useUserServers = (): Server[] => { - const { user } = useNDK(); +export const useUserServers = (): { + servers: Server[]; + storeUserServers: (newServers: Server[]) => Promise; +} => { + const { user, ndk } = useNDK(); const pubkey = user?.npub && (nip19.decode(user?.npub).data as string); // TODO validate type + const storeUserServers = async (newServers: Server[]) => { + if (!pubkey) return; + const ev = new NDKEvent(ndk, { + kind: USER_BLOSSOM_SERVER_LIST_KIND, + created_at: dayjs().unix(), + content: '', + pubkey, + tags: newServers.filter(s => s.type == 'blossom').map(s => ['server', `${s.url}`]), + }); + await ev.sign(); + console.log(ev.rawEvent()); + await ev.publish(); + + const evNip96 = new NDKEvent(ndk, { + kind: USER_NIP96_SERVER_LIST_KIND, + created_at: dayjs().unix(), + content: '', + pubkey, + tags: newServers.filter(s => s.type == 'nip96').map(s => ['server', `${s.url}`]), + }); + await evNip96.sign(); + console.log(evNip96.rawEvent()); + await evNip96.publish(); + }; + const blossomServerListEvent = useEvent( { kinds: [USER_BLOSSOM_SERVER_LIST_KIND as NDKKind], authors: [pubkey!] }, { disable: !pubkey } @@ -46,16 +75,17 @@ export const useUserServers = (): Server[] => { }, [blossomServerListEvent]); const nip96Servers = useMemo((): Server[] => { + if (!user) return []; return [ - /*...(nip96ServerListEvent?.getMatchingTags('server').map(t => t[1]) || []).map(s => { - const url = s.toLocaleLowerCase().replace(/\/$/, ''); + ...(nip96ServerListEvent?.getMatchingTags('server').map(t => t[1]) || []).map(s => { + const url = s.toLocaleLowerCase().replace(/\/$/, ''); - return { - url, - name: url.replace(/https?:\/\//, ''), - type: 'nip96' as ServerType, - }; - }),*/ { + return { + url, + name: url.replace(/https?:\/\//, ''), + type: 'nip96' as ServerType, + }; + }) /* { url: 'https://nostrcheck.me', name: 'nostrcheck.me', type: 'nip96' as ServerType, @@ -66,6 +96,7 @@ export const useUserServers = (): Server[] => { type: 'nip96' as ServerType, message: 'nostr.build does currently not support listing files', }, + */, ]; }, [nip96ServerListEvent]); @@ -83,5 +114,5 @@ export const useUserServers = (): Server[] => { ]; }, [blossomServers, nip96Servers, nip96InfoQueries]); - return servers; + return { servers, storeUserServers }; };