diff --git a/.changeset/rare-carrots-watch.md b/.changeset/rare-carrots-watch.md
new file mode 100644
index 000000000..1d0e02632
--- /dev/null
+++ b/.changeset/rare-carrots-watch.md
@@ -0,0 +1,5 @@
+---
+"nostrudel": minor
+---
+
+Add settings for Invidious, Nitter, Libreddit, Teddit redirects
diff --git a/src/components/embed-types/common.tsx b/src/components/embed-types/common.tsx
index 4ac13e3c2..bee8ff359 100644
--- a/src/components/embed-types/common.tsx
+++ b/src/components/embed-types/common.tsx
@@ -1,9 +1,8 @@
import { Box, Image, ImageProps, Link, useDisclosure } from "@chakra-ui/react";
-import { EmbedableContent, embedJSX } from "../../helpers/embeds";
import appSettings from "../../services/app-settings";
import { ImageGalleryLink } from "../image-gallery";
import { useIsMobile } from "../../hooks/use-is-mobile";
-import { matchImageUrls } from "../../helpers/regexp";
+import { useTrusted } from "../note/trust";
const BlurredImage = (props: ImageProps) => {
const { isOpen, onOpen } = useDisclosure();
@@ -14,9 +13,10 @@ const BlurredImage = (props: ImageProps) => {
);
};
-const EmbeddedImage = ({ src, blue }: { src: string; blue: boolean }) => {
+const EmbeddedImage = ({ src }: { src: string }) => {
const isMobile = useIsMobile();
- const ImageComponent = blue || !appSettings.value.blurImages ? Image : BlurredImage;
+ 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;
@@ -29,31 +29,24 @@ const EmbeddedImage = ({ src, blue }: { src: string; blue: boolean }) => {
};
// note1n06jceulg3gukw836ghd94p0ppwaz6u3mksnnz960d8vlcp2fnqsgx3fu9
-export function embedImages(content: EmbedableContent, trusted = false) {
- return embedJSX(content, {
- regexp: matchImageUrls,
- render: (match) => ,
- name: "Image",
- });
+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 ;
}
-export function embedVideos(content: EmbedableContent) {
- return embedJSX(content, {
- name: "Video",
- regexp:
- /https?:\/\/([\dA-z\.-]+\.[A-z\.]{2,12})((?:\/[\+~%\/\.\w\-_]*)?\.(?:mp4|mkv|webm|mov))(\??(?:[\?#\-\+=&;%@\.\w_]*)#?(?:[\-\.\!\/\\\w]*))?/i,
- render: (match) => ,
- });
+const videoExt = [".mp4", ".mkv", ".webm", ".mov"];
+export function renderVideoUrl(match: URL) {
+ if (!videoExt.some((ext) => match.pathname.endsWith(ext))) return null;
+
+ return ;
}
-export function embedLinks(content: EmbedableContent) {
- return embedJSX(content, {
- name: "Link",
- regexp: /https?:\/\/([\dA-z\.-]+\.[A-z\.]{2,12})(\/[\+~%\/\.\w\-_]*)?([\?#][^\s]+)?/i,
- render: (match) => (
-
- {match[0]}
-
- ),
- });
+export function renderDefaultUrl(match: URL) {
+ return (
+
+ {match.toString()}
+
+ );
}
diff --git a/src/components/embed-types/music.tsx b/src/components/embed-types/music.tsx
index 8f4266705..c6121f504 100644
--- a/src/components/embed-types/music.tsx
+++ b/src/components/embed-types/music.tsx
@@ -1,75 +1,81 @@
import { EmbedableContent, embedJSX } from "../../helpers/embeds";
+import appSettings from "../../services/app-settings";
-export function embedWavlakeTrack(content: EmbedableContent) {
- return embedJSX(content, {
- name: "Wavlake Track",
- regexp: /https?:\/\/wavlake\.com\/track\/[\w-]+/i,
- render: (match) => (
-
- ),
- });
+// nostr:nevent1qqsve4ud5v8gjds2f2h7exlmjvhqayu4s520pge7frpwe22wezny0pcpp4mhxue69uhkummn9ekx7mqprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2mxs3z0
+export function renderWavlakeUrl(match: URL) {
+ if (match.hostname !== "wavlake.com") return null;
+
+ const embedUrl = new URL(match);
+ embedUrl.hostname = "embed.wavlake.com";
+
+ return (
+
+ );
}
-// note1tvqk2mu829yr6asf7w5dgpp8t0mlp2ax5t26ctfdx8m0ptkssamqsleeux
-// note1ygx9tec3af92704d92jwrj3zs7cws2jl29yvrlxzqlcdlykhwssqpupa7t
-export function embedAppleMusic(content: EmbedableContent) {
- return embedJSX(content, {
- regexp: /https?:\/\/music\.apple\.com(?:\/[\+~%\/\.\w\-_]*)?(\??(?:[\?#\-\+=&;%@\.\w_]*)#?(?:[\-\.\!\/\\\w]*))?/,
- render: (match) => (
-
- ),
- name: "Apple Music",
- });
+// nostr:nevent1qqs9kqt9d7r4zjpawcyl82x5qsn4hals4wn294dv95knrahs4mggwasprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2whhzvz
+// nostr:nevent1qqszyrz4uug75j4086kj4f8peg3g0v8g9f04zjxplnpq0uxljtthggqprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk2aeexmq
+export function renderAppleMusicUrl(match: URL) {
+ if (match.hostname !== "music.apple.com") return null;
+
+ const isList = match.searchParams.get("l") !== null;
+
+ const embedUrl = new URL(match);
+ embedUrl.hostname = "embed.music.apple.com";
+
+ return (
+
+ );
}
// nostr:nevent1qqs9r94qeqhqayvuz6q6u88spvuz0d25nhpyv0c39wympmfu646x4pgpz3mhxue69uhhyetvv9ujuerpd46hxtnfduq3samnwvaz7tmjv4kxz7fwwdhx7un59eek7cmfv9kqmhxhvq
-export function embedSpotifyMusic(content: EmbedableContent) {
- return embedJSX(content, {
- regexp:
- /https?:\/\/open\.spotify\.com\/(track|episode|album|playlist)\/(\w+)(\??(?:[\?#\-\+=&;%@\.\w_]*)#?(?:[\-\.\!\/\\\w]*))?/im,
- render: (match) => {
- const isList = match[1] === "album" || match[1] === "playlist";
- return (
-
- );
- },
- name: "Spotify",
- });
+// nostr:nevent1qqsx0lz7m72qzq499exwhnfszvgwea8tv38x9wkv32yhkmwwmhgs7jgprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk25m3sln
+// nostr:nevent1qqsqxkmz49hydf8ppa9k6x6zrcq7m4evhhlye0j3lcnz8hrl2q6np4spz3mhxue69uhhyetvv9ujuerpd46hxtnfdult02qz
+const spotifyPaths = ["/track", "/episode", "/album", "/playlist"];
+export function renderSpotifyUrl(match: URL) {
+ if (match.hostname !== "open.spotify.com") return null;
+ if (!spotifyPaths.some((p) => match.pathname.startsWith(p))) return null;
+
+ const isList = match.pathname.startsWith("/album") || match.pathname.startsWith("/playlist");
+
+ const embedUrl = new URL(match);
+ embedUrl.pathname = "/embed" + embedUrl.pathname;
+
+ return (
+
+ );
}
-// note132m5xc3zhj7fap67vzwx5x3s8xqgz49k669htcn8kppr4m654tuq960tuu
-export function embedTidalMusic(content: EmbedableContent) {
- return embedJSX(content, {
- regexp: /https?:\/\/tidal\.com(\/browse)?\/(track|album)\/(\d+)/im,
- render: (match) => (
-
- ),
- name: "Tidal",
- });
+// nostr:nevent1qqsg4d6rvg3te0y7sa0xp8r2rgcrnqyp2jmddzm4ufnmqs36aa2247qprpmhxue69uhhyetvv9ujuumwdae8gtnnda3kjctvmdc953
+export function renderTidalUrl(match: URL) {
+ if (match.hostname !== "tidal.com") return null;
+
+ const isList = match.pathname.includes("/album");
+ const [_, _browse, type, id] = match.pathname.match(/(\/browse)?\/(track|album)\/(\d+)/i) ?? [];
+
+ const embedUrl = new URL(`https://embed.tidal.com/${type}s/${id}`);
+ embedUrl.searchParams.set("disableAnalytics", "true");
+
+ return ;
}
diff --git a/src/components/embed-types/reddit.tsx b/src/components/embed-types/reddit.tsx
new file mode 100644
index 000000000..ab92603ca
--- /dev/null
+++ b/src/components/embed-types/reddit.tsx
@@ -0,0 +1,25 @@
+import { replaceDomain } from "../../helpers/url";
+import appSettings from "../../services/app-settings";
+import { renderDefaultUrl } from "./common";
+
+// copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/reddit.js
+const REDDIT_DOMAINS = [
+ "www.reddit.com",
+ "np.reddit.com",
+ "new.reddit.com",
+ "amp.reddit.com",
+ "i.redd.it",
+ "redd.it",
+ "old.reddit.com",
+];
+
+const bypassPaths = /\/(gallery\/poll\/rpan\/settings\/topics)/;
+export function renderRedditUrl(match: URL) {
+ if (!REDDIT_DOMAINS.includes(match.hostname)) return null;
+ if (match.pathname.match(bypassPaths)) return null;
+
+ const { redditRedirect } = appSettings.value;
+ const fixed = redditRedirect ? replaceDomain(match, redditRedirect) : match;
+
+ return renderDefaultUrl(fixed);
+}
diff --git a/src/components/embed-types/twitter.tsx b/src/components/embed-types/twitter.tsx
index b2ea77051..a8323b746 100644
--- a/src/components/embed-types/twitter.tsx
+++ b/src/components/embed-types/twitter.tsx
@@ -1,11 +1,21 @@
-import { EmbedableContent, embedJSX } from "../../helpers/embeds";
+import { replaceDomain } from "../../helpers/url";
+import appSettings from "../../services/app-settings";
import { TweetEmbed } from "../tweet-embed";
+import { renderDefaultUrl } from "./common";
-export function embedTweet(content: EmbedableContent) {
- return embedJSX(content, {
- name: "Tweet",
- regexp:
- /https?:\/\/twitter\.com\/(?:\#!\/)?(\w+)\/status(es)?\/(\d+)(\??(?:[\?#\-\+=&;%@\.\w_]*)#?(?:[\-\.\!\/\\\w]*))?/im,
- render: (match) => ,
- });
+// copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/twitter.js
+export const TWITTER_DOMAINS = [
+ "twitter.com",
+ "www.twitter.com",
+ "mobile.twitter.com",
+ "pbs.twimg.com",
+ "video.twimg.com",
+];
+
+export function renderTwitterUrl(match: URL) {
+ if (!TWITTER_DOMAINS.includes(match.hostname)) return null;
+
+ const { twitterRedirect } = appSettings.value;
+ if (twitterRedirect) return renderDefaultUrl(replaceDomain(match, twitterRedirect));
+ else return ;
}
diff --git a/src/components/embed-types/youtube.tsx b/src/components/embed-types/youtube.tsx
index 5c64c6ba3..439574497 100644
--- a/src/components/embed-types/youtube.tsx
+++ b/src/components/embed-types/youtube.tsx
@@ -1,65 +1,40 @@
import { AspectRatio } from "@chakra-ui/react";
-import { EmbedType, EmbedableContent, embedJSX } from "../../helpers/embeds";
+import appSettings from "../../services/app-settings";
-// nostr:note1ya94hd44g3m2x4gagcydkg28qcp924dd238vq5k4chly84mqt2wqnwgu6d
-// nostr:note1apu56y4h2ms5uwpzz209vychr309kllhq6wz46te84u9rus5x7kqj5f5n9
-export function embedYoutubeVideo(content: EmbedableContent) {
- return embedJSX(content, {
- name: "Youtube Video",
- regexp:
- /https?:\/\/(?:(?:www|m)\.)?(?:youtube\.com|youtu\.be)(\/(?:[\w\-]+\?v=|embed\/|v\/|live\/|shorts\/)?)([\w\-]+)(\S+)?/,
- render: (match) => (
-
-
-
- ),
- });
-}
+// copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/youtube.js
+export const YOUTUBE_DOMAINS = [
+ "m.youtube.com",
+ "youtube.com",
+ "img.youtube.com",
+ "www.youtube.com",
+ "youtube-nocookie.com",
+ "www.youtube-nocookie.com",
+ "youtu.be",
+ "s.ytimg.com",
+ "music.youtube.com",
+];
-// nostr:note12vqqte3gtd729gp65tgdk0a8yym5ynwqjuhk5s6l333yethlvlcsqptvmk
-export function embedYoutubePlaylist(content: EmbedableContent) {
- return embedJSX(content, {
- name: "Youtube Playlist",
- regexp: /https?:\/\/(?:(?:www|m)\.)?(?:youtube\.com|youtu\.be)\/(?:playlist\?list=)([\w\-]+)(\S+)?/im,
- render: (match) => (
-
-
-
- ),
- });
-}
+// nostr:nevent1qqszwj6mk665ga4r25w5vzxmy9rsvqj42kk4gnkq2t2utljr6as948qpp4mhxue69uhkummn9ekx7mqprdmhxue69uhkvet9v3ejumn0wd68ytnzv9hxgtmdv4kk245xvyn
+export function renderYoutubeUrl(match: URL) {
+ if (!YOUTUBE_DOMAINS.includes(match.hostname)) return null;
+ if (match.pathname.startsWith("/live")) return null;
-export function embedYoutubeMusic(content: EmbedableContent) {
- return embedJSX(content, {
- regexp: /https?:\/\/music\.youtube\.com\/watch\?v=(\w+)(\??(?:[\?#\-\+=&;%@\.\w_]*)#?(?:[\-\.\!\/\\\w]*))?/,
- render: (match) => (
-
-
-
- ),
- name: "Youtube Music",
- });
+ const { youtubeRedirect } = appSettings.value;
+
+ const videoId = match.searchParams.get("v");
+ if (!videoId) throw new Error("cant find video id");
+ const embedUrl = new URL(`/embed/${videoId}`, youtubeRedirect || "https://youtube.com");
+
+ return (
+
+
+
+ );
}
diff --git a/src/components/note/note-contents.tsx b/src/components/note/note-contents.tsx
index 397218926..289e59435 100644
--- a/src/components/note/note-contents.tsx
+++ b/src/components/note/note-contents.tsx
@@ -3,44 +3,45 @@ import { Box, Text } from "@chakra-ui/react";
import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event";
import styled from "@emotion/styled";
import { useExpand } from "./expanded";
-import { EmbedableContent } from "../../helpers/embeds";
+import { EmbedableContent, embedUrls } from "../../helpers/embeds";
import {
- embedTweet,
embedLightningInvoice,
- embedImages,
- embedVideos,
- embedLinks,
- embedSpotifyMusic,
- embedTidalMusic,
- embedYoutubeVideo,
- embedYoutubePlaylist,
- embedYoutubeMusic,
embedNostrLinks,
embedNostrMentions,
- embedAppleMusic,
embedNostrHashtags,
- embedWavlakeTrack,
+ renderWavlakeUrl,
+ renderYoutubeUrl,
+ renderDefaultUrl,
+ renderImageUrl,
+ renderTwitterUrl,
+ renderAppleMusicUrl,
+ renderSpotifyUrl,
+ renderTidalUrl,
+ renderVideoUrl,
} from "../embed-types";
import { ImageGalleryProvider } from "../image-gallery";
import { useTrusted } from "./trust";
+import { renderRedditUrl } from "../embed-types/reddit";
function buildContents(event: NostrEvent | DraftNostrEvent, trusted = false) {
let content: EmbedableContent = [event.content.trim()];
- content = embedLightningInvoice(content);
- content = embedTweet(content);
- content = embedYoutubeVideo(content);
- content = embedYoutubePlaylist(content);
- content = embedYoutubeMusic(content);
- content = embedWavlakeTrack(content);
- content = embedTidalMusic(content);
- content = embedAppleMusic(content);
- content = embedSpotifyMusic(content);
-
// common
- content = embedImages(content, trusted);
- content = embedVideos(content);
- content = embedLinks(content);
+ content = embedUrls(content, [
+ renderYoutubeUrl,
+ renderTwitterUrl,
+ renderRedditUrl,
+ renderWavlakeUrl,
+ renderAppleMusicUrl,
+ renderSpotifyUrl,
+ renderTidalUrl,
+ renderImageUrl,
+ renderVideoUrl,
+ renderDefaultUrl,
+ ]);
+
+ // bitcoin
+ content = embedLightningInvoice(content);
// nostr
content = embedNostrLinks(content, event);
diff --git a/src/components/tweet-embed.tsx b/src/components/tweet-embed.tsx
index 508212ab0..29b33fb4a 100644
--- a/src/components/tweet-embed.tsx
+++ b/src/components/tweet-embed.tsx
@@ -1,5 +1,5 @@
import { useColorMode } from "@chakra-ui/react";
-import { useEffect, useRef } from "react";
+import { useEffect, useRef, useState } from "react";
export type TweetEmbedProps = {
href: string;
diff --git a/src/helpers/embeds.ts b/src/helpers/embeds.ts
index 601964f38..057aa8e78 100644
--- a/src/helpers/embeds.ts
+++ b/src/helpers/embeds.ts
@@ -3,7 +3,7 @@ import { cloneElement } from "react";
export type EmbedableContent = (string | JSX.Element)[];
export type EmbedType = {
regexp: RegExp;
- render: (match: RegExpMatchArray) => JSX.Element | string;
+ render: (match: RegExpMatchArray) => JSX.Element | string | null;
name: string;
};
@@ -18,6 +18,8 @@ export function embedJSX(content: EmbedableContent, embed: EmbedType): Embedable
const after = subContent.slice(match.index + match[0].length, subContent.length);
let embedRender = embed.render(match);
+ if (embedRender === null) return subContent;
+
if (typeof embedRender !== "string" && !embedRender.props.key) {
embedRender = cloneElement(embedRender, { key: embed.name + i });
}
@@ -30,3 +32,26 @@ export function embedJSX(content: EmbedableContent, embed: EmbedType): Embedable
})
.flat();
}
+
+export type LinkEmbedHandler = (link: URL) => JSX.Element | string | null;
+
+export function embedUrls(content: EmbedableContent, handlers: LinkEmbedHandler[]) {
+ return embedJSX(content, {
+ name: "embedUrls",
+ regexp: /https?:\/\/([\dA-z\.-]+\.[A-z\.]{2,12})(\/[\+~%\/\.\w\-_]*)?([\?#][^\s]+)?/i,
+ render: (match) => {
+ try {
+ const url = new URL(match[0]);
+ for (const handler of handlers) {
+ const content = handler(url);
+ if (content) return content;
+ }
+ } catch (e) {
+ if (e instanceof Error) {
+ console.error("Failed to embed link", match[0], e.message);
+ }
+ }
+ return null;
+ },
+ });
+}
diff --git a/src/helpers/url.ts b/src/helpers/url.ts
index 4665fcb85..90965977c 100644
--- a/src/helpers/url.ts
+++ b/src/helpers/url.ts
@@ -1,3 +1,5 @@
+const convertToUrl = (url: string | URL) => (url instanceof URL ? url : new URL(url));
+
export function normalizeRelayUrl(relayUrl: string) {
const url = new URL(relayUrl);
@@ -18,3 +20,14 @@ export function safeRelayUrl(relayUrl: string) {
} catch (e) {}
return null;
}
+
+export function replaceDomain(url: string | URL, replacementUrl: string | URL) {
+ const newUrl = new URL(url);
+ replacementUrl = convertToUrl(replacementUrl);
+ newUrl.host = replacementUrl.host;
+ newUrl.protocol = replacementUrl.protocol;
+ if (replacementUrl.port) newUrl.port = replacementUrl.port;
+ if (replacementUrl.username) newUrl.username = replacementUrl.username;
+ if (replacementUrl.password) newUrl.password = replacementUrl.password;
+ return newUrl;
+}
diff --git a/src/services/user-app-settings.ts b/src/services/user-app-settings.ts
index de2eacfae..a055c13f6 100644
--- a/src/services/user-app-settings.ts
+++ b/src/services/user-app-settings.ts
@@ -27,6 +27,9 @@ export type AppSettings = {
primaryColor: string;
imageProxy: string;
showContentWarning: boolean;
+ twitterRedirect?: string;
+ redditRedirect?: string;
+ youtubeRedirect?: string;
};
export const defaultSettings: AppSettings = {
@@ -41,6 +44,9 @@ export const defaultSettings: AppSettings = {
primaryColor: "#8DB600",
imageProxy: "",
showContentWarning: true,
+ twitterRedirect: undefined,
+ redditRedirect: undefined,
+ youtubeRedirect: undefined,
};
function parseAppSettings(event: NostrEvent): AppSettings {
diff --git a/src/views/dm/chat.tsx b/src/views/dm/chat.tsx
index 496173c4e..93e855b88 100644
--- a/src/views/dm/chat.tsx
+++ b/src/views/dm/chat.tsx
@@ -17,19 +17,17 @@ import clientRelaysService from "../../services/client-relays";
import directMessagesService, { getMessageRecipient } from "../../services/direct-messages";
import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event";
import DecryptPlaceholder from "./decrypt-placeholder";
-import { EmbedableContent } from "../../helpers/embeds";
-import { embedImages, embedLinks, embedNostrLinks, embedVideos } from "../../components/embed-types";
+import { EmbedableContent, embedUrls } from "../../helpers/embeds";
+import { embedNostrLinks, renderDefaultUrl, renderImageUrl, renderVideoUrl } from "../../components/embed-types";
import RequireCurrentAccount from "../../providers/require-current-account";
function MessageContent({ event, text }: { event: NostrEvent; text: string }) {
let content: EmbedableContent = [text];
- content = embedImages(content, true);
- content = embedVideos(content);
- content = embedLinks(content);
-
content = embedNostrLinks(content, event);
+ content = embedUrls(content, [renderImageUrl, renderVideoUrl, renderDefaultUrl]);
+
return {content};
}
diff --git a/src/views/settings/index.tsx b/src/views/settings/index.tsx
index 2d201e3ac..16800ec70 100644
--- a/src/views/settings/index.tsx
+++ b/src/views/settings/index.tsx
@@ -6,6 +6,7 @@ import LightningSettings from "./lightning-settings";
import DatabaseSettings from "./database-settings";
import DisplaySettings from "./display-settings";
import PerformanceSettings from "./performance-settings";
+import PrivacySettings from "./privacy-settings";
export default function SettingsView() {
return (
@@ -15,6 +16,8 @@ export default function SettingsView() {
+
+
diff --git a/src/views/settings/performance-settings.tsx b/src/views/settings/performance-settings.tsx
index 3f6c4dd0f..5649cc4f1 100644
--- a/src/views/settings/performance-settings.tsx
+++ b/src/views/settings/performance-settings.tsx
@@ -12,8 +12,6 @@ import {
Input,
Link,
} from "@chakra-ui/react";
-import appSettings, { replaceSettings } from "../../services/app-settings";
-import useSubject from "../../hooks/use-subject";
import useAppSettings from "../../hooks/use-app-settings";
import { useEffect, useState } from "react";
diff --git a/src/views/settings/privacy-settings.tsx b/src/views/settings/privacy-settings.tsx
new file mode 100644
index 000000000..5b7558fc9
--- /dev/null
+++ b/src/views/settings/privacy-settings.tsx
@@ -0,0 +1,130 @@
+import {
+ Flex,
+ FormControl,
+ FormLabel,
+ Switch,
+ AccordionItem,
+ AccordionPanel,
+ AccordionButton,
+ Box,
+ AccordionIcon,
+ FormHelperText,
+ Input,
+ Link,
+ Button,
+ FormErrorMessage,
+} from "@chakra-ui/react";
+import useAppSettings from "../../hooks/use-app-settings";
+import { useForm } from "react-hook-form";
+import { useAsync } from "react-use";
+
+async function validateInvidiousUrl(url?: string) {
+ if (!url) return true;
+ try {
+ const res = await fetch(new URL("/api/v1/stats", url));
+ return res.ok || "Catch reach instance";
+ } catch (e) {
+ return "Catch reach instance";
+ }
+}
+
+export default function PrivacySettings() {
+ const { youtubeRedirect, twitterRedirect, redditRedirect, updateSettings } = useAppSettings();
+
+ const { register, handleSubmit, formState } = useForm({
+ mode: "onBlur",
+ defaultValues: {
+ youtubeRedirect,
+ twitterRedirect,
+ redditRedirect,
+ },
+ });
+
+ const save = handleSubmit(async (values) => {
+ await updateSettings(values);
+ });
+
+ return (
+
+
+
+
+ Privacy
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/views/user/components/header.tsx b/src/views/user/components/header.tsx
index 3cff0019e..8e8afbb52 100644
--- a/src/views/user/components/header.tsx
+++ b/src/views/user/components/header.tsx
@@ -14,7 +14,8 @@ import { useCurrentAccount } from "../../../hooks/use-current-account";
import { useIsMobile } from "../../../hooks/use-is-mobile";
import { useUserMetadata } from "../../../hooks/use-user-metadata";
import { UserProfileMenu } from "./user-profile-menu";
-import { embedLinks } from "../../../components/embed-types";
+import { embedUrls } from "../../../helpers/embeds";
+import { renderDefaultUrl } from "../../../components/embed-types";
export default function Header({
pubkey,
@@ -52,7 +53,7 @@ export default function Header({
/>
- {metadata?.about && {embedLinks([metadata.about])}}
+ {metadata?.about && {embedUrls([metadata.about], [renderDefaultUrl])}}