diff --git a/.changeset/beige-waves-wash.md b/.changeset/beige-waves-wash.md new file mode 100644 index 000000000..48df44f93 --- /dev/null +++ b/.changeset/beige-waves-wash.md @@ -0,0 +1,5 @@ +--- +"nostrudel": minor +--- + +Show streamer cards in stream view on desktop diff --git a/CHANGELOG.md b/CHANGELOG.md index a000a15f3..6eac9e74e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # nostrudel +## 0.24.0 + +### Minor Changes + +- 03d84eb: Show notes in relay view +- 1e75dbd: Improve layout of image galleries +- 07f67cc: Show all images in lightbox +- d2948e7: Rebuild event publish details +- 1148093: Render multiple images as image gallery +- d8b29b4: Add relay review form +- 9b6c653: Add simple timeline health view +- b7deb16: Clean up navigation menu +- 018c917: Add mobile friendly lightbox +- ce550f5: Show label for paid relays +- e052991: Add inline reply form +- 70bada5: Add and options to CORS proxy url +- 70bada5: Use corsproxy.io as default service for CORS proxy + +### Patch Changes + +- 1bc4500: Fix non-english characters breaking links + ## 0.23.0 ### Minor Changes 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 d86a7a8fb..3708e59e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nostrudel", - "version": "0.23.0", + "version": "0.24.0", "private": true, "license": "MIT", "scripts": { @@ -12,16 +12,16 @@ "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", @@ -29,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", @@ -48,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 b84171fa1..a61dca935 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"; @@ -118,10 +117,7 @@ const router = createHashRouter([ { path: "profile", element: }, { path: "tools", - children: [ - { path: "", element: }, - { path: "nip19", element: }, - ], + children: [{ path: "", element: }], }, { path: "lists", 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 7fa2c1e3c..a4b3aece0 100644 --- a/src/classes/subject.ts +++ b/src/classes/subject.ts @@ -14,14 +14,16 @@ 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/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