diff --git a/.changeset/brown-elephants-fail.md b/.changeset/brown-elephants-fail.md new file mode 100644 index 000000000..3d1ba5e62 --- /dev/null +++ b/.changeset/brown-elephants-fail.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Show notes in relay view diff --git a/.changeset/fair-roses-lick.md b/.changeset/fair-roses-lick.md new file mode 100644 index 000000000..cb9c30d61 --- /dev/null +++ b/.changeset/fair-roses-lick.md @@ -0,0 +1,5 @@ +--- +"nostrudel": patch +--- + +Fix non-english characters breaking links diff --git a/.changeset/fresh-starfishes-scream.md b/.changeset/fresh-starfishes-scream.md new file mode 100644 index 000000000..6f647c9b9 --- /dev/null +++ b/.changeset/fresh-starfishes-scream.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Improve layout of image galleries diff --git a/.changeset/lovely-plants-leave.md b/.changeset/lovely-plants-leave.md new file mode 100644 index 000000000..0bd7707d6 --- /dev/null +++ b/.changeset/lovely-plants-leave.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Show all images in lightbox diff --git a/.changeset/serious-ligers-boil.md b/.changeset/serious-ligers-boil.md new file mode 100644 index 000000000..1945f75df --- /dev/null +++ b/.changeset/serious-ligers-boil.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Rebuild event publish details diff --git a/.changeset/silver-phones-develop.md b/.changeset/silver-phones-develop.md new file mode 100644 index 000000000..5ffea34af --- /dev/null +++ b/.changeset/silver-phones-develop.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Render multiple images as image gallery diff --git a/.changeset/six-games-beg.md b/.changeset/six-games-beg.md new file mode 100644 index 000000000..b2f384f5e --- /dev/null +++ b/.changeset/six-games-beg.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Add relay review form diff --git a/.changeset/sixty-planes-sin.md b/.changeset/sixty-planes-sin.md new file mode 100644 index 000000000..27358efa9 --- /dev/null +++ b/.changeset/sixty-planes-sin.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Add simple timeline health view diff --git a/.changeset/smooth-kings-retire.md b/.changeset/smooth-kings-retire.md new file mode 100644 index 000000000..3f6b77d19 --- /dev/null +++ b/.changeset/smooth-kings-retire.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Clean up navigation menu diff --git a/.changeset/thin-games-appear.md b/.changeset/thin-games-appear.md new file mode 100644 index 000000000..aa93bf41a --- /dev/null +++ b/.changeset/thin-games-appear.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Add mobile friendly lightbox diff --git a/.changeset/three-chairs-mix.md b/.changeset/three-chairs-mix.md new file mode 100644 index 000000000..5a95d3b8e --- /dev/null +++ b/.changeset/three-chairs-mix.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Show label for paid relays diff --git a/.changeset/tricky-hounds-double.md b/.changeset/tricky-hounds-double.md new file mode 100644 index 000000000..089943993 --- /dev/null +++ b/.changeset/tricky-hounds-double.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Add inline reply form diff --git a/.changeset/unlucky-melons-divide.md b/.changeset/unlucky-melons-divide.md new file mode 100644 index 000000000..a714cf3ad --- /dev/null +++ b/.changeset/unlucky-melons-divide.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Add and options to CORS proxy url diff --git a/.changeset/violet-walls-dance.md b/.changeset/violet-walls-dance.md new file mode 100644 index 000000000..c676e7883 --- /dev/null +++ b/.changeset/violet-walls-dance.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Use corsproxy.io as default service for CORS proxy diff --git a/README.md b/README.md index ceaca5d97..9799f1cf3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # noStrudel -> NOTE: This client is still in development and is buggy +> NOTE: This client is still in development and will have bugs ## noStrudel is my personal nostr client. @@ -10,7 +10,7 @@ There are many features missing from this client and I wont get around to implem Live Instance: [nostrudel.ninja](https://nostrudel.ninja) -You can find better clients with more features in the [awesome-nostr](https://github.com/aljazceru/awesome-nostr) repo. +You can find better clients with more features on [nostrapps.com](https://www.nostrapps.com/) or in the [awesome-nostr](https://github.com/aljazceru/awesome-nostr) repo. ## Please don't trust my app with your nsec @@ -30,7 +30,7 @@ docker run --rm -p 8080:80 ghcr.io/hzrd149/nostrudel git clone git@github.com:hzrd149/nostrudel.git cd nostrudel yarn install -yarn start +yarn dev ``` ## Contributing diff --git a/cypress/e2e/embeds.cy.ts b/cypress/e2e/embeds.cy.ts index 054d29f4f..2116fa165 100644 --- a/cypress/e2e/embeds.cy.ts +++ b/cypress/e2e/embeds.cy.ts @@ -2,7 +2,7 @@ describe("Embeds", () => { describe("hashtags", () => { it('should handle uppercase hashtags and ","', () => { cy.visit( - "#/n/nevent1qqsrj5ns6wva3fcghlyx0hp7hhajqtqk3kuckp7xhhscrm4jl7futegpz9mhxue69uhkummnw3e82efwvdhk6qgswaehxw309ahx7um5wgh8w6twv5pkpt8l" + "#/n/nevent1qqsrj5ns6wva3fcghlyx0hp7hhajqtqk3kuckp7xhhscrm4jl7futegpz9mhxue69uhkummnw3e82efwvdhk6qgswaehxw309ahx7um5wgh8w6twv5pkpt8l", ); cy.findByRole("link", { name: "#Japan" }).should("be.visible"); @@ -15,18 +15,18 @@ describe("Embeds", () => { describe("links", () => { it("embed trustless.computer links", () => { cy.visit( - "#/n/nevent1qqsfn2mv3pe2v7jak4r5wnyengt36t0rx26w04hgysrmtpml8jnlk5cprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2qgawaehxw309ahx7um5wgkhqatz9emk2mrvdaexgetj9ehx2aq2wry06" + "#/n/nevent1qqsfn2mv3pe2v7jak4r5wnyengt36t0rx26w04hgysrmtpml8jnlk5cprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2qgawaehxw309ahx7um5wgkhqatz9emk2mrvdaexgetj9ehx2aq2wry06", ); cy.get('[href="https://trustless.computer/"]').should("be.visible"); cy.get( - '[href="https://mempool.space/tx/461c6f56015c94d74837b68c9d08f4b80e7db7ca1e5ac4c53d9aa8c76b667672"]' + '[href="https://mempool.space/tx/461c6f56015c94d74837b68c9d08f4b80e7db7ca1e5ac4c53d9aa8c76b667672"]', ).should("be.visible"); }); it("embeds links", () => { cy.visit( - "#/n/nevent1qqsvg6kt4hl79qpp5p673g7ref6r0c5jvp4yys7mmvs4m50t30sy9dgpz9mhxue69uhkummnw3e82efwvdhk6qgjwaehxw309aex2mrp0yhxvdm69e5k7r3xlpe" + "#/n/nevent1qqsvg6kt4hl79qpp5p673g7ref6r0c5jvp4yys7mmvs4m50t30sy9dgpz9mhxue69uhkummnw3e82efwvdhk6qgjwaehxw309aex2mrp0yhxvdm69e5k7r3xlpe", ); cy.get('[href="https://getalby.com/"]').should("exist"); @@ -38,11 +38,11 @@ describe("Embeds", () => { it("embeds simplex.chat links", () => { cy.visit( - "#/n/nevent1qqsymds0vlpp4f5s0dckjf4qz283pdsen0rmx8lu7ct6hpnxag2hpacpremhxue69uhkummnw3ez6un9d3shjtnwda4k7arpwfhjucm0d5q3qamnwvaz7tmwdaehgu3wwa5kueghxyq76" + "#/n/nevent1qqsymds0vlpp4f5s0dckjf4qz283pdsen0rmx8lu7ct6hpnxag2hpacpremhxue69uhkummnw3ez6un9d3shjtnwda4k7arpwfhjucm0d5q3qamnwvaz7tmwdaehgu3wwa5kueghxyq76", ); cy.get( - '[href="https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2F0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU%3D%40smp8.simplex.im%2FVlHiRmia02CDgga7w-uNb2FQZTZsj3UR%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAd2GEWU9Zjrljhw8O4FldcxrqehkDWezXl-cWD-VkeEw%253D%26srv%3Dbeccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion"]' + '[href="https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2F0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU%3D%40smp8.simplex.im%2FVlHiRmia02CDgga7w-uNb2FQZTZsj3UR%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAd2GEWU9Zjrljhw8O4FldcxrqehkDWezXl-cWD-VkeEw%253D%26srv%3Dbeccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion"]', ).should("be.visible"); }); }); @@ -50,7 +50,7 @@ describe("Embeds", () => { describe("Nostr links", () => { it("should embed noub1...", () => { cy.visit( - "#/n/nevent1qqsd5yw7sntqfc4e7u4aempvgctry2plz653t9gpf97ctk5vc0ftskgpz3mhxue69uhhyetvv9ujuerpd46hxtnfduq3zamnwvaz7tmwdaehgun4v5hxxmmdfxdj3a" + "#/n/nevent1qqsd5yw7sntqfc4e7u4aempvgctry2plz653t9gpf97ctk5vc0ftskgpz3mhxue69uhhyetvv9ujuerpd46hxtnfduq3zamnwvaz7tmwdaehgun4v5hxxmmdfxdj3a", ); cy.contains("Alby team"); @@ -69,7 +69,7 @@ describe("Embeds", () => { describe("youtube", () => { it("should embed playlists", () => { cy.visit( - "#/n/nevent1qqs8w6e63smpr5ccmz4l0w5pvnkp6r7z2fxaadjwu2g74y95pl9xv0cpzpmhxue69uhkummnw3ezuamfdejszrthwden5te0dehhxtnvdakqqkgf54" + "#/n/nevent1qqs8w6e63smpr5ccmz4l0w5pvnkp6r7z2fxaadjwu2g74y95pl9xv0cpzpmhxue69uhkummnw3ezuamfdejszrthwden5te0dehhxtnvdakqqkgf54", ); cy.findByTitle(/youtube video player/i).should("be.visible"); @@ -80,31 +80,31 @@ describe("Embeds", () => { describe("Music", () => { it("should handle wavlake links", () => { cy.visit( - "#/n/nevent1qqsve4ud5v8gjds2f2h7exlmjvhqayu4s520pge7frpwe22wezny0pcpp4mhxue69uhkummn9ekx7mqprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2mxs3z0" + "#/n/nevent1qqsve4ud5v8gjds2f2h7exlmjvhqayu4s520pge7frpwe22wezny0pcpp4mhxue69uhkummn9ekx7mqprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2mxs3z0", ); cy.findByTitle("Wavlake Embed").should("be.visible"); }); it("should handle spotify links", () => { cy.visit( - "#/n/nevent1qqsx0lz7m72qzq499exwhnfszvgwea8tv38x9wkv32yhkmwwmhgs7jgprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk25m3sln" + "#/n/nevent1qqsx0lz7m72qzq499exwhnfszvgwea8tv38x9wkv32yhkmwwmhgs7jgprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk25m3sln", ); cy.findByTitle("Spotify List Embed").should("exist"); cy.visit( - "#/n/nevent1qqsqxkmz49hydf8ppa9k6x6zrcq7m4evhhlye0j3lcnz8hrl2q6np4spz3mhxue69uhhyetvv9ujuerpd46hxtnfdult02qz" + "#/n/nevent1qqsqxkmz49hydf8ppa9k6x6zrcq7m4evhhlye0j3lcnz8hrl2q6np4spz3mhxue69uhhyetvv9ujuerpd46hxtnfdult02qz", ); cy.findByTitle("Spotify Embed").should("exist"); }); it("should handle apple music links", () => { cy.visit( - "#/n/nevent1qqs9kqt9d7r4zjpawcyl82x5qsn4hals4wn294dv95knrahs4mggwasprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2whhzvz" + "#/n/nevent1qqs9kqt9d7r4zjpawcyl82x5qsn4hals4wn294dv95knrahs4mggwasprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2whhzvz", ); cy.findByTitle("Apple Music Embed").should("exist"); cy.visit( - "#/n/nevent1qqszyrz4uug75j4086kj4f8peg3g0v8g9f04zjxplnpq0uxljtthggqprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2aeexmq" + "#/n/nevent1qqszyrz4uug75j4086kj4f8peg3g0v8g9f04zjxplnpq0uxljtthggqprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2aeexmq", ); cy.findByTitle("Apple Music List Embed").should("exist"); }); @@ -118,7 +118,7 @@ describe("Embeds", () => { describe("Emoji", () => { it("should embed emojis", () => { cy.visit( - "#/n/nevent1qqsdj7k47uh4z0ypl2m29lvd4ar9zpf6dcy7ls0q6g6qctnxfj5n3pcpzpmhxue69uhkummnw3ezuamfdejszrthwden5te0dehhxtnvdakqdyqlpq" + "#/n/nevent1qqsdj7k47uh4z0ypl2m29lvd4ar9zpf6dcy7ls0q6g6qctnxfj5n3pcpzpmhxue69uhkummnw3ezuamfdejszrthwden5te0dehhxtnvdakqdyqlpq", ); cy.findByRole("img", { name: /pepeD/i }).should("be.visible"); diff --git a/cypress/e2e/login.cy.ts b/cypress/e2e/login.cy.ts index e068c957a..00a7770a5 100644 --- a/cypress/e2e/login.cy.ts +++ b/cypress/e2e/login.cy.ts @@ -50,7 +50,7 @@ describe("Login view", () => { it("should redirect after login", () => { cy.visit( - "#/n/nevent1qqs88gdxv36qsjfwr66k7wxuq9r2tg8rsdcnfkcqdg4sc6vlnsma98qpzpmhxue69uhkummnw3ezuamfdejsz9rhwden5te0wfjkccte9ejxzmt4wvhxjmccew89d" + "#/n/nevent1qqs88gdxv36qsjfwr66k7wxuq9r2tg8rsdcnfkcqdg4sc6vlnsma98qpzpmhxue69uhkummnw3ezuamfdejsz9rhwden5te0wfjkccte9ejxzmt4wvhxjmccew89d", ); cy.findByRole("link", { name: /login/i }).click(); diff --git a/cypress/e2e/profile.cy.ts b/cypress/e2e/profile.cy.ts index d175f099d..5f6d3ad38 100644 --- a/cypress/e2e/profile.cy.ts +++ b/cypress/e2e/profile.cy.ts @@ -1,7 +1,7 @@ describe("Profile view", () => { it("should load a rss feed profile", () => { cy.visit( - "#/u/nprofile1qqsp6hxqjatvxtesgszs8aee0fcjccxa3ef3mzjva4uv2yr5lucp6jcpzemhxue69uhhyumnd3shjtnwdaehgu3wd4hk2s8c5un" + "#/u/nprofile1qqsp6hxqjatvxtesgszs8aee0fcjccxa3ef3mzjva4uv2yr5lucp6jcpzemhxue69uhhyumnd3shjtnwdaehgu3wd4hk2s8c5un", ); cy.contains("fjsmu"); diff --git a/cypress/e2e/public.cy.ts b/cypress/e2e/public.cy.ts index 8aeae9332..499d320b2 100644 --- a/cypress/e2e/public.cy.ts +++ b/cypress/e2e/public.cy.ts @@ -2,7 +2,7 @@ describe("No account", () => { describe("note view", () => { it("should fetch and render note", () => { cy.visit( - "#/n/nevent1qqs84hwdlls703w4yf66qsszxjqfc0xselfxrzr6n4qp40vzdnczragpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5jcwczn" + "#/n/nevent1qqs84hwdlls703w4yf66qsszxjqfc0xselfxrzr6n4qp40vzdnczragpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5jcwczn", ); cy.get(".chakra-card") diff --git a/cypress/e2e/thread.cy.ts b/cypress/e2e/thread.cy.ts index 313aca5de..0a1d37156 100644 --- a/cypress/e2e/thread.cy.ts +++ b/cypress/e2e/thread.cy.ts @@ -1,7 +1,7 @@ describe("Thread", () => { it("should handle quote notes with e tags correctly", () => { cy.visit( - "#/n/nevent1qqsx2lnyuke6vmsrz9fdrd6uwjy0g0e9l6menfgdj5truugkh9qmkkgpzpmhxue69uhkummnw3ezuamfdejszrthwden5te0dehhxtnvdakqgc9md6" + "#/n/nevent1qqsx2lnyuke6vmsrz9fdrd6uwjy0g0e9l6menfgdj5truugkh9qmkkgpzpmhxue69uhkummnw3ezuamfdejszrthwden5te0dehhxtnvdakqgc9md6", ); // find first note diff --git a/index.html b/index.html index f80fce05e..42965167e 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - + diff --git a/package.json b/package.json index 5341b9d17..5f279e356 100644 --- a/package.json +++ b/package.json @@ -5,22 +5,23 @@ "license": "MIT", "scripts": { "start": "vite serve", + "dev": "vite serve", "build": "tsc --project tsconfig.json && vite build", "format": "prettier --ignore-path .prettierignore -w .", "e2e": "cypress open", "test": "cypress run --e2e --browser=chrome" }, "dependencies": { - "@chakra-ui/icons": "^2.0.19", - "@chakra-ui/react": "^2.7.1", - "@emotion/react": "^11.11.0", + "@chakra-ui/icons": "^2.1.0", + "@chakra-ui/react": "^2.8.0", + "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "bech32": "^2.0.0", "cheerio": "^1.0.0-rc.12", "dayjs": "^1.11.9", "debug": "^4.3.4", - "framer-motion": "^7.10.3", - "hls.js": "^1.4.7", + "framer-motion": "^10.16.0", + "hls.js": "^1.4.10", "idb": "^7.1.1", "identicon.js": "^2.3.3", "leaflet": "^1.9.4", @@ -28,16 +29,18 @@ "light-bolt11-decoder": "^3.0.0", "ngeohash": "^0.6.3", "noble-secp256k1": "^1.2.14", - "nostr-tools": "^1.12.1", + "nostr-tools": "^1.14.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-error-boundary": "^4.0.10", - "react-hook-form": "^7.45.1", + "react-error-boundary": "^4.0.11", + "react-hook-form": "^7.45.4", + "react-photo-album": "^2.3.0", "react-qr-barcode-scanner": "^1.0.6", - "react-router-dom": "^6.14.1", + "react-router-dom": "^6.15.0", "react-singleton-hook": "^4.0.1", "react-use": "^17.4.0", - "webln": "^0.3.2" + "webln": "^0.3.2", + "yet-another-react-lightbox": "^3.12.1" }, "devDependencies": { "@changesets/cli": "^2.26.2", @@ -47,17 +50,17 @@ "@types/leaflet": "^1.9.3", "@types/leaflet.locatecontrol": "^0.74.1", "@types/ngeohash": "^0.6.4", - "@types/react": "^18.2.14", - "@types/react-dom": "^18.2.6", - "@vitejs/plugin-react": "^4.0.1", - "cypress": "^12.16.0", - "prettier": "^2.8.8", + "@types/react": "^18.2.20", + "@types/react-dom": "^18.2.7", + "@vitejs/plugin-react": "^4.0.4", + "cypress": "^12.17.4", + "prettier": "^3.0.2", "typescript": "^5.1.6", - "vite": "^4.3.9", + "vite": "^4.4.9", "vite-plugin-pwa": "^0.16.4" }, "resolutions": { - "@types/react": "^18.2.14", - "@types/react-dom": "^18.2.6" + "@types/react": "^18.2.20", + "@types/react-dom": "^18.2.7" } } diff --git a/src/app.tsx b/src/app.tsx index ae21fd0b1..bb375daff 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -28,7 +28,6 @@ import DirectMessageChatView from "./views/messages/chat"; import NostrLinkView from "./views/link"; import UserReportsTab from "./views/user/reports"; import ToolsHomeView from "./views/tools"; -import Nip19ToolsView from "./views/tools/nip19"; import UserAboutTab from "./views/user/about"; import UserLikesTab from "./views/user/likes"; import useSetColorMode from "./hooks/use-set-color-mode"; @@ -116,10 +115,7 @@ const router = createHashRouter([ { path: "profile", element: }, { path: "tools", - children: [ - { path: "", element: }, - { path: "nip19", element: }, - ], + children: [{ path: "", element: }], }, { path: "streams", diff --git a/src/classes/nostr-post-action.ts b/src/classes/nostr-post-action.ts deleted file mode 100644 index d3254e3e0..000000000 --- a/src/classes/nostr-post-action.ts +++ /dev/null @@ -1,50 +0,0 @@ -import relayPoolService from "../services/relay-pool"; -import { NostrEvent } from "../types/nostr-event"; -import createDefer from "./deferred"; -import { IncomingCommandResult, Relay } from "./relay"; -import { ListenerFn, Subject } from "./subject"; - -export type PostResult = { url: string; message?: string; status: boolean }; - -export function nostrPostAction(relays: string[], event: NostrEvent, timeout: number = 5000) { - const subject = new Subject(); - const onComplete = createDefer(); - const remaining = new Map>(); - - for (const url of relays) { - const relay = relayPoolService.requestRelay(url); - - const handler = (result: IncomingCommandResult) => { - if (result.eventId === event.id) { - subject.next({ - url, - status: result.status, - message: result.message, - }); - - relay.onCommandResult.unsubscribe(handler); - remaining.delete(relay); - if (remaining.size === 0) onComplete.resolve(); - } - }; - relay.onCommandResult.subscribe(handler); - remaining.set(relay, handler); - - // send event - relay.send(["EVENT", event]); - } - - setTimeout(() => { - if (remaining.size > 0) { - for (const [relay, handler] of remaining) { - relay.onCommandResult.unsubscribe(handler); - } - onComplete.resolve(); - } - }, timeout); - - return { - results: subject, - onComplete, - }; -} diff --git a/src/classes/nostr-publish-action.ts b/src/classes/nostr-publish-action.ts new file mode 100644 index 000000000..e08bf67a0 --- /dev/null +++ b/src/classes/nostr-publish-action.ts @@ -0,0 +1,65 @@ +import { addToLog } from "../services/publish-log"; +import relayPoolService from "../services/relay-pool"; +import { NostrEvent } from "../types/nostr-event"; +import createDefer from "./deferred"; +import { IncomingCommandResult, Relay } from "./relay"; +import Subject, { PersistentSubject } from "./subject"; + +let lastId = 0; + +export default class NostrPublishAction { + id = lastId++; + label: string; + relays: string[]; + event: NostrEvent; + + results = new PersistentSubject([]); + onResult = new Subject(undefined, false); + onComplete = createDefer(); + + private remaining = new Set(); + + constructor(label: string, relays: string[], event: NostrEvent, timeout: number = 5000) { + this.label = label; + this.relays = relays; + this.event = event; + + for (const url of relays) { + const relay = relayPoolService.requestRelay(url); + this.remaining.add(relay); + relay.onCommandResult.subscribe(this.handleResult, this); + + // send event + relay.send(["EVENT", event]); + } + + setTimeout(this.handleTimeout.bind(this), timeout); + + addToLog(this); + } + + private handleResult(result: IncomingCommandResult) { + if (result.eventId === this.event.id) { + const relay = result.relay; + this.results.next([...this.results.value, result]); + + this.onResult.next(result); + + relay.onCommandResult.unsubscribe(this.handleResult, this); + this.remaining.delete(relay); + if (this.remaining.size === 0) this.onComplete.resolve(this.results.value); + } + } + + private handleTimeout() { + for (const relay of this.remaining) { + this.handleResult({ + message: "Timeout", + eventId: this.event.id, + status: false, + type: "OK", + relay, + }); + } + } +} diff --git a/src/classes/relay.ts b/src/classes/relay.ts index 69d7b375f..f3b2197e6 100644 --- a/src/classes/relay.ts +++ b/src/classes/relay.ts @@ -40,12 +40,12 @@ const CONNECTION_TIMEOUT = 1000 * 30; export class Relay { url: string; - onOpen = new Subject(); - onClose = new Subject(); - onEvent = new Subject(); - onNotice = new Subject(); - onEOSE = new Subject(); - onCommandResult = new Subject(); + onOpen = new Subject(undefined, false); + onClose = new Subject(undefined, false); + onEvent = new Subject(undefined, false); + onNotice = new Subject(undefined, false); + onEOSE = new Subject(undefined, false); + onCommandResult = new Subject(undefined, false); ws?: WebSocket; mode: RelayMode = RelayMode.ALL; diff --git a/src/classes/subject.ts b/src/classes/subject.ts index 064dd8d4d..a4b3aece0 100644 --- a/src/classes/subject.ts +++ b/src/classes/subject.ts @@ -8,20 +8,22 @@ interface ConnectableApi { connect(connectable: Connectable): this; disconnect(connectable: Connectable): this; } -type Connection = (value: From, next: (value: To) => any, prevValue: Prev) => void; +export type Connection = (value: From, next: (value: To) => any, prevValue: Prev) => void; export class Subject implements Connectable { listeners: [ListenerFn, Object | undefined][] = []; value?: Value; - constructor(value?: Value) { - this.value = value; + cacheValue: boolean; + constructor(value?: Value, cacheValue = true) { + this.cacheValue = cacheValue; + if (this.cacheValue) this.value = value; } next(value: Value) { if (this.value === value) return; - this.value = value; + if (this.cacheValue) this.value = value; for (const [listener, ctx] of this.listeners) { if (ctx) listener.call(ctx, value); else listener(value); @@ -99,7 +101,7 @@ export class Subject implements Connectable { export class PersistentSubject extends Subject implements ConnectableApi { value: Value; constructor(value: Value) { - super(); + super(value, true); this.value = value; } } diff --git a/src/components/connected-relays.tsx b/src/components/connected-relays.tsx index 719e47d8a..64e9e1493 100644 --- a/src/components/connected-relays.tsx +++ b/src/components/connected-relays.tsx @@ -21,7 +21,6 @@ import { import relayPoolService from "../services/relay-pool"; import { useInterval } from "react-use"; import { RelayStatus } from "./relay-status"; -import { useIsMobile } from "../hooks/use-is-mobile"; import { RelayIcon } from "./icons"; import { Relay } from "../classes/relay"; import { RelayFavicon } from "./relay-favicon"; @@ -29,7 +28,6 @@ import relayScoreboardService from "../services/relay-scoreboard"; import { RelayScoreBreakdown } from "./relay-score-breakdown"; export const ConnectedRelays = () => { - const isMobile = useIsMobile(); const { isOpen, onOpen, onClose } = useDisclosure(); const [relays, setRelays] = useState(relayPoolService.getRelays()); const sortedRelays = useMemo(() => relayScoreboardService.getRankedRelays(relays.map((r) => r.url)), [relays]); diff --git a/src/components/embed-types/common.tsx b/src/components/embed-types/common.tsx index 32804bc27..8e9c22361 100644 --- a/src/components/embed-types/common.tsx +++ b/src/components/embed-types/common.tsx @@ -1,56 +1,10 @@ -import { Box, Image, ImageProps, Link, useDisclosure } from "@chakra-ui/react"; -import appSettings from "../../services/settings/app-settings"; -import { ImageGalleryLink } from "../image-gallery"; -import { useTrusted } from "../../providers/trust"; +import { Link } from "@chakra-ui/react"; + import OpenGraphCard from "../open-graph-card"; +import { isVideoURL } from "../../helpers/url"; -const BlurredImage = (props: ImageProps) => { - const { isOpen, onOpen } = useDisclosure(); - return ( - - { - e.stopPropagation(); - e.preventDefault(); - onOpen(); - } - : undefined - } - cursor="pointer" - filter={isOpen ? "" : "blur(1.5rem)"} - {...props} - /> - - ); -}; - -const EmbeddedImage = ({ src }: { src: string }) => { - const trusted = useTrusted(); - const ImageComponent = trusted || !appSettings.value.blurImages ? Image : BlurredImage; - const thumbnail = appSettings.value.imageProxy - ? new URL(`/256,fit/${src}`, appSettings.value.imageProxy).toString() - : src; - - return ( - - - - ); -}; - -// note1n06jceulg3gukw836ghd94p0ppwaz6u3mksnnz960d8vlcp2fnqsgx3fu9 -const imageExt = [".svg", ".gif", ".png", ".jpg", ".jpeg", ".webp", ".avif"]; -export function renderImageUrl(match: URL) { - if (!imageExt.some((ext) => match.pathname.endsWith(ext))) return null; - - return ; -} - -const videoExt = [".mp4", ".mkv", ".webm", ".mov"]; export function renderVideoUrl(match: URL) { - if (!videoExt.some((ext) => match.pathname.endsWith(ext))) return null; + if (!isVideoURL(match)) return null; return