convert to typescript

This commit is contained in:
hzrd149
2023-02-07 17:04:18 -06:00
parent 27260a9f9b
commit 40352f77c7
41 changed files with 470 additions and 191 deletions

View File

@@ -8,6 +8,6 @@
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="src/index.jsx"></script> <script type="module" src="src/index.tsx"></script>
</body> </body>
</html> </html>

View File

@@ -5,14 +5,9 @@
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"start": "vite serve", "start": "vite serve",
"build": "vite build", "build": "tsc && vite build",
"format": "prettier --ignore-path .gitignore -w ." "format": "prettier --ignore-path .gitignore -w ."
}, },
"devDependencies": {
"prettier": "^2.8.1",
"vite": "^4.0.2",
"vite-plugin-pwa": "^0.14.0"
},
"dependencies": { "dependencies": {
"@chakra-ui/icons": "^2.0.14", "@chakra-ui/icons": "^2.0.14",
"@chakra-ui/react": "^2.4.4", "@chakra-ui/react": "^2.4.4",
@@ -29,5 +24,14 @@
"react-router-dom": "^6.5.0", "react-router-dom": "^6.5.0",
"react-use": "^17.4.0", "react-use": "^17.4.0",
"rxjs": "^7.8.0" "rxjs": "^7.8.0"
},
"devDependencies": {
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^3.0.0",
"prettier": "^2.8.1",
"typescript": "^4.9.4",
"vite": "^4.0.2",
"vite-plugin-pwa": "^0.14.0"
} }
} }

View File

@@ -1,3 +1,4 @@
import React, { useState } from "react";
import { import {
Button, Button,
Modal, Modal,
@@ -10,7 +11,6 @@ import {
Table, Table,
Thead, Thead,
Tbody, Tbody,
Tfoot,
Tr, Tr,
Th, Th,
Td, Td,
@@ -19,11 +19,11 @@ import {
useDisclosure, useDisclosure,
Badge, Badge,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import React, { useState } from "react";
import { useInterval } from "react-use"; import { useInterval } from "react-use";
import { Relay } from "../services/relays";
import relayPool from "../services/relays/relay-pool"; import relayPool from "../services/relays/relay-pool";
const getRelayStatusText = (relay) => { const getRelayStatusText = (relay: Relay) => {
if (relay.connecting) return "Connecting..."; if (relay.connecting) return "Connecting...";
if (relay.connected) return "Connected"; if (relay.connected) return "Connected";
if (relay.closing) return "Disconnecting..."; if (relay.closing) return "Disconnecting...";
@@ -32,7 +32,7 @@ const getRelayStatusText = (relay) => {
export const ConnectedRelays = () => { export const ConnectedRelays = () => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const [relays, setRelays] = useState([]); const [relays, setRelays] = useState<Relay[]>([]);
useInterval(() => { useInterval(() => {
setRelays(relayPool.getRelays()); setRelays(relayPool.getRelays());

View File

@@ -1,5 +1,8 @@
import React from "react"; import React from "react";
import { ErrorBoundary as ErrorBoundaryHelper } from "react-error-boundary"; import {
ErrorBoundary as ErrorBoundaryHelper,
FallbackProps,
} from "react-error-boundary";
import { import {
Alert, Alert,
AlertIcon, AlertIcon,
@@ -7,7 +10,7 @@ import {
AlertDescription, AlertDescription,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
export function ErrorFallback({ error, resetErrorBoundary }) { export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return ( return (
<Alert status="error"> <Alert status="error">
<AlertIcon /> <AlertIcon />
@@ -17,7 +20,12 @@ export function ErrorFallback({ error, resetErrorBoundary }) {
); );
} }
export const ErrorBoundary = ({ children, ...props }) => ( export const ErrorBoundary = ({
children,
...props
}: {
children: React.ReactNode;
}) => (
<ErrorBoundaryHelper FallbackComponent={ErrorFallback} {...props}> <ErrorBoundaryHelper FallbackComponent={ErrorFallback} {...props}>
{children} {children}
</ErrorBoundaryHelper> </ErrorBoundaryHelper>

View File

@@ -14,7 +14,7 @@ import {
import { useAsync } from "react-use"; import { useAsync } from "react-use";
import { getRelaysEventWasSeen } from "../services/events-seen"; import { getRelaysEventWasSeen } from "../services/events-seen";
export const EventSeenOn = ({ id }) => { export const EventSeenOn = ({ id }: { id: string }) => {
const { value } = useAsync(() => getRelaysEventWasSeen(id), [id]); const { value } = useAsync(() => getRelaysEventWasSeen(id), [id]);
if (!value) return null; if (!value) return null;

View File

@@ -4,7 +4,7 @@ import { useNavigate } from "react-router-dom";
import { ErrorBoundary } from "./error-boundary"; import { ErrorBoundary } from "./error-boundary";
import { ConnectedRelays } from "./connected-relays"; import { ConnectedRelays } from "./connected-relays";
export const Page = ({ children }) => { export const Page = ({ children }: {children: React.ReactNode}) => {
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (

View File

@@ -10,8 +10,15 @@ import {
ModalOverlay, ModalOverlay,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import { NostrEvent } from "../types/nostr-event";
export const PostModal = ({ event, onClose, isOpen }) => ( export type PostModalProps = {
event: NostrEvent;
isOpen: boolean;
onClose: () => void;
};
export const PostModal = ({ event, onClose, isOpen }: PostModalProps) => (
<Modal isOpen={isOpen} onClose={onClose} size="6xl"> <Modal isOpen={isOpen} onClose={onClose} size="6xl">
<ModalOverlay /> <ModalOverlay />
<ModalContent> <ModalContent>

View File

@@ -8,6 +8,7 @@ import {
CardHeader, CardHeader,
Flex, Flex,
Heading, Heading,
HStack,
Text, Text,
useDisclosure, useDisclosure,
VStack, VStack,
@@ -16,9 +17,12 @@ import ReactMarkdown from "react-markdown";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import moment from "moment"; import moment from "moment";
import { PostModal } from "./post-modal"; import { PostModal } from "./post-modal";
import { EventSeenOn } from "./event-seen-on"; import { NostrEvent } from "../types/nostr-event";
export const Post = ({ event }) => { export type PostProps = {
event: NostrEvent;
};
export const Post = ({ event }: PostProps) => {
const { isOpen, onClose, onOpen } = useDisclosure(); const { isOpen, onClose, onOpen } = useDisclosure();
const isLong = event.content.length > 800; const isLong = event.content.length > 800;
@@ -26,7 +30,7 @@ export const Post = ({ event }) => {
return ( return (
<Card> <Card>
<CardHeader> <CardHeader>
<Flex spacing="4"> <HStack spacing="4">
<Flex flex="1" gap="4" alignItems="center" flexWrap="wrap"> <Flex flex="1" gap="4" alignItems="center" flexWrap="wrap">
<Avatar name="Segun Adebayo" src="https://bit.ly/sage-adebayo" /> <Avatar name="Segun Adebayo" src="https://bit.ly/sage-adebayo" />
@@ -37,7 +41,7 @@ export const Post = ({ event }) => {
<Text>{moment(event.created_at * 1000).fromNow()}</Text> <Text>{moment(event.created_at * 1000).fromNow()}</Text>
</Box> </Box>
</Flex> </Flex>
</Flex> </HStack>
</CardHeader> </CardHeader>
<CardBody pt={0}> <CardBody pt={0}>
<VStack alignItems="flex-start" justifyContent="stretch"> <VStack alignItems="flex-start" justifyContent="stretch">

View File

@@ -1,9 +1,14 @@
import { useRef } from "react"; import { useRef } from "react";
import { useDeepCompareEffect, useMount, useUnmount } from "react-use"; import { useDeepCompareEffect, useMount, useUnmount } from "react-use";
import { Subscription } from "../services/subscriptions"; import { Subscription } from "../services/subscriptions";
import { NostrQuery } from "../types/nostr-query";
export function useSubscription(urls, query, name) { export function useSubscription(
const sub = useRef(null); urls: string[],
query: NostrQuery,
name?: string
) {
const sub = useRef<Subscription | null>(null);
sub.current = sub.current || new Subscription(urls, query, name); sub.current = sub.current || new Subscription(urls, query, name);
useMount(() => { useMount(() => {
@@ -16,5 +21,5 @@ export function useSubscription(urls, query, name) {
if (sub.current) sub.current.close(); if (sub.current) sub.current.close();
}); });
return sub.current; return sub.current as Subscription;
} }

View File

@@ -4,7 +4,9 @@ import { App } from "./app";
import { Providers } from "./providers"; import { Providers } from "./providers";
import "./services/events-seen"; import "./services/events-seen";
const root = createRoot(document.getElementById("root")); const element = document.getElementById("root");
if (!element) throw new Error("missing mount point");
const root = createRoot(element);
root.render( root.render(
<Providers> <Providers>
<App /> <App />

View File

@@ -4,7 +4,7 @@ import { RelaysProvider } from "./relay-provider";
import { HashRouter } from "react-router-dom"; import { HashRouter } from "react-router-dom";
import theme from "../theme"; import theme from "../theme";
export const Providers = ({ children }) => ( export const Providers = ({ children }: { children: React.ReactNode }) => (
<ChakraProvider theme={theme}> <ChakraProvider theme={theme}>
<HashRouter> <HashRouter>
<RelaysProvider>{children}</RelaysProvider> <RelaysProvider>{children}</RelaysProvider>

View File

@@ -8,17 +8,17 @@ import React, {
import settingsService from "../services/settings"; import settingsService from "../services/settings";
const relaysContext = createContext({ const relaysContext = createContext({
relays: [], relays: [] as string[],
loading: true, loading: true,
overwriteRelays: () => {}, overwriteRelays: (urls: string[]) => {},
}); });
export function useRelays() { export function useRelays() {
return useContext(relaysContext); return useContext(relaysContext);
} }
export const RelaysProvider = ({ children }) => { export const RelaysProvider = ({ children }: { children: React.ReactNode }) => {
const [relays, setRelays] = useState([]); const [relays, setRelays] = useState<string[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const update = useCallback(async () => { const update = useCallback(async () => {
@@ -27,7 +27,7 @@ export const RelaysProvider = ({ children }) => {
}, [setRelays]); }, [setRelays]);
const overwriteRelays = useCallback( const overwriteRelays = useCallback(
async (urls) => { async (urls: string[]) => {
if (urls) await settingsService.setRelays(urls); if (urls) await settingsService.setRelays(urls);
await update(); await update();
}, },

View File

@@ -1,9 +0,0 @@
import { openDB } from "idb";
import { upgrade } from "./migrations";
const version = 1;
const db = await openDB("storage", version, {
upgrade,
});
export default db;

50
src/services/db/index.ts Normal file
View File

@@ -0,0 +1,50 @@
import { openDB } from "idb";
import { IDBPDatabase, IDBPTransaction, StoreNames } from "idb";
import { CustomSchema } from "./schema";
type MigrationFunction = (
database: IDBPDatabase<CustomSchema>,
transaction: IDBPTransaction<
CustomSchema,
StoreNames<CustomSchema>[],
"versionchange"
>,
event: IDBVersionChangeEvent
) => void;
const MIGRATIONS: MigrationFunction[] = [
// 0 -> 1
function (db, transaction, event) {
db.createObjectStore("user-metadata", {
keyPath: "pubkey",
});
const eventsSeen = db.createObjectStore("events-seen", { keyPath: "id" });
eventsSeen.createIndex("lastSeen", "lastSeen");
// db.createObjectStore("contacts", {
// keyPath: "pubkey",
// });
// setup data
const settings = db.createObjectStore("settings");
settings.put(["wss://nostr.rdfriedl.com"], "relays");
},
];
const version = 1;
const db = await openDB<CustomSchema>("storage", version, {
upgrade(db, oldVersion, newVersion, transaction, event) {
// TODO: why is newVersion sometimes null?
// @ts-ignore
for (let i = oldVersion; i <= newVersion; i++) {
if (MIGRATIONS[i]) {
console.log(`Running database migration ${i}`);
MIGRATIONS[i](db, transaction, event);
}
}
},
});
export default db;

View File

@@ -1,29 +0,0 @@
const MIGRATIONS = [
// 0 -> 1
function (db, transaction, event) {
const userMetadata = db.createObjectStore("user-metadata", {
keyPath: "pubkey",
});
userMetadata.createIndex("id", "id", { unique: true });
const eventsSeen = db.createObjectStore("events-seen", { keyPath: "id" });
eventsSeen.createIndex("lastSeen", "lastSeen");
db.createObjectStore("contacts", {
keyPath: "pubkey",
});
// setup data
const settings = db.createObjectStore("settings");
settings.put(["wss://nostr.rdfriedl.com"], "relays");
},
];
export function upgrade(db, oldVersion, newVersion, transaction, event) {
for (let i = oldVersion; i <= newVersion; i++) {
if (MIGRATIONS[i]) {
console.log(`Running database migration ${i}`);
MIGRATIONS[i](db, transaction, event);
}
}
}

21
src/services/db/schema.ts Normal file
View File

@@ -0,0 +1,21 @@
import { DBSchema } from "idb";
export interface CustomSchema extends DBSchema {
"user-metadata": {
key: string;
value: any;
};
"events-seen": {
key: string;
value: {
id: string;
relays: string[];
lastSeen: Date;
};
indexes: { lastSeen: Date };
};
settings: {
key: string;
value: any;
};
}

View File

@@ -22,6 +22,6 @@ relayPool.onRelayCreated.subscribe((relay) => {
}); });
}); });
export async function getRelaysEventWasSeen(id) { export async function getRelaysEventWasSeen(id: string) {
return await db.get("events-seen", id); return await db.get("events-seen", id);
} }

View File

@@ -3,28 +3,28 @@ import { Relay } from "./relay";
import settingsService from "../settings"; import settingsService from "../settings";
export class RelayPool { export class RelayPool {
relays = new Map(); relays = new Map<string, Relay>();
relayClaims = new Map(); relayClaims = new Map<string, Set<any>>();
onRelayCreated = new Subject(); onRelayCreated = new Subject<Relay>();
getRelays() { getRelays() {
return Array.from(this.relays.values()); return Array.from(this.relays.values());
} }
getRelayClaims(url) { getRelayClaims(url: string) {
if (!this.relayClaims.has(url)) { if (!this.relayClaims.has(url)) {
this.relayClaims.set(url, new Set()); this.relayClaims.set(url, new Set());
} }
return this.relayClaims.get(url); return this.relayClaims.get(url) as Set<any>;
} }
requestRelay(url, connect = true) { requestRelay(url: string, connect = true) {
if (!this.relays.has(url)) { if (!this.relays.has(url)) {
const newRelay = new Relay(url); const newRelay = new Relay(url);
this.relays.set(url, newRelay); this.relays.set(url, newRelay);
this.onRelayCreated.next(newRelay); this.onRelayCreated.next(newRelay);
} }
const relay = this.relays.get(url); const relay = this.relays.get(url) as Relay;
if (connect && !relay.okay) { if (connect && !relay.okay) {
relay.open(); relay.open();
} }
@@ -49,10 +49,10 @@ export class RelayPool {
} }
// id can be anything // id can be anything
addClaim(url, id) { addClaim(url: string, id: any) {
this.getRelayClaims(url).add(id); this.getRelayClaims(url).add(id);
} }
removeClaim(url, id) { removeClaim(url: string, id: any) {
this.getRelayClaims(url).delete(id); this.getRelayClaims(url).delete(id);
} }
@@ -68,6 +68,7 @@ export class RelayPool {
const relayPool = new RelayPool(); const relayPool = new RelayPool();
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
// @ts-ignore
window.relayPool = relayPool; window.relayPool = relayPool;
} }

View File

@@ -1,7 +1,26 @@
import { Subject } from "rxjs"; import { Subject } from "rxjs";
import { IncomingNostrEvent, NostrEvent } from "../../types/nostr-event";
import { NostrOutgoingMessage } from "../../types/nostr-query";
export type IncomingEvent = {
type: "EVENT";
subId: string;
body: NostrEvent;
};
export type IncomingNotice = {
type: "NOTICE";
message: string;
};
export class Relay { export class Relay {
constructor(url) { url: string;
onOpen: Subject<Relay>;
onClose: Subject<Relay>;
onEvent: Subject<IncomingEvent>;
onNotice: Subject<IncomingNotice>;
ws?: WebSocket;
constructor(url: string) {
this.url = url; this.url = url;
this.onOpen = new Subject(); this.onOpen = new Subject();
@@ -30,9 +49,9 @@ export class Relay {
}; };
this.ws.onmessage = this.handleMessage.bind(this); this.ws.onmessage = this.handleMessage.bind(this);
} }
send(json) { send(json: NostrOutgoingMessage) {
if (this.connected) { if (this.connected) {
this.ws.send(JSON.stringify(json)); this.ws?.send(JSON.stringify(json));
} }
} }
close() { close() {
@@ -58,20 +77,20 @@ export class Relay {
return this.ws?.readyState; return this.ws?.readyState;
} }
handleMessage(event) { handleMessage(event: MessageEvent<string>) {
// skip empty events // skip empty events
if (!event.data) return; if (!event.data) return;
try { try {
const data = JSON.parse(event.data); const data: IncomingNostrEvent = JSON.parse(event.data);
const type = data[0]; const type = data[0];
switch (type) { switch (type) {
case "EVENT": case "EVENT":
this.onEvent.next({ type, subId: data[1], body: data[2] }, this); this.onEvent.next({ type, subId: data[1], body: data[2] });
break; break;
case "NOTICE": case "NOTICE":
this.onNotice.next({ type, message: data[1] }, this); this.onNotice.next({ type, message: data[1] });
break; break;
} }
} catch (e) { } catch (e) {

View File

@@ -1,10 +1,10 @@
import db from "./db"; import db from "./db";
export async function getRelays() { export async function getRelays(): Promise<string[]> {
return await db.get("settings", "relays"); return await db.get("settings", "relays");
} }
export async function setRelays(relays = []) { export async function setRelays(relays: string[] = []) {
return await db.put("settings", relays, "relays"); await db.put("settings", relays, "relays");
} }
const settingsService = { const settingsService = {
@@ -13,6 +13,7 @@ const settingsService = {
}; };
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
// @ts-ignore
window.settingsService = settingsService; window.settingsService = settingsService;
} }

View File

@@ -1,16 +1,24 @@
import { Subject } from "rxjs"; import { Subject, SubscriptionLike } from "rxjs";
import { NostrEvent } from "../types/nostr-event";
import { NostrOutgoingMessage, NostrQuery } from "../types/nostr-query";
import { Relay } from "./relays";
import { IncomingEvent } from "./relays/relay";
import relayPool from "./relays/relay-pool"; import relayPool from "./relays/relay-pool";
import settingsService from "./settings";
export class Subscription { export class Subscription {
static OPEN = "open"; static OPEN = "open";
static CLOSED = "closed"; static CLOSED = "closed";
id: string;
name?: string;
query?: NostrQuery;
relayUrls: string[];
relays: Relay[];
state = Subscription.CLOSED; state = Subscription.CLOSED;
onEvent = new Subject(); onEvent = new Subject<NostrEvent>();
cleanup = []; cleanup: SubscriptionLike[] = [];
constructor(relayUrls, query, name) { constructor(relayUrls: string[], query?: NostrQuery, name?: string) {
this.id = String(Math.floor(Math.random() * 1000000)); this.id = String(Math.floor(Math.random() * 1000000));
this.query = query; this.query = query;
this.name = name; this.name = name;
@@ -18,22 +26,23 @@ export class Subscription {
this.relays = relayUrls.map((url) => relayPool.requestRelay(url)); this.relays = relayUrls.map((url) => relayPool.requestRelay(url));
} }
handleOpen(relay) { handleOpen(relay: Relay) {
if (!this.query) return;
// when the relay connects send the req event // when the relay connects send the req event
relay.send(["REQ", this.id, this.query]); relay.send(["REQ", this.id, this.query]);
} }
handleEvent(event) { handleEvent(event: IncomingEvent) {
if (event.subId === this.id) { if (event.subId === this.id) {
this.onEvent.next(event.body); this.onEvent.next(event.body);
} }
} }
send(message) { send(message: NostrOutgoingMessage) {
for (const relay of this.relays) { for (const relay of this.relays) {
relay.send(message); relay.send(message);
} }
} }
setQuery(query) { setQuery(query: NostrQuery) {
this.query = query; this.query = query;
// if open, than update remote subscription // if open, than update remote subscription
@@ -75,8 +84,3 @@ export class Subscription {
} }
} }
} }
export async function createSubscription(query) {
const urls = await settingsService.getRelays();
return new Subscription(urls, query);
}

View File

@@ -1,22 +1,14 @@
import { BehaviorSubject, Subject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { NostrEvent } from "../types/nostr-event";
import settingsService from "./settings"; import settingsService from "./settings";
import { Subscription } from "./subscriptions"; import { Subscription } from "./subscriptions";
function waitOnSignal(signal) {
return new Promise((res) => {
const listener = (event) => {
signal.removeListener(listener);
res(event);
};
signal.addListener(listener);
});
}
class UserMetadata { class UserMetadata {
requests = new Map(); requests = new Map<string, BehaviorSubject<NostrEvent | null>>();
subscription: Subscription;
constructor(relayUrls = []) { constructor(relayUrls: string[] = []) {
this.subscription = new Subscription(relayUrls, null, "user-metadata"); this.subscription = new Subscription(relayUrls, undefined, "user-metadata");
this.subscription.onEvent.subscribe((event) => { this.subscription.onEvent.subscribe((event) => {
try { try {
@@ -30,9 +22,9 @@ class UserMetadata {
}, 1000 * 10); }, 1000 * 10);
} }
requestUserMetadata(pubkey) { requestUserMetadata(pubkey: string) {
if (!this.requests.has(pubkey)) { if (!this.requests.has(pubkey)) {
this.requests.set(pubkey, new BehaviorSubject(null)); this.requests.set(pubkey, new BehaviorSubject<NostrEvent | null>(null));
this.updateSubscription(); this.updateSubscription();
} }
return this.requests.get(pubkey); return this.requests.get(pubkey);
@@ -69,6 +61,7 @@ class UserMetadata {
const userMetadata = new UserMetadata(await settingsService.getRelays()); const userMetadata = new UserMetadata(await settingsService.getRelays());
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
// @ts-ignore
window.userMetadata = userMetadata; window.userMetadata = userMetadata;
} }

13
src/types/nostr-event.ts Normal file
View File

@@ -0,0 +1,13 @@
export type NostrEvent = {
id: string;
pubkey: string;
created_at: number;
kind: number;
tags: any[];
content: string;
sig: string;
};
export type IncomingNostrEvent =
| ["EVENT", string, NostrEvent]
| ["NOTICE", string];

21
src/types/nostr-query.ts Normal file
View File

@@ -0,0 +1,21 @@
import { NostrEvent } from "./nostr-event";
export type NostrOutgoingEvent = ["EVENT", NostrEvent];
export type NostrOutgoingRequest = ["REQ", string, NostrQuery];
export type NostrOutgoingClose = ["CLOSE", string];
export type NostrOutgoingMessage =
| NostrOutgoingEvent
| NostrOutgoingRequest
| NostrOutgoingClose;
export type NostrQuery = {
ids?: string[];
authors?: string[];
kinds?: number[];
// "#e": <a list of event ids that are referenced in an "e" tag>,
// "#p": <a list of pubkeys that are referenced in a "p" tag>,
since?: number;
until?: number;
limit?: number;
};

View File

@@ -4,10 +4,11 @@ import { useSubscription } from "../../hooks/use-subscription";
import { useRelays } from "../../providers/relay-provider"; import { useRelays } from "../../providers/relay-provider";
import { Post } from "../../components/post"; import { Post } from "../../components/post";
import moment from "moment/moment"; import moment from "moment/moment";
import { NostrEvent } from "../../types/nostr-event";
export const GlobalView = () => { export const GlobalView = () => {
const { relays } = useRelays(); const { relays } = useRelays();
const [events, setEvents] = useState({}); const [events, setEvents] = useState<Record<string, NostrEvent>>({});
const sub = useSubscription( const sub = useSubscription(
relays, relays,

View File

@@ -6,14 +6,14 @@ import {
Stack, Stack,
Textarea, Textarea,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import React, { useState } from "react"; import React, { SyntheticEvent, useState } from "react";
import { useRelays } from "../providers/relay-provider"; import { useRelays } from "../providers/relay-provider";
export const SettingsView = () => { export const SettingsView = () => {
const { relays, overwriteRelays } = useRelays(); const { relays, overwriteRelays } = useRelays();
const [relayUrls, setRelayUrls] = useState(relays.join("\n")); const [relayUrls, setRelayUrls] = useState(relays.join("\n"));
const handleSubmit = async (event) => { const handleSubmit = async (event: SyntheticEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
const newRelays = relayUrls const newRelays = relayUrls
.split("\n") .split("\n")

View File

@@ -13,22 +13,23 @@ import { useObservable } from "react-use";
import userMetadata from "../../services/user-metadata"; import userMetadata from "../../services/user-metadata";
export const UserView = () => { export const UserView = () => {
const params = useParams(); const { pubkey } = useParams();
if (!pubkey) {
if (!params.pubkey) {
// TODO: better 404 // TODO: better 404
throw new Error("No pubkey"); throw new Error("No pubkey");
} }
const observable = useMemo( const observable = useMemo(
() => userMetadata.requestUserMetadata(params.pubkey), () => userMetadata.requestUserMetadata(pubkey),
[params.pubkey] [pubkey]
); );
// @ts-ignore
const metadata = useObservable(observable); const metadata = useObservable(observable);
return ( return (
<> <>
<Heading>{metadata?.name ?? params.pubkey}</Heading> {/* @ts-ignore */}
<Heading>{metadata?.name ?? pubkey}</Heading>
<Tabs> <Tabs>
<TabList> <TabList>
<Tab>Posts</Tab> <Tab>Posts</Tab>
@@ -38,7 +39,7 @@ export const UserView = () => {
<TabPanels> <TabPanels>
<TabPanel> <TabPanel>
<UserPostsTab pubkey={params.pubkey} /> <UserPostsTab pubkey={pubkey} />
</TabPanel> </TabPanel>
<TabPanel> <TabPanel>
<p>two!</p> <p>two!</p>

View File

@@ -3,11 +3,12 @@ import { SkeletonText } from "@chakra-ui/react";
import settingsService from "../../services/settings"; import settingsService from "../../services/settings";
import { useSubscription } from "../../hooks/use-subscription"; import { useSubscription } from "../../hooks/use-subscription";
import { Post } from "../../components/post"; import { Post } from "../../components/post";
import { NostrEvent } from "../../types/nostr-event";
const relayUrls = await settingsService.getRelays(); const relayUrls = await settingsService.getRelays();
export const UserPostsTab = ({ pubkey }) => { export const UserPostsTab = ({ pubkey }: { pubkey: string }) => {
const [events, setEvents] = useState({}); const [events, setEvents] = useState<Record<string, NostrEvent>>({});
const sub = useSubscription( const sub = useSubscription(
relayUrls, relayUrls,
@@ -36,5 +37,11 @@ export const UserPostsTab = ({ pubkey }) => {
return <SkeletonText />; return <SkeletonText />;
} }
return timeline.map((event) => <Post key={event.id} event={event} />); return (
<>
{timeline.map((event) => (
<Post key={event.id} event={event} />
))}
</>
);
}; };

View File

@@ -1,41 +0,0 @@
// import React, { useEffect, useState } from "react";
// import { Card, CardBody, SkeletonText } from "@chakra-ui/react";
// import ReactMarkdown from "react-markdown";
// import { onEvent, subscribeToAuthor } from "../../services/relays";
// import { useSignal } from "../../hooks/use-signal";
// export const UserRelaysTab = ({ pubkey }) => {
// const [events, setEvents] = useState({});
// useEffect(() => {
// if (pubkey) {
// subscribeToAuthor(pubkey);
// }
// }, [pubkey]);
// useSignal(
// onEvent,
// (event) => {
// if (event.body.kind === 1) {
// setEvents((dir) => ({ [event.body.id]: event.body, ...dir }));
// }
// },
// [setEvents]
// );
// const timeline = Object.values(events).sort(
// (a, b) => a.created_at - b.created_at
// );
// if (timeline.length === 0) {
// return <SkeletonText />;
// }
// return timeline.map((event) => (
// <Card key={event.id}>
// <CardBody>
// <ReactMarkdown>{event.content}</ReactMarkdown>
// </CardBody>
// </Card>
// ));
// };

1
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

21
tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

9
tsconfig.node.json Normal file
View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1,16 +0,0 @@
import { VitePWA } from "vite-plugin-pwa";
import { defineConfig } from "vite";
export default defineConfig({
build: {
target: ["chrome89", "edge89", "firefox89", "safari15"],
},
// plugins: [
// VitePWA({
// registerType: "autoUpdate",
// devOptions: {
// enabled: true,
// },
// }),
// ],
});

10
vite.config.ts Normal file
View File

@@ -0,0 +1,10 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig({
build: {
target: ["chrome89", "edge89", "firefox89", "safari15"],
},
plugins: [react()],
});

175
yarn.lock
View File

@@ -26,7 +26,7 @@
dependencies: dependencies:
"@babel/highlight" "^7.18.6" "@babel/highlight" "^7.18.6"
"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1": "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1", "@babel/compat-data@^7.20.5":
version "7.20.5" version "7.20.5"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.5.tgz#86f172690b093373a933223b4745deeb6049e733" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.5.tgz#86f172690b093373a933223b4745deeb6049e733"
integrity sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g== integrity sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==
@@ -52,6 +52,27 @@
json5 "^2.2.1" json5 "^2.2.1"
semver "^6.3.0" semver "^6.3.0"
"@babel/core@^7.20.5":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.7.tgz#37072f951bd4d28315445f66e0ec9f6ae0c8c35f"
integrity sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==
dependencies:
"@ampproject/remapping" "^2.1.0"
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.20.7"
"@babel/helper-compilation-targets" "^7.20.7"
"@babel/helper-module-transforms" "^7.20.7"
"@babel/helpers" "^7.20.7"
"@babel/parser" "^7.20.7"
"@babel/template" "^7.20.7"
"@babel/traverse" "^7.20.7"
"@babel/types" "^7.20.7"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.1"
semver "^6.3.0"
"@babel/generator@^7.20.5": "@babel/generator@^7.20.5":
version "7.20.5" version "7.20.5"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95"
@@ -61,6 +82,15 @@
"@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1" jsesc "^2.5.1"
"@babel/generator@^7.20.7":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.7.tgz#f8ef57c8242665c5929fe2e8d82ba75460187b4a"
integrity sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==
dependencies:
"@babel/types" "^7.20.7"
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
"@babel/helper-annotate-as-pure@^7.18.6": "@babel/helper-annotate-as-pure@^7.18.6":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
@@ -86,6 +116,17 @@
browserslist "^4.21.3" browserslist "^4.21.3"
semver "^6.3.0" semver "^6.3.0"
"@babel/helper-compilation-targets@^7.20.7":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb"
integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==
dependencies:
"@babel/compat-data" "^7.20.5"
"@babel/helper-validator-option" "^7.18.6"
browserslist "^4.21.3"
lru-cache "^5.1.1"
semver "^6.3.0"
"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.5": "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.5":
version "7.20.5" version "7.20.5"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz#327154eedfb12e977baa4ecc72e5806720a85a06" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz#327154eedfb12e977baa4ecc72e5806720a85a06"
@@ -174,6 +215,20 @@
"@babel/traverse" "^7.20.1" "@babel/traverse" "^7.20.1"
"@babel/types" "^7.20.2" "@babel/types" "^7.20.2"
"@babel/helper-module-transforms@^7.20.7":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.7.tgz#7a6c9a1155bef55e914af574153069c9d9470c43"
integrity sha512-FNdu7r67fqMUSVuQpFQGE6BPdhJIhitoxhGzDbAXNcA07uoVG37fOiMk3OSV8rEICuyG6t8LGkd9EE64qIEoIA==
dependencies:
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-module-imports" "^7.18.6"
"@babel/helper-simple-access" "^7.20.2"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/helper-validator-identifier" "^7.19.1"
"@babel/template" "^7.20.7"
"@babel/traverse" "^7.20.7"
"@babel/types" "^7.20.7"
"@babel/helper-optimise-call-expression@^7.18.6": "@babel/helper-optimise-call-expression@^7.18.6":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe"
@@ -262,6 +317,15 @@
"@babel/traverse" "^7.20.5" "@babel/traverse" "^7.20.5"
"@babel/types" "^7.20.5" "@babel/types" "^7.20.5"
"@babel/helpers@^7.20.7":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.7.tgz#04502ff0feecc9f20ecfaad120a18f011a8e6dce"
integrity sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==
dependencies:
"@babel/template" "^7.20.7"
"@babel/traverse" "^7.20.7"
"@babel/types" "^7.20.7"
"@babel/highlight@^7.18.6": "@babel/highlight@^7.18.6":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
@@ -276,6 +340,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8"
integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA== integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==
"@babel/parser@^7.20.7":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.7.tgz#66fe23b3c8569220817d5feb8b9dcdc95bb4f71b"
integrity sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6" version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
@@ -717,6 +786,20 @@
dependencies: dependencies:
"@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-transform-react-jsx-self@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz#3849401bab7ae8ffa1e3e5687c94a753fc75bda7"
integrity sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-transform-react-jsx-source@^7.19.6":
version "7.19.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.19.6.tgz#88578ae8331e5887e8ce28e4c9dc83fb29da0b86"
integrity sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==
dependencies:
"@babel/helper-plugin-utils" "^7.19.0"
"@babel/plugin-transform-regenerator@^7.18.6": "@babel/plugin-transform-regenerator@^7.18.6":
version "7.20.5" version "7.20.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz#57cda588c7ffb7f4f8483cc83bdcea02a907f04d" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz#57cda588c7ffb7f4f8483cc83bdcea02a907f04d"
@@ -891,6 +974,15 @@
"@babel/parser" "^7.18.10" "@babel/parser" "^7.18.10"
"@babel/types" "^7.18.10" "@babel/types" "^7.18.10"
"@babel/template@^7.20.7":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8"
integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/parser" "^7.20.7"
"@babel/types" "^7.20.7"
"@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.20.5": "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.20.5":
version "7.20.5" version "7.20.5"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.5.tgz#78eb244bea8270fdda1ef9af22a5d5e5b7e57133" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.5.tgz#78eb244bea8270fdda1ef9af22a5d5e5b7e57133"
@@ -907,6 +999,22 @@
debug "^4.1.0" debug "^4.1.0"
globals "^11.1.0" globals "^11.1.0"
"@babel/traverse@^7.20.7":
version "7.20.8"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.8.tgz#e3a23eb04af24f8bbe8a8ba3eef6155b77df0b08"
integrity sha512-/RNkaYDeCy4MjyV70+QkSHhxbvj2JO/5Ft2Pa880qJOG8tWrqcT/wXUuCCv43yogfqPzHL77Xu101KQPf4clnQ==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.20.7"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.19.0"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/parser" "^7.20.7"
"@babel/types" "^7.20.7"
debug "^4.1.0"
globals "^11.1.0"
"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.4.4": "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.4.4":
version "7.20.5" version "7.20.5"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84"
@@ -916,6 +1024,15 @@
"@babel/helper-validator-identifier" "^7.19.1" "@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@babel/types@^7.20.7":
version "7.20.7"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f"
integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==
dependencies:
"@babel/helper-string-parser" "^7.19.4"
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
"@chakra-ui/accordion@2.1.4": "@chakra-ui/accordion@2.1.4":
version "2.1.4" version "2.1.4"
resolved "https://registry.yarnpkg.com/@chakra-ui/accordion/-/accordion-2.1.4.tgz#a3eca38f8e52d5a5f4b9528fb9d269dcdcb035ac" resolved "https://registry.yarnpkg.com/@chakra-ui/accordion/-/accordion-2.1.4.tgz#a3eca38f8e52d5a5f4b9528fb9d269dcdcb035ac"
@@ -2158,11 +2275,27 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/prop-types@^15.0.0": "@types/prop-types@*", "@types/prop-types@^15.0.0":
version "15.7.5" version "15.7.5"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
"@types/react-dom@^18.0.9":
version "18.0.9"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.9.tgz#ffee5e4bfc2a2f8774b15496474f8e7fe8d0b504"
integrity sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18.0.26":
version "18.0.26"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.26.tgz#8ad59fc01fef8eaf5c74f4ea392621749f0b7917"
integrity sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/resolve@1.17.1": "@types/resolve@1.17.1":
version "1.17.1" version "1.17.1"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
@@ -2170,6 +2303,11 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/scheduler@*":
version "0.16.2"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
"@types/trusted-types@^2.0.2": "@types/trusted-types@^2.0.2":
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
@@ -2180,6 +2318,17 @@
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
"@vitejs/plugin-react@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-3.0.0.tgz#f36ee1b2ce958dd11ac63fdf746a3b27b0d258ed"
integrity sha512-1mvyPc0xYW5G8CHQvJIJXLoMjl5Ct3q2g5Y2s6Ccfgwm45y48LBvsla7az+GkkAtYikWQ4Lxqcsq5RHLcZgtNQ==
dependencies:
"@babel/core" "^7.20.5"
"@babel/plugin-transform-react-jsx-self" "^7.18.6"
"@babel/plugin-transform-react-jsx-source" "^7.19.6"
magic-string "^0.27.0"
react-refresh "^0.14.0"
"@xobotyi/scrollbar-width@^1.9.5": "@xobotyi/scrollbar-width@^1.9.5":
version "1.9.5" version "1.9.5"
resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d"
@@ -3222,6 +3371,13 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
dependencies: dependencies:
js-tokens "^3.0.0 || ^4.0.0" js-tokens "^3.0.0 || ^4.0.0"
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
dependencies:
yallist "^3.0.2"
magic-string@^0.25.0, magic-string@^0.25.7: magic-string@^0.25.0, magic-string@^0.25.7:
version "0.25.9" version "0.25.9"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
@@ -3758,6 +3914,11 @@ react-markdown@^8.0.4:
unist-util-visit "^4.0.0" unist-util-visit "^4.0.0"
vfile "^5.0.0" vfile "^5.0.0"
react-refresh@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==
react-remove-scroll-bar@^2.3.3: react-remove-scroll-bar@^2.3.3:
version "2.3.4" version "2.3.4"
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz#53e272d7a5cb8242990c7f144c44d8bd8ab5afd9" resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz#53e272d7a5cb8242990c7f144c44d8bd8ab5afd9"
@@ -4282,6 +4443,11 @@ type-fest@^0.16.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860"
integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg== integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==
typescript@^4.9.4:
version "4.9.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78"
integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==
unbox-primitive@^1.0.2: unbox-primitive@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
@@ -4664,6 +4830,11 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yaml@^1.10.0: yaml@^1.10.0:
version "1.10.2" version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"