-
-
-
+ {status === "loading" ? (
+
+ ) : (
+
+
+
+
+
+
+
+ Reply as
+
+
+ {user?.nip05 || user?.name}
+
+
-
-
- Reply as
-
-
- {user?.nip05 || user?.name}
-
+
+
-
-
-
-
+ )}
);
diff --git a/src/shared/notes/replies/list.tsx b/src/shared/notes/replies/list.tsx
index c39bd5bb..807f18ef 100644
--- a/src/shared/notes/replies/list.tsx
+++ b/src/shared/notes/replies/list.tsx
@@ -2,12 +2,12 @@ import { getReplies } from "@libs/storage";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { EmptyIcon } from "@shared/icons";
import { Reply } from "@shared/notes/replies/item";
-import useSWR from "swr";
-
-const fetcher = ([, id]) => getReplies(id);
+import { useQuery } from "@tanstack/react-query";
export function RepliesList({ parent_id }: { parent_id: string }) {
- const { data }: any = useSWR(["note-replies", parent_id], fetcher);
+ const { data } = useQuery(["replies", parent_id], async () => {
+ return await getReplies(parent_id);
+ });
return (
diff --git a/src/shared/notes/repost.tsx b/src/shared/notes/repost.tsx
index 6ed29b79..e60b7981 100644
--- a/src/shared/notes/repost.tsx
+++ b/src/shared/notes/repost.tsx
@@ -4,7 +4,6 @@ import { NoteMetadata } from "@shared/notes/metadata";
import { NoteSkeleton } from "@shared/notes/skeleton";
import { User } from "@shared/user";
import { useEvent } from "@utils/hooks/useEvent";
-import { parser } from "@utils/parser";
import { getRepostID } from "@utils/transform";
import { LumeEvent } from "@utils/types";
@@ -13,14 +12,16 @@ export function Repost({
currentBlock,
}: { event: LumeEvent; currentBlock?: number }) {
const repostID = getRepostID(event.tags);
- const data = useEvent(repostID);
+ const { status, data, isFetching } = useEvent(repostID);
- const kind1 = data?.kind === 1 ? parser(data) : null;
+ const kind1 = data?.kind === 1 ? data.content : null;
const kind1063 = data?.kind === 1063 ? data.tags : null;
return (
- {data ? (
+ {isFetching || status === "loading" ? (
+
+ ) : (
<>
@@ -48,8 +49,6 @@ export function Repost({
/>
>
- ) : (
-
)}
);
diff --git a/src/shared/protected.tsx b/src/shared/protected.tsx
new file mode 100644
index 00000000..79aff269
--- /dev/null
+++ b/src/shared/protected.tsx
@@ -0,0 +1,13 @@
+import { useAccount } from "@utils/hooks/useAccount";
+import { ReactNode } from "react";
+import { Navigate } from "react-router-dom";
+
+export function Protected({ children }: { children: ReactNode }) {
+ const { status, account } = useAccount();
+
+ if (status === "success" && !account) {
+ return
;
+ }
+
+ return children;
+}
diff --git a/src/shared/relayProvider.tsx b/src/shared/relayProvider.tsx
index 1c56ef29..7b635eb1 100644
--- a/src/shared/relayProvider.tsx
+++ b/src/shared/relayProvider.tsx
@@ -1,9 +1,12 @@
import { initNDK } from "@libs/ndk";
import NDK from "@nostr-dev-kit/ndk";
+import { FULL_RELAYS } from "@stores/constants";
import { createContext } from "react";
export const RelayContext = createContext
(null);
-const ndk = await initNDK();
+
+const ndk = new NDK({ explicitRelayUrls: FULL_RELAYS });
+await ndk.connect();
export function RelayProvider({ children }: { children: React.ReactNode }) {
return {children};
diff --git a/src/shared/user.tsx b/src/shared/user.tsx
index 30e37739..c82747d9 100644
--- a/src/shared/user.tsx
+++ b/src/shared/user.tsx
@@ -1,12 +1,12 @@
import { Popover, Transition } from "@headlessui/react";
import { Image } from "@shared/image";
-import { Link } from "@shared/link";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { Fragment } from "react";
+import { Link } from "react-router-dom";
dayjs.extend(relativeTime);
@@ -96,13 +96,13 @@ export function User({
View profile
Message
diff --git a/src/stores/constants.tsx b/src/stores/constants.tsx
index d6b4c69d..f794201e 100644
--- a/src/stores/constants.tsx
+++ b/src/stores/constants.tsx
@@ -7,6 +7,5 @@ export const OPENGRAPH_KEY = "9EJG4SY-19Q4M5J-H8R29C9-091XPCC";
export const FULL_RELAYS = [
"wss://relay.damus.io",
"wss://relay.nostr.band/all",
- "wss://relay.nostrich.land",
"wss://relay.nostrgraph.net",
];
diff --git a/src/stores/onboarding.tsx b/src/stores/onboarding.tsx
new file mode 100644
index 00000000..e5334a34
--- /dev/null
+++ b/src/stores/onboarding.tsx
@@ -0,0 +1,17 @@
+import { create } from "zustand";
+import { createJSONStorage, persist } from "zustand/middleware";
+
+export const useOnboarding = create(
+ persist(
+ (set) => ({
+ profile: {},
+ createProfile: (data) => {
+ set({ profile: data });
+ },
+ }),
+ {
+ name: "onboarding",
+ storage: createJSONStorage(() => sessionStorage),
+ },
+ ),
+);
diff --git a/src/utils/hooks/useAccount.tsx b/src/utils/hooks/useAccount.tsx
new file mode 100644
index 00000000..00dda840
--- /dev/null
+++ b/src/utils/hooks/useAccount.tsx
@@ -0,0 +1,11 @@
+import { getActiveAccount } from "@libs/storage";
+import { useQuery } from "@tanstack/react-query";
+
+export function useAccount() {
+ const { status, data: account } = useQuery(["currentAccount"], async () => {
+ const res = await getActiveAccount();
+ return res;
+ });
+
+ return { status, account };
+}
diff --git a/src/utils/hooks/useEvent.tsx b/src/utils/hooks/useEvent.tsx
index db15bbf5..adb76149 100644
--- a/src/utils/hooks/useEvent.tsx
+++ b/src/utils/hooks/useEvent.tsx
@@ -1,31 +1,40 @@
import { createNote, getNoteByID } from "@libs/storage";
import { RelayContext } from "@shared/relayProvider";
+import { useQuery } from "@tanstack/react-query";
+import { parser } from "@utils/parser";
import { useContext } from "react";
-import useSWR from "swr";
-
-const fetcher = async ([, ndk, id]) => {
- const result = await getNoteByID(id);
-
- if (result) {
- return result;
- } else {
- const event = await ndk.fetchEvent(id);
- await createNote(
- event.id,
- event.pubkey,
- event.kind,
- event.tags,
- event.content,
- event.created_at,
- );
- event["event_id"] = event.id;
- return event;
- }
-};
export function useEvent(id: string) {
const ndk = useContext(RelayContext);
- const { data } = useSWR(["note", ndk, id], fetcher);
+ const { status, data, error, isFetching } = useQuery(
+ ["note", id],
+ async () => {
+ const result = await getNoteByID(id);
+ if (result) {
+ result["content"] = parser(result);
+ return result;
+ } else {
+ const event = await ndk.fetchEvent(id);
+ await createNote(
+ event.id,
+ event.pubkey,
+ event.kind,
+ event.tags,
+ event.content,
+ event.created_at,
+ );
+ event["event_id"] = event.id;
+ // @ts-ignore
+ event["content"] = parser(event);
+ return event;
+ }
+ },
+ {
+ refetchOnWindowFocus: false,
+ refetchOnMount: false,
+ refetchOnReconnect: false,
+ },
+ );
- return data;
+ return { status, data, error, isFetching };
}
diff --git a/src/utils/hooks/useOpenGraph.tsx b/src/utils/hooks/useOpenGraph.tsx
index 932b1252..9b2750fa 100644
--- a/src/utils/hooks/useOpenGraph.tsx
+++ b/src/utils/hooks/useOpenGraph.tsx
@@ -1,28 +1,49 @@
import { OPENGRAPH_KEY } from "@stores/constants";
+import { useQuery } from "@tanstack/react-query";
import { fetch } from "@tauri-apps/api/http";
-import useSWR from "swr";
-
-const fetcher = async (url: string) => {
- const result = await fetch(url, {
- method: "GET",
- timeout: 20,
- });
- if (result.ok) {
- return result.data;
- } else {
- return null;
- }
-};
export function useOpenGraph(url: string) {
- const { data, error, isLoading } = useSWR(
- `https://skrape.dev/api/opengraph/?url=${url}&key=${OPENGRAPH_KEY}`,
- fetcher,
+ const { status, data, error, isFetching } = useQuery(
+ ["preview", url],
+ async () => {
+ const result = await fetch(
+ `https://skrape.dev/api/opengraph/?url=${url}&key=${OPENGRAPH_KEY}`,
+ {
+ method: "GET",
+ timeout: 10,
+ },
+ );
+ if (result.ok) {
+ if (Object.keys(result.data).length === 0) {
+ const origin = new URL(url).origin;
+ const result = await fetch(
+ `https://skrape.dev/api/opengraph/?url=${origin}&key=${OPENGRAPH_KEY}`,
+ {
+ method: "GET",
+ timeout: 10,
+ },
+ );
+ if (result.ok) {
+ return result.data;
+ }
+ } else {
+ return result.data;
+ }
+ } else {
+ return null;
+ }
+ },
+ {
+ refetchOnWindowFocus: false,
+ refetchOnMount: false,
+ refetchOnReconnect: false,
+ },
);
return {
- data: data,
- error: error,
- isLoading: isLoading,
+ status,
+ data,
+ error,
+ isFetching,
};
}
diff --git a/src/utils/hooks/useProfile.tsx b/src/utils/hooks/useProfile.tsx
index 1622a727..e62539b8 100644
--- a/src/utils/hooks/useProfile.tsx
+++ b/src/utils/hooks/useProfile.tsx
@@ -1,44 +1,38 @@
import { createPleb, getPleb } from "@libs/storage";
-import NDK from "@nostr-dev-kit/ndk";
import { RelayContext } from "@shared/relayProvider";
+import { useQuery } from "@tanstack/react-query";
import { nip19 } from "nostr-tools";
import { useContext } from "react";
-import useSWR from "swr";
-const fetcher = async ([, ndk, key]) => {
- let npub: string;
-
- if (key.substring(0, 4) === "npub") {
- npub = key;
- } else {
- npub = nip19.npubEncode(key);
- }
-
- const current = Math.floor(Date.now() / 1000);
- const result = await getPleb(npub);
-
- if (result && result.created_at + 86400 > current) {
- return result;
- } else {
- const user = ndk.getUser({ npub });
- await user.fetchProfile();
- await createPleb(key, user.profile);
-
- return user.profile;
- }
-};
-
-export function useProfile(key: string) {
+export function useProfile(id: string) {
const ndk = useContext(RelayContext);
- const { data, error, isLoading } = useSWR(["profile", ndk, key], fetcher, {
- revalidateIfStale: false,
- revalidateOnFocus: false,
- revalidateOnReconnect: false,
+ const {
+ status,
+ data: user,
+ error,
+ isFetching,
+ } = useQuery(["user", id], async () => {
+ let npub: string;
+
+ if (id.substring(0, 4) === "npub") {
+ npub = id;
+ } else {
+ npub = nip19.npubEncode(id);
+ }
+
+ const current = Math.floor(Date.now() / 1000);
+ const result = await getPleb(npub);
+
+ if (result && result.created_at + 86400 > current) {
+ return result;
+ } else {
+ const user = ndk.getUser({ npub });
+ await user.fetchProfile();
+ await createPleb(id, user.profile);
+
+ return user.profile;
+ }
});
- return {
- user: data,
- isLoading,
- isError: error,
- };
+ return { status, user, error, isFetching };
}
diff --git a/src/utils/parser.tsx b/src/utils/parser.tsx
index 7fbbe163..07cdbb7a 100644
--- a/src/utils/parser.tsx
+++ b/src/utils/parser.tsx
@@ -1,8 +1,8 @@
-import { Link } from "@shared/link";
import { MentionUser } from "@shared/notes/mentions/user";
import destr from "destr";
import getUrls from "get-urls";
import { parseReferences } from "nostr-tools";
+import { Link } from "react-router-dom";
import reactStringReplace from "react-string-replace";
function isJsonString(str) {
@@ -50,17 +50,33 @@ export function parser(event: any) {
// image url
content.images.push(url);
// remove url from original content
- content.parsed = content.parsed.replace(url, "");
+ content.parsed = reactStringReplace(content.parsed, url, () => null);
} else if (url.match(/\.(mp4|webm|mov|ogv|avi|mp3)$/)) {
// video
content.videos.push(url);
// remove url from original content
- content.parsed = content.parsed.replace(url, "");
+ content.parsed = reactStringReplace(content.parsed, url, () => null);
} else {
- // push to store
- content.links.push(url);
- // remove url from original content
- content.parsed = content.parsed.replace(url, "");
+ if (content.links.length < 1) {
+ // push to store
+ content.links.push(url);
+ // remove url from original content
+ content.parsed = reactStringReplace(content.parsed, url, () => null);
+ } else {
+ content.parsed = reactStringReplace(
+ content.parsed,
+ /#(\w+)/g,
+ (match, i) => (
+
+ {match}
+
+ ),
+ );
+ }
}
});
@@ -70,7 +86,11 @@ export function parser(event: any) {
const event = item.event;
if (event) {
content.notes.push(event.id);
- content.parsed = content.parsed.replace(item.text, "");
+ content.parsed = reactStringReplace(
+ content.parsed,
+ item.text,
+ () => null,
+ );
}
if (profile) {
content.parsed = reactStringReplace(
@@ -85,7 +105,7 @@ export function parser(event: any) {
content.parsed = reactStringReplace(content.parsed, /#(\w+)/g, (match, i) => (
#{match}
diff --git a/tailwind.config.js b/tailwind.config.js
index 579efa5a..650c71e8 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,57 +1,10 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
- content: ['./src/**/*.{js,ts,jsx,tsx}'],
- darkMode: 'class',
- theme: {
- extend: {
- boxShadow: {
- input: `
- 0px 1px 0px -1px var(--tw-shadow-color),
- 0px 1px 1px -1px var(--tw-shadow-color),
- 0px 1px 2px -1px var(--tw-shadow-color),
- 0px 2px 4px -2px var(--tw-shadow-color),
- 0px 3px 6px -3px var(--tw-shadow-color)
- `,
- highlight: `
- inset 0px 0px 0px 1px var(--tw-shadow-color),
- inset 0px 1px 0px var(--tw-shadow-color)
- `,
- popover: `0px 0px 7px rgba(0,0,0,0.52)`,
- inner: `
- 0 2px 2px rgb(4 4 7 / 45%),
- 0 8px 24px rgb(4 4 7 / 60%)
- `,
- button: `
- rgba(74, 4, 78, 0.5) 0px 2px 8px,
- rgb(74, 4, 78) 0px 2px 4px,
- rgb(74, 4, 78) 0px 0px 0px 1px,
- rgba(255, 255, 255, 0.2) 0px 0px 0px 1px inset
- `,
- 'mini-button': `
- rgba(13, 16, 23, 0.36) 0px 2px 8px,
- rgba(13, 16, 23, 0.36) 0px 2px 4px,
- rgba(13, 16, 23, 0.36) 0px 0px 0px 1px,
- rgba(255, 255, 255, 0.1) 0px 0px 0px 1px inset
- `,
- },
- backgroundImage: {
- fade: 'linear-gradient(120deg, #000, transparent 30%, transparent 70%, #000)',
- },
- keyframes: {
- moveBg: {
- '0%': { backgroundPosition: '50px' },
- '20%': { backgroundPosition: '150px' },
- '40%': { backgroundPosition: '250px' },
- '60%': { backgroundPosition: '350px' },
- '80%': { backgroundPosition: '450px' },
- '100%': { backgroundPosition: '550px' },
- },
- },
- animation: {
- moveBg: 'moveBg 3s ease-in-out infinite alternate running forwards',
- },
- },
- },
- plugins: [require('@tailwindcss/typography')],
+ content: ["./src/**/*.{js,ts,jsx,tsx}", "index.html"],
+ darkMode: "class",
+ theme: {
+ extend: {},
+ },
+ plugins: [require("@tailwindcss/typography")],
};
diff --git a/tsconfig.json b/tsconfig.json
index f9b77aac..0ce1e749 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -25,6 +25,6 @@
"jsx": "preserve",
"strictNullChecks": false
},
- "include": ["global.d.ts", "**/*.ts", "**/*.tsx"],
+ "include": ["global.d.ts", "**/*.ts", "**/*.tsx", "src/main.tsx"],
"exclude": ["node_modules", "dist", "src-tauri"]
}
diff --git a/vite.config.ts b/vite.config.ts
index 218e854a..4d0cf738 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,30 +1,26 @@
import react from "@vitejs/plugin-react-swc";
import { defineConfig } from "vite";
-import ssr from "vite-plugin-ssr/plugin";
import topLevelAwait from "vite-plugin-top-level-await";
import viteTsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
- clearScreen: false,
- server: {
- strictPort: true,
- },
- envPrefix: ["VITE_", "TAURI_"],
- build: {
- // Tauri uses Chromium on Windows and WebKit on macOS and Linux
- target: process.env.TAURI_PLATFORM === "windows" ? "chrome105" : "safari13",
- // don't minify for debug builds
- minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
- // produce sourcemaps for debug builds
- sourcemap: !!process.env.TAURI_DEBUG,
- },
plugins: [
react(),
- ssr({ prerender: true }),
viteTsconfigPaths(),
topLevelAwait({
promiseExportName: "__tla",
promiseImportName: (i) => `__tla_${i}`,
}),
],
+ envPrefix: ["VITE_", "TAURI_"],
+ build: {
+ target: process.env.TAURI_PLATFORM === "windows" ? "chrome105" : "safari13",
+ minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
+ sourcemap: !!process.env.TAURI_DEBUG,
+ },
+ server: {
+ strictPort: true,
+ port: 3000,
+ },
+ clearScreen: false,
});