From 68771779c9a85070b0beddc822d1f091f8b067c6 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Wed, 8 Feb 2023 21:59:44 +0100 Subject: [PATCH 01/17] nip-65 --- src/Element/Relays.css | 26 +++++++++++++++++++++ src/Element/Relays.tsx | 43 +++++++++++++++++++++++++++++++++++ src/Element/Zap.css | 2 +- src/Feed/EventPublisher.ts | 18 +++++++++++++++ src/Feed/RelaysFeed.tsx | 37 ++++++++++++++++++++++++++++++ src/Icons/Read.tsx | 17 ++++++++++++++ src/Icons/Write.tsx | 17 ++++++++++++++ src/Nostr/EventKind.ts | 1 + src/Nostr/Subscriptions.ts | 9 ++++++++ src/Nostr/index.ts | 1 + src/Pages/ProfilePage.tsx | 15 +++++++++++- src/Pages/messages.js | 35 ++++++++++++++++++++++++++++ src/Pages/settings/Relays.tsx | 2 ++ src/translations/en.json | 2 +- src/translations/es.json | 3 +++ src/translations/fr.json | 3 +++ src/translations/ja.json | 2 ++ src/translations/zh.json | 3 +++ 18 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 src/Element/Relays.css create mode 100644 src/Element/Relays.tsx create mode 100644 src/Feed/RelaysFeed.tsx create mode 100644 src/Icons/Read.tsx create mode 100644 src/Icons/Write.tsx create mode 100644 src/Pages/messages.js diff --git a/src/Element/Relays.css b/src/Element/Relays.css new file mode 100644 index 00000000..e17b06e6 --- /dev/null +++ b/src/Element/Relays.css @@ -0,0 +1,26 @@ +.favicon { + width: 21px; + height: 21px; + border-radius: 100%; + margin-right: 12px; +} + +.relay-card { + display: flex; + flex-direction: row; + align-items: center; +} + +.relay-settings { + margin-left: auto; +} + +.relay-settings svg:not(:last-child) { + margin-right: 12px; +} +.relay-settings svg.enabled { + color: var(--highlight); +} +.relay-settings svg.disabled { + opacity: 0.3; +} diff --git a/src/Element/Relays.tsx b/src/Element/Relays.tsx new file mode 100644 index 00000000..82439401 --- /dev/null +++ b/src/Element/Relays.tsx @@ -0,0 +1,43 @@ +import "./Relays.css"; +import Nostrich from "nostrich.webp"; +import { useState } from "react"; + +import Read from "Icons/Read"; +import Write from "Icons/Write"; + +export interface RelaySpec { + url: string; + settings: { read: boolean; write: boolean }; +} + +interface RelaysProps { + relays: RelaySpec[]; +} + +const RelayFavicon = ({ url }: { url: string }) => { + const cleanUrl = url.replace("wss://relay.", "https://").replace("wss://nostr.", "https://"); + const [faviconUrl, setFaviconUrl] = useState(`${cleanUrl}/favicon.ico`); + + return setFaviconUrl(Nostrich)} />; +}; + +const Relays = ({ relays }: RelaysProps) => { + return ( +
+ {relays?.map(({ url, settings }) => { + return ( +
+ + {url} +
+ + +
+
+ ); + })} +
+ ); +}; + +export default Relays; diff --git a/src/Element/Zap.css b/src/Element/Zap.css index 2dd2c9e0..dfc7bedd 100644 --- a/src/Element/Zap.css +++ b/src/Element/Zap.css @@ -12,7 +12,7 @@ } .zap .header .amount { - font-size: 32px; + font-size: 24px; } @media (max-width: 520px) { diff --git a/src/Feed/EventPublisher.ts b/src/Feed/EventPublisher.ts index eae20568..6a984cee 100644 --- a/src/Feed/EventPublisher.ts +++ b/src/Feed/EventPublisher.ts @@ -220,6 +220,24 @@ export default function useEventPublisher() { return await signEvent(ev); } }, + saveRelaysSettings: async () => { + if (pubKey) { + let ev = NEvent.ForPubKey(pubKey); + ev.Kind = EventKind.Relays; + ev.Content = ""; + for (let [url, settings] of Object.entries(relays)) { + let rTag = ["r", url]; + if (settings.read) { + rTag.push("read"); + } + if (settings.write) { + rTag.push("write"); + } + ev.Tags.push(new Tag(rTag, ev.Tags.length)); + } + return await signEvent(ev); + } + }, addFollow: async (pkAdd: HexKey | HexKey[], newRelays?: Record) => { if (pubKey) { const ev = NEvent.ForPubKey(pubKey); diff --git a/src/Feed/RelaysFeed.tsx b/src/Feed/RelaysFeed.tsx new file mode 100644 index 00000000..771fa913 --- /dev/null +++ b/src/Feed/RelaysFeed.tsx @@ -0,0 +1,37 @@ +import { useMemo } from "react"; +import { HexKey } from "Nostr"; +import EventKind from "Nostr/EventKind"; +import { RelaySpec } from "Element/Relays"; +import { Subscriptions } from "Nostr/Subscriptions"; +import useSubscription from "./Subscription"; + +export default function useRelaysFeed(pubkey: HexKey) { + const sub = useMemo(() => { + const x = new Subscriptions(); + x.Id = `relays:${pubkey.slice(0, 12)}`; + x.Kinds = new Set([EventKind.Relays]); + x.Authors = new Set([pubkey]); + x.Limit = 1; + return x; + }, [pubkey]); + + const relays = useSubscription(sub, { leaveOpen: true, cache: true }); + const notes = relays.store.notes; + const tags = notes.slice(-1)[0]?.tags || []; + return tags.reduce((rs, tag) => { + const [t, url, ...settings] = tag; + if (t === "r") { + return [ + ...rs, + { + url, + settings: { + read: settings.includes("read"), + write: settings.includes("write"), + }, + }, + ]; + } + return rs; + }, [] as RelaySpec[]); +} diff --git a/src/Icons/Read.tsx b/src/Icons/Read.tsx new file mode 100644 index 00000000..e19a66b5 --- /dev/null +++ b/src/Icons/Read.tsx @@ -0,0 +1,17 @@ +import IconProps from "./IconProps"; + +const Read = (props: IconProps) => { + return ( + + + + ); +}; + +export default Read; diff --git a/src/Icons/Write.tsx b/src/Icons/Write.tsx new file mode 100644 index 00000000..9c016e57 --- /dev/null +++ b/src/Icons/Write.tsx @@ -0,0 +1,17 @@ +import IconProps from "./IconProps"; + +const Write = (props: IconProps) => { + return ( + + + + ); +}; + +export default Write; diff --git a/src/Nostr/EventKind.ts b/src/Nostr/EventKind.ts index 8d793efb..ec16373b 100644 --- a/src/Nostr/EventKind.ts +++ b/src/Nostr/EventKind.ts @@ -8,6 +8,7 @@ const enum EventKind { Deletion = 5, // NIP-09 Repost = 6, // NIP-18 Reaction = 7, // NIP-25 + Relays = 10002, // NIP-65 Auth = 22242, // NIP-42 Lists = 30000, // NIP-51 ZapRequest = 9734, // NIP tba diff --git a/src/Nostr/Subscriptions.ts b/src/Nostr/Subscriptions.ts index dc57283a..d22050d8 100644 --- a/src/Nostr/Subscriptions.ts +++ b/src/Nostr/Subscriptions.ts @@ -47,6 +47,11 @@ export class Subscriptions { */ DTags?: Set; + /** + * A litst of "r" tags to search + */ + RTags?: Set; + /** * A list of search terms */ @@ -100,6 +105,7 @@ export class Subscriptions { this.ETags = sub?.["#e"] ? new Set(sub["#e"]) : undefined; this.PTags = sub?.["#p"] ? new Set(sub["#p"]) : undefined; this.DTags = sub?.["#d"] ? new Set(["#d"]) : undefined; + this.RTags = sub?.["#r"] ? new Set(["#r"]) : undefined; this.Search = sub?.search ?? undefined; this.Since = sub?.since ?? undefined; this.Until = sub?.until ?? undefined; @@ -150,6 +156,9 @@ export class Subscriptions { if (this.DTags) { ret["#d"] = Array.from(this.DTags); } + if (this.RTags) { + ret["#r"] = Array.from(this.RTags); + } if (this.Search) { ret.search = this.Search; } diff --git a/src/Nostr/index.ts b/src/Nostr/index.ts index b59491ff..f9ebf853 100644 --- a/src/Nostr/index.ts +++ b/src/Nostr/index.ts @@ -41,6 +41,7 @@ export type RawReqFilter = { "#p"?: u256[]; "#t"?: string[]; "#d"?: string[]; + "#r"?: string[]; search?: string; since?: number; until?: number; diff --git a/src/Pages/ProfilePage.tsx b/src/Pages/ProfilePage.tsx index 7db6adfe..6263272b 100644 --- a/src/Pages/ProfilePage.tsx +++ b/src/Pages/ProfilePage.tsx @@ -4,12 +4,15 @@ import { useIntl, FormattedMessage } from "react-intl"; import { useSelector } from "react-redux"; import { useNavigate, useParams } from "react-router-dom"; +import { unwrap } from "Util"; import { formatShort } from "Number"; +import Relays from "Element/Relays"; import { Tab, TabElement } from "Element/Tabs"; import Link from "Icons/Link"; import Qr from "Icons/Qr"; import Zap from "Icons/Zap"; import Envelope from "Icons/Envelope"; +import useRelaysFeed from "Feed/RelaysFeed"; import { useUserProfile } from "Feed/ProfileFeed"; import useZapsFeed from "Feed/ZapsFeed"; import { default as ZapElement, parseZap } from "Element/Zap"; @@ -46,6 +49,7 @@ const FOLLOWS = 3; const ZAPS = 4; const MUTED = 5; const BLOCKED = 6; +const RELAYS = 7; export default function ProfilePage() { const { formatMessage } = useIntl(); @@ -69,6 +73,7 @@ export default function ProfilePage() { const lnurl = extractLnAddress(user?.lud16 || user?.lud06 || ""); const website_url = user?.website && !user.website.startsWith("http") ? "https://" + user.website : user?.website || ""; + const relays = useRelaysFeed(id); const zapFeed = useZapsFeed(id); const zaps = useMemo(() => { const profileZaps = zapFeed.store.notes.map(parseZap).filter(z => z.valid && z.p === id && !z.e && z.zapper !== id); @@ -85,8 +90,12 @@ export default function ProfilePage() { Zaps: { text: formatMessage(messages.Zaps), value: ZAPS }, Muted: { text: formatMessage(messages.Muted), value: MUTED }, Blocked: { text: formatMessage(messages.Blocked), value: BLOCKED }, + Relays: { text: formatMessage(messages.Relays), value: RELAYS }, }; const [tab, setTab] = useState(ProfileTab.Notes); + const optionalTabs = [zapsTotal > 0 && ProfileTab.Zaps, relays.length > 0 && ProfileTab.Relays].filter(a => + unwrap(a) + ) as Tab[]; useEffect(() => { setTab(ProfileTab.Notes); @@ -204,6 +213,9 @@ export default function ProfilePage() { case BLOCKED: { return isMe ? : null; } + case RELAYS: { + return ; + } } } @@ -282,7 +294,8 @@ export default function ProfilePage() {
- {[ProfileTab.Notes, ProfileTab.Followers, ProfileTab.Follows, ProfileTab.Zaps, ProfileTab.Muted].map(renderTab)} + {[ProfileTab.Notes, ProfileTab.Followers, ProfileTab.Follows, ProfileTab.Muted].map(renderTab)} + {optionalTabs.map(renderTab)} {isMe && renderTab(ProfileTab.Blocked)}
{tabContent()} diff --git a/src/Pages/messages.js b/src/Pages/messages.js new file mode 100644 index 00000000..13e2d269 --- /dev/null +++ b/src/Pages/messages.js @@ -0,0 +1,35 @@ +import { defineMessages } from "react-intl"; + +export default defineMessages({ + Login: "Login", + Posts: "Posts", + Conversations: "Conversations", + Global: "Global", + NewUsers: "New users page", + NoFollows: "Hmm nothing here.. Checkout {newUsersPage} to follow some recommended nostrich's!", + Notes: "Notes", + Reactions: "Reactions", + Followers: "Followers", + Follows: "Follows", + Zaps: "Zaps", + ZapsCount: "{n} Zaps", + Muted: "Muted", + Blocked: "Blocked", + Sats: "{n} {n, plural, =1 {sat} other {sats}}", + Following: "Following {n}", + Settings: "Settings", + Search: "Search", + SearchPlaceholder: "Search...", + Messages: "Messages", + MarkAllRead: "Mark All Read", + GetVerified: "Get Verified", + Nip05: `NIP-05 is a DNS based verification spec which helps to validate you as a real user.`, + Nip05Pros: `Getting NIP-05 verified can help:`, + AvoidImpersonators: "Prevent fake accounts from imitating you", + EasierToFind: "Make your profile easier to find and share", + Funding: "Fund developers and platforms providing NIP-05 verification services", + SnortSocialNip: `Our very own NIP-05 verification service, help support the development of this site and get a shiny special badge on our site!`, + NostrPlebsNip: `Nostr Plebs is one of the first NIP-05 providers in the space and offers a good collection of domains at reasonable prices`, + Relays: "Relays", + RelaysCount: "{n} Relays", +}); diff --git a/src/Pages/settings/Relays.tsx b/src/Pages/settings/Relays.tsx index 455e2e3b..1fdf405e 100644 --- a/src/Pages/settings/Relays.tsx +++ b/src/Pages/settings/Relays.tsx @@ -20,6 +20,8 @@ const RelaySettingsPage = () => { const ev = await publisher.saveRelays(); publisher.broadcast(ev); publisher.broadcastForBootstrap(ev); + let settingsEv = await publisher.saveRelaysSettings(); + publisher.broadcast(settingsEv); } function addRelay() { diff --git a/src/translations/en.json b/src/translations/en.json index d0e89c97..7dc716e1 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -209,4 +209,4 @@ "zjJZBd": "You're ready!", "zonsdq": "Failed to load LNURL service", "zvCDao": "Automatically show latest notes" -} \ No newline at end of file +} diff --git a/src/translations/es.json b/src/translations/es.json index c9590df8..d334f80f 100644 --- a/src/translations/es.json +++ b/src/translations/es.json @@ -111,12 +111,15 @@ "Pages.Notes": "Notas", "Pages.Posts": "Notas", "Pages.Reactions": "Reacciones", + "Pages.Relays": "", + "Pages.RelaysCount": "", "Pages.Sats": "{n} {n, plural, =1 {sat} other {sats}}", "Pages.Search": "Búsqueda", "Pages.SearchPlaceholder": "Buscar...", "Pages.Settings": "Configuración", "Pages.SnortSocialNip": "Nuestro servicio de verificación NIP-05, apoya el desarrollo de este proyecto y obtén una apariencia especial en nuestra web!", "Pages.Zaps": "Zaps", + "Pages.ZapsCount": "", "Pages.new.Bitcoin": "", "Pages.new.Check": "", "Pages.new.DigitalSignatures": "", diff --git a/src/translations/fr.json b/src/translations/fr.json index 074fdf79..226dede0 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -110,12 +110,15 @@ "Pages.Notes": "Notes", "Pages.Posts": "Publications", "Pages.Reactions": "Réactions", + "Pages.Relays": "", + "Pages.RelaysCount": "", "Pages.Sats": "{n} {n, plural, =1 {sat} other {sats}}", "Pages.Search": "Chercher", "Pages.SearchPlaceholder": "Chercher...", "Pages.Settings": "Paramètres", "Pages.SnortSocialNip": "Notre propre service de vérification NIP-05, aidez à soutenir le développement de ce site et obtenez un badge spécial brillant sur notre site !", "Pages.Zaps": "Zaps", + "Pages.ZapsCount": "", "Pages.new.Bitcoin": "", "Pages.new.Check": "", "Pages.new.DigitalSignatures": "", diff --git a/src/translations/ja.json b/src/translations/ja.json index 2171e6bd..54c5e5c4 100644 --- a/src/translations/ja.json +++ b/src/translations/ja.json @@ -112,11 +112,13 @@ "Pages.Posts": "ポスト", "Pages.Reactions": "リアクション", "Pages.Sats": "{n} {n, plural, =1 {sat} other {sats}}", + "Pages.Relays": "", "Pages.Search": "検索", "Pages.SearchPlaceholder": "検索する", "Pages.Settings": "設定", "Pages.SnortSocialNip": "私たち独自のNIP-05認証サービスです。このサイトの開発を支援し、ピカピカの特別なバッジを私たちのサイトで使えるようになります!", "Pages.Zaps": "Zap", + "Pages.ZapsCount": "", "Pages.new.Bitcoin": "", "Pages.new.Check": "", "Pages.new.DigitalSignatures": "", diff --git a/src/translations/zh.json b/src/translations/zh.json index b4f6c6cb..da4d0fc6 100644 --- a/src/translations/zh.json +++ b/src/translations/zh.json @@ -111,12 +111,15 @@ "Pages.Notes": "", "Pages.Posts": "", "Pages.Reactions": "", + "Pages.Relays": "", + "Pages.RelaysCount": "", "Pages.Sats": "", "Pages.Search": "", "Pages.SearchPlaceholder": "", "Pages.Settings": "", "Pages.SnortSocialNip": "", "Pages.Zaps": "", + "Pages.ZapsCount": "", "Pages.new.Bitcoin": "", "Pages.new.Check": "", "Pages.new.DigitalSignatures": "", -- 2.45.2 From 91280dc5f0a0ba6ee03e8154c91156e44a54e212 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 10 Feb 2023 00:40:53 +0100 Subject: [PATCH 02/17] tweak relay font size --- src/Element/Relays.css | 10 ++++++++++ src/Element/Relays.tsx | 2 +- src/Pages/ProfilePage.tsx | 2 -- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Element/Relays.css b/src/Element/Relays.css index e17b06e6..8bfb2fa2 100644 --- a/src/Element/Relays.css +++ b/src/Element/Relays.css @@ -24,3 +24,13 @@ .relay-settings svg.disabled { opacity: 0.3; } + +.relay-url { + font-size: 14px; +} + +@media (min-width: 520px) { + .relay-url { + font-size: 16px; + } +} diff --git a/src/Element/Relays.tsx b/src/Element/Relays.tsx index 82439401..f1a3acec 100644 --- a/src/Element/Relays.tsx +++ b/src/Element/Relays.tsx @@ -28,7 +28,7 @@ const Relays = ({ relays }: RelaysProps) => { return (
- {url} + {url}
diff --git a/src/Pages/ProfilePage.tsx b/src/Pages/ProfilePage.tsx index 6263272b..29b3fc74 100644 --- a/src/Pages/ProfilePage.tsx +++ b/src/Pages/ProfilePage.tsx @@ -19,7 +19,6 @@ import { default as ZapElement, parseZap } from "Element/Zap"; import FollowButton from "Element/FollowButton"; import { extractLnAddress, parseId, hexToBech32 } from "Util"; import Avatar from "Element/Avatar"; -import LogoutButton from "Element/LogoutButton"; import Timeline from "Element/Timeline"; import Text from "Element/Text"; import SendSats from "Element/SendSats"; @@ -241,7 +240,6 @@ export default function ProfilePage() { )} {isMe ? ( <> - -- 2.45.2 From 5233d8fe602d4a8f13bc667c123dd5a94ea7cc8a Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 10 Feb 2023 00:41:59 +0100 Subject: [PATCH 03/17] add nip to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 54f68e5b..7d28d53d 100644 --- a/README.md +++ b/README.md @@ -29,3 +29,4 @@ Snort supports the following NIP's: - [ ] NIP-42: Authentication of clients to relays - [x] NIP-50: Search - [x] NIP-51: Lists +- [x] NIP-65: Relay List Metadata -- 2.45.2 From 8be118db1be4dbac256f0898919e217fb3c3f3c2 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 10 Feb 2023 00:46:59 +0100 Subject: [PATCH 04/17] don't leave relays feed open --- src/Element/Zap.css | 2 +- src/Feed/RelaysFeed.tsx | 2 +- src/Pages/messages.js | 2 -- src/translations/es.json | 2 -- src/translations/fr.json | 2 -- src/translations/ja.json | 1 - src/translations/zh.json | 2 -- 7 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Element/Zap.css b/src/Element/Zap.css index dfc7bedd..d934e65c 100644 --- a/src/Element/Zap.css +++ b/src/Element/Zap.css @@ -17,7 +17,7 @@ @media (max-width: 520px) { .zap .header .amount { - font-size: 21px; + font-size: 16px; } } diff --git a/src/Feed/RelaysFeed.tsx b/src/Feed/RelaysFeed.tsx index 771fa913..4ad25184 100644 --- a/src/Feed/RelaysFeed.tsx +++ b/src/Feed/RelaysFeed.tsx @@ -15,7 +15,7 @@ export default function useRelaysFeed(pubkey: HexKey) { return x; }, [pubkey]); - const relays = useSubscription(sub, { leaveOpen: true, cache: true }); + const relays = useSubscription(sub, { leaveOpen: false, cache: true }); const notes = relays.store.notes; const tags = notes.slice(-1)[0]?.tags || []; return tags.reduce((rs, tag) => { diff --git a/src/Pages/messages.js b/src/Pages/messages.js index 13e2d269..6794b9ef 100644 --- a/src/Pages/messages.js +++ b/src/Pages/messages.js @@ -12,7 +12,6 @@ export default defineMessages({ Followers: "Followers", Follows: "Follows", Zaps: "Zaps", - ZapsCount: "{n} Zaps", Muted: "Muted", Blocked: "Blocked", Sats: "{n} {n, plural, =1 {sat} other {sats}}", @@ -31,5 +30,4 @@ export default defineMessages({ SnortSocialNip: `Our very own NIP-05 verification service, help support the development of this site and get a shiny special badge on our site!`, NostrPlebsNip: `Nostr Plebs is one of the first NIP-05 providers in the space and offers a good collection of domains at reasonable prices`, Relays: "Relays", - RelaysCount: "{n} Relays", }); diff --git a/src/translations/es.json b/src/translations/es.json index d334f80f..dd9e7630 100644 --- a/src/translations/es.json +++ b/src/translations/es.json @@ -112,14 +112,12 @@ "Pages.Posts": "Notas", "Pages.Reactions": "Reacciones", "Pages.Relays": "", - "Pages.RelaysCount": "", "Pages.Sats": "{n} {n, plural, =1 {sat} other {sats}}", "Pages.Search": "Búsqueda", "Pages.SearchPlaceholder": "Buscar...", "Pages.Settings": "Configuración", "Pages.SnortSocialNip": "Nuestro servicio de verificación NIP-05, apoya el desarrollo de este proyecto y obtén una apariencia especial en nuestra web!", "Pages.Zaps": "Zaps", - "Pages.ZapsCount": "", "Pages.new.Bitcoin": "", "Pages.new.Check": "", "Pages.new.DigitalSignatures": "", diff --git a/src/translations/fr.json b/src/translations/fr.json index 226dede0..d8948235 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -111,14 +111,12 @@ "Pages.Posts": "Publications", "Pages.Reactions": "Réactions", "Pages.Relays": "", - "Pages.RelaysCount": "", "Pages.Sats": "{n} {n, plural, =1 {sat} other {sats}}", "Pages.Search": "Chercher", "Pages.SearchPlaceholder": "Chercher...", "Pages.Settings": "Paramètres", "Pages.SnortSocialNip": "Notre propre service de vérification NIP-05, aidez à soutenir le développement de ce site et obtenez un badge spécial brillant sur notre site !", "Pages.Zaps": "Zaps", - "Pages.ZapsCount": "", "Pages.new.Bitcoin": "", "Pages.new.Check": "", "Pages.new.DigitalSignatures": "", diff --git a/src/translations/ja.json b/src/translations/ja.json index 54c5e5c4..aa7f0728 100644 --- a/src/translations/ja.json +++ b/src/translations/ja.json @@ -118,7 +118,6 @@ "Pages.Settings": "設定", "Pages.SnortSocialNip": "私たち独自のNIP-05認証サービスです。このサイトの開発を支援し、ピカピカの特別なバッジを私たちのサイトで使えるようになります!", "Pages.Zaps": "Zap", - "Pages.ZapsCount": "", "Pages.new.Bitcoin": "", "Pages.new.Check": "", "Pages.new.DigitalSignatures": "", diff --git a/src/translations/zh.json b/src/translations/zh.json index da4d0fc6..6ad2e461 100644 --- a/src/translations/zh.json +++ b/src/translations/zh.json @@ -112,14 +112,12 @@ "Pages.Posts": "", "Pages.Reactions": "", "Pages.Relays": "", - "Pages.RelaysCount": "", "Pages.Sats": "", "Pages.Search": "", "Pages.SearchPlaceholder": "", "Pages.Settings": "", "Pages.SnortSocialNip": "", "Pages.Zaps": "", - "Pages.ZapsCount": "", "Pages.new.Bitcoin": "", "Pages.new.Check": "", "Pages.new.DigitalSignatures": "", -- 2.45.2 From 243228f862b43e9a2d0ef0478a72d20dd56f0eed Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 10 Feb 2023 00:48:57 +0100 Subject: [PATCH 05/17] lint fixes --- src/Feed/EventPublisher.ts | 6 +++--- src/Pages/settings/Relays.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Feed/EventPublisher.ts b/src/Feed/EventPublisher.ts index 6a984cee..12159fee 100644 --- a/src/Feed/EventPublisher.ts +++ b/src/Feed/EventPublisher.ts @@ -222,11 +222,11 @@ export default function useEventPublisher() { }, saveRelaysSettings: async () => { if (pubKey) { - let ev = NEvent.ForPubKey(pubKey); + const ev = NEvent.ForPubKey(pubKey); ev.Kind = EventKind.Relays; ev.Content = ""; - for (let [url, settings] of Object.entries(relays)) { - let rTag = ["r", url]; + for (const [url, settings] of Object.entries(relays)) { + const rTag = ["r", url]; if (settings.read) { rTag.push("read"); } diff --git a/src/Pages/settings/Relays.tsx b/src/Pages/settings/Relays.tsx index 1fdf405e..0dce4da4 100644 --- a/src/Pages/settings/Relays.tsx +++ b/src/Pages/settings/Relays.tsx @@ -20,7 +20,7 @@ const RelaySettingsPage = () => { const ev = await publisher.saveRelays(); publisher.broadcast(ev); publisher.broadcastForBootstrap(ev); - let settingsEv = await publisher.saveRelaysSettings(); + const settingsEv = await publisher.saveRelaysSettings(); publisher.broadcast(settingsEv); } -- 2.45.2 From cae91d273b7c12b2ed148f33b436769d5c2f3f35 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 10 Feb 2023 01:08:27 +0100 Subject: [PATCH 06/17] fix --- src/Feed/EventPublisher.ts | 4 ++-- src/Feed/RelaysFeed.tsx | 4 ++-- src/Icons/Gear.tsx | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Feed/EventPublisher.ts b/src/Feed/EventPublisher.ts index 12159fee..aae85c94 100644 --- a/src/Feed/EventPublisher.ts +++ b/src/Feed/EventPublisher.ts @@ -227,10 +227,10 @@ export default function useEventPublisher() { ev.Content = ""; for (const [url, settings] of Object.entries(relays)) { const rTag = ["r", url]; - if (settings.read) { + if (settings.read && !settings.write) { rTag.push("read"); } - if (settings.write) { + if (settings.write && !settings.read) { rTag.push("write"); } ev.Tags.push(new Tag(rTag, ev.Tags.length)); diff --git a/src/Feed/RelaysFeed.tsx b/src/Feed/RelaysFeed.tsx index 4ad25184..ca4aff6e 100644 --- a/src/Feed/RelaysFeed.tsx +++ b/src/Feed/RelaysFeed.tsx @@ -26,8 +26,8 @@ export default function useRelaysFeed(pubkey: HexKey) { { url, settings: { - read: settings.includes("read"), - write: settings.includes("write"), + read: settings.length === 0 || settings.includes("read"), + write: settings.length === 0 || settings.includes("write"), }, }, ]; diff --git a/src/Icons/Gear.tsx b/src/Icons/Gear.tsx index 6c29be98..f12950f0 100644 --- a/src/Icons/Gear.tsx +++ b/src/Icons/Gear.tsx @@ -13,9 +13,9 @@ const Gear = (props: IconProps) => { ); -- 2.45.2 From 8035c8908dc9473607c4ebda9a6733a229ffd690 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 10 Feb 2023 01:38:45 +0100 Subject: [PATCH 07/17] fix --- src/Element/Relays.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Element/Relays.tsx b/src/Element/Relays.tsx index f1a3acec..b1fd9160 100644 --- a/src/Element/Relays.tsx +++ b/src/Element/Relays.tsx @@ -15,7 +15,11 @@ interface RelaysProps { } const RelayFavicon = ({ url }: { url: string }) => { - const cleanUrl = url.replace("wss://relay.", "https://").replace("wss://nostr.", "https://"); + const cleanUrl = url + .replace("wss://relay.", "https://") + .replace("wss://nostr.", "https://") + .replace("wss://", "https://") + .replace(/\/$/, ""); const [faviconUrl, setFaviconUrl] = useState(`${cleanUrl}/favicon.ico`); return setFaviconUrl(Nostrich)} />; -- 2.45.2 From 64b4a843553cefd959b3e519052b779db6e35ef1 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 10 Feb 2023 01:57:08 +0100 Subject: [PATCH 08/17] feat: reaction emoji --- src/Element/NoteFooter.tsx | 4 ++- src/Pages/settings/Preferences.tsx | 35 ++++++++++++++++++ src/Pages/settings/messages.js | 57 ++++++++++++++++++++++++++++++ src/State/Login.ts | 6 ++++ src/translations/es.json | 2 ++ src/translations/fr.json | 2 ++ src/translations/ja.json | 2 ++ src/translations/zh.json | 2 ++ 8 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/Pages/settings/messages.js diff --git a/src/Element/NoteFooter.tsx b/src/Element/NoteFooter.tsx index 44b90586..4b0a7de5 100644 --- a/src/Element/NoteFooter.tsx +++ b/src/Element/NoteFooter.tsx @@ -161,7 +161,9 @@ export default function NoteFooter(props: NoteFooterProps) { } return ( <> -
react("+")}> +
react(prefs.reactionEmoji)}>
diff --git a/src/Pages/settings/Preferences.tsx b/src/Pages/settings/Preferences.tsx index 2f63304e..cf3b89cb 100644 --- a/src/Pages/settings/Preferences.tsx +++ b/src/Pages/settings/Preferences.tsx @@ -5,6 +5,7 @@ import { FormattedMessage } from "react-intl"; import { DefaultImgProxy, setPreferences, UserPreferences } from "State/Login"; import { RootState } from "State/Store"; +import emoji from "@jukben/emoji-search"; import messages from "./messages"; import { unwrap } from "Util"; @@ -197,6 +198,40 @@ const PreferencesPage = () => { />
+
+
+
+ +
+ + + +
+
+ +
+
diff --git a/src/Pages/settings/messages.js b/src/Pages/settings/messages.js new file mode 100644 index 00000000..304770b7 --- /dev/null +++ b/src/Pages/settings/messages.js @@ -0,0 +1,57 @@ +import { defineMessages } from "react-intl"; + +export default defineMessages({ + Profile: "Profile", + Relays: "Relays", + Owner: "Owner", + Software: "Software", + Contact: "Contact", + Supports: "Supports", + Remove: "Remove", + Preferences: "Preferences", + Donate: "Donate", + LogOut: "Log Out", + Theme: "Theme", + System: "System (Default)", + Light: "Light", + Dark: "Dark", + AutoloadMedia: "Automatically load media", + AutoloadMediaHelp: + "Media in posts will automatically be shown for selected people, otherwise only the link will show", + None: "None", + FollowsOnly: "Follows only", + All: "All", + ImgProxy: "Image proxy service", + ImgProxyHelp: "Use imgproxy to compress images", + ServiceUrl: "Service URL", + ServiceKey: "Service Key", + ServiceSalt: "Service Salt", + EnableReactions: "Enable reactions", + EnableReactionsHelp: "Reactions will be shown on every page, if disabled no reactions will be shown", + ConfirmReposts: "Confirm Reposts", + ConfirmRepostsHelp: "Reposts need to be manually confirmed", + ShowLatest: "Automatically show latest notes", + ShowLatestHelp: "Notes will stream in real time into global and posts tab", + FileUpload: "File upload service", + FileUploadHelp: "Pick which upload service you want to upload attachments to", + Default: "(Default)", + DebugMenus: "Debug Menus", + DebugMenusHelp: `Shows "Copy ID" and "Copy Event JSON" in the context menu on each message`, + EditProfile: "Edit Profile", + About: "About", + LnAddress: "LN Address", + Avatar: "Avatar", + Banner: "Banner", + Edit: "Edit", + PrivateKey: "Your Private Key Is (do not share this with anyone)", + Add: "Add", + AddRelays: "Add Relays", + Name: "Name", + Website: "Website", + Save: "Save", + DisplayName: "Display name", + Buy: "Buy", + Nip05: "NIP-05", + ReactionEmoji: "Reaction emoji", + ReactionEmojiHelp: "Emoji that will be used as reaction content", +}); diff --git a/src/State/Login.ts b/src/State/Login.ts index 202aac6a..4863403e 100644 --- a/src/State/Login.ts +++ b/src/State/Login.ts @@ -26,6 +26,11 @@ export interface UserPreferences { */ enableReactions: boolean; + /** + * Reaction emoji + */ + reactionEmoji: string; + /** * Automatically load media (show link only) (bandwidth/privacy) */ @@ -176,6 +181,7 @@ export const InitState = { dmInteraction: 0, preferences: { enableReactions: true, + reactionEmoji: "+1", autoLoadMedia: "follows-only", theme: "system", confirmReposts: false, diff --git a/src/translations/es.json b/src/translations/es.json index dd9e7630..88228f03 100644 --- a/src/translations/es.json +++ b/src/translations/es.json @@ -202,6 +202,8 @@ "Pages.settings.Preferences": "Preferencias", "Pages.settings.PrivateKey": "Tu Clave Privada (no la compartas con nadie) es", "Pages.settings.Profile": "Perfil", + "Pages.settings.ReactionEmoji": "", + "Pages.settings.ReactionEmojiHelp": "", "Pages.settings.Relays": "Relays", "Pages.settings.Remove": "Eliminar", "Pages.settings.Save": "Guardar", diff --git a/src/translations/fr.json b/src/translations/fr.json index d8948235..3ff3305f 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -201,6 +201,8 @@ "Pages.settings.Preferences": "Préférences", "Pages.settings.PrivateKey": "Votre Clé Privée Est (ne la partagez avec personne)", "Pages.settings.Profile": "Profil", + "Pages.settings.ReactionEmoji": "", + "Pages.settings.ReactionEmojiHelp": "", "Pages.settings.Relays": "Relais", "Pages.settings.Remove": "Retirer", "Pages.settings.Save": "Sauvegarder", diff --git a/src/translations/ja.json b/src/translations/ja.json index aa7f0728..ae1c6a44 100644 --- a/src/translations/ja.json +++ b/src/translations/ja.json @@ -202,6 +202,8 @@ "Pages.settings.Preferences": "ユーザー設定", "Pages.settings.PrivateKey": "あなたの秘密鍵(誰とも共有しないこと)", "Pages.settings.Profile": "プロフィール", + "Pages.settings.ReactionEmoji": "", + "Pages.settings.ReactionEmojiHelp": "", "Pages.settings.Relays": "リレー", "Pages.settings.Remove": "削除", "Pages.settings.Save": "保存", diff --git a/src/translations/zh.json b/src/translations/zh.json index 6ad2e461..e8a00cef 100644 --- a/src/translations/zh.json +++ b/src/translations/zh.json @@ -202,6 +202,8 @@ "Pages.settings.Preferences": "", "Pages.settings.PrivateKey": "", "Pages.settings.Profile": "", + "Pages.settings.ReactionEmoji": "", + "Pages.settings.ReactionEmojiHelp": "", "Pages.settings.Relays": "", "Pages.settings.Remove": "", "Pages.settings.Save": "", -- 2.45.2 From 5b5ae2e6b9cffc7215e41ad8affe3b592b2a084e Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 10 Feb 2023 08:22:57 +0100 Subject: [PATCH 09/17] name first --- src/Pages/settings/Preferences.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Pages/settings/Preferences.tsx b/src/Pages/settings/Preferences.tsx index cf3b89cb..307c0bf7 100644 --- a/src/Pages/settings/Preferences.tsx +++ b/src/Pages/settings/Preferences.tsx @@ -225,7 +225,7 @@ const PreferencesPage = () => { {emoji("").map(({ name, char }) => { return ( ); })} -- 2.45.2 From d3761b0973c7363570794b5fbad52db35a435df8 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 10 Feb 2023 12:12:37 +0100 Subject: [PATCH 10/17] fix --- src/State/Login.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/State/Login.ts b/src/State/Login.ts index 4863403e..8e892dd3 100644 --- a/src/State/Login.ts +++ b/src/State/Login.ts @@ -181,7 +181,7 @@ export const InitState = { dmInteraction: 0, preferences: { enableReactions: true, - reactionEmoji: "+1", + reactionEmoji: "+", autoLoadMedia: "follows-only", theme: "system", confirmReposts: false, -- 2.45.2 From c5ad25c3a1aee84fab96928a39afd781936f9611 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 10 Feb 2023 19:20:01 +0100 Subject: [PATCH 11/17] spread message to 20 random relays --- src/Element/Relays.tsx | 9 +++------ src/Feed/EventPublisher.ts | 10 ++++++++++ src/Feed/RelaysFeed.tsx | 5 ++--- src/Nostr/Connection.ts | 2 +- src/Nostr/index.ts | 5 +++++ src/Pages/Layout.tsx | 6 +++--- src/Pages/messages.ts | 3 +++ src/Pages/settings/Relays.tsx | 11 +++++++++-- src/Pages/settings/messages.ts | 2 ++ src/Util.ts | 5 +++++ 10 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/Element/Relays.tsx b/src/Element/Relays.tsx index b1fd9160..2a0cca13 100644 --- a/src/Element/Relays.tsx +++ b/src/Element/Relays.tsx @@ -2,16 +2,12 @@ import "./Relays.css"; import Nostrich from "nostrich.webp"; import { useState } from "react"; +import { RelaySettings } from "Nostr"; import Read from "Icons/Read"; import Write from "Icons/Write"; -export interface RelaySpec { - url: string; - settings: { read: boolean; write: boolean }; -} - interface RelaysProps { - relays: RelaySpec[]; + relays: RelaySettings[]; } const RelayFavicon = ({ url }: { url: string }) => { @@ -19,6 +15,7 @@ const RelayFavicon = ({ url }: { url: string }) => { .replace("wss://relay.", "https://") .replace("wss://nostr.", "https://") .replace("wss://", "https://") + .replace("ws://", "http://") .replace(/\/$/, ""); const [faviconUrl, setFaviconUrl] = useState(`${cleanUrl}/favicon.ico`); diff --git a/src/Feed/EventPublisher.ts b/src/Feed/EventPublisher.ts index aae85c94..cbb83b2a 100644 --- a/src/Feed/EventPublisher.ts +++ b/src/Feed/EventPublisher.ts @@ -109,6 +109,16 @@ export default function useEventPublisher() { } } }, + /** + * Write event to all given relays. + */ + broadcastAll: (ev: NEvent | undefined, relays: string[]) => { + if (ev) { + for (const k of relays) { + System.WriteOnceToRelay(k, ev); + } + } + }, muted: async (keys: HexKey[], priv: HexKey[]) => { if (pubKey) { const ev = NEvent.ForPubKey(pubKey); diff --git a/src/Feed/RelaysFeed.tsx b/src/Feed/RelaysFeed.tsx index ca4aff6e..eb6d1221 100644 --- a/src/Feed/RelaysFeed.tsx +++ b/src/Feed/RelaysFeed.tsx @@ -1,7 +1,6 @@ import { useMemo } from "react"; -import { HexKey } from "Nostr"; +import { HexKey, RelaySettings } from "Nostr"; import EventKind from "Nostr/EventKind"; -import { RelaySpec } from "Element/Relays"; import { Subscriptions } from "Nostr/Subscriptions"; import useSubscription from "./Subscription"; @@ -33,5 +32,5 @@ export default function useRelaysFeed(pubkey: HexKey) { ]; } return rs; - }, [] as RelaySpec[]); + }, [] as RelaySettings[]); } diff --git a/src/Nostr/Connection.ts b/src/Nostr/Connection.ts index 12913e85..d07cead0 100644 --- a/src/Nostr/Connection.ts +++ b/src/Nostr/Connection.ts @@ -179,7 +179,7 @@ export default class Connection { } case "OK": { // feedback to broadcast call - console.debug("OK: ", msg); + console.debug(`${this.Address} OK: `, msg); const id = msg[1]; if (this.EventsCallback.has(id)) { const cb = unwrap(this.EventsCallback.get(id)); diff --git a/src/Nostr/index.ts b/src/Nostr/index.ts index f9ebf853..f3949914 100644 --- a/src/Nostr/index.ts +++ b/src/Nostr/index.ts @@ -69,3 +69,8 @@ export type UserMetadata = { export enum Lists { Muted = "mute", } + +export interface RelaySettings { + url: string; + settings: { read: boolean; write: boolean }; +} diff --git a/src/Pages/Layout.tsx b/src/Pages/Layout.tsx index 4697e5d8..db90a88a 100644 --- a/src/Pages/Layout.tsx +++ b/src/Pages/Layout.tsx @@ -2,10 +2,11 @@ import "./Layout.css"; import { useEffect, useMemo, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { Outlet, useLocation, useNavigate } from "react-router-dom"; + +import { randomSample } from "Util"; import Envelope from "Icons/Envelope"; import Bell from "Icons/Bell"; import Search from "Icons/Search"; - import { RootState } from "State/Store"; import { init, setRelays } from "State/Login"; import { System } from "Nostr/System"; @@ -147,8 +148,7 @@ export default function Layout() { const rsp = await fetch("https://api.nostr.watch/v1/online"); if (rsp.ok) { const online: string[] = await rsp.json(); - const pickRandom = online.sort(() => (Math.random() >= 0.5 ? 1 : -1)).slice(0, 4); // pick 4 random relays - + const pickRandom = randomSample(online, 4); const relayObjects = pickRandom.map(a => [a, { read: true, write: true }]); newRelays = Object.fromEntries(relayObjects); dispatch( diff --git a/src/Pages/messages.ts b/src/Pages/messages.ts index 8510cf83..e23f47f7 100644 --- a/src/Pages/messages.ts +++ b/src/Pages/messages.ts @@ -33,4 +33,7 @@ export default defineMessages({ NostrPlebsNip: { defaultMessage: `Nostr Plebs is one of the first NIP-05 providers in the space and offers a good collection of domains at reasonable prices`, }, + Relays: { + defaultMessage: "Relays", + }, }); diff --git a/src/Pages/settings/Relays.tsx b/src/Pages/settings/Relays.tsx index 0dce4da4..90559118 100644 --- a/src/Pages/settings/Relays.tsx +++ b/src/Pages/settings/Relays.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { FormattedMessage } from "react-intl"; import { useDispatch, useSelector } from "react-redux"; +import { randomSample } from "Util"; import Relay from "Element/Relay"; import useEventPublisher from "Feed/EventPublisher"; import { RootState } from "State/Store"; @@ -20,8 +21,14 @@ const RelaySettingsPage = () => { const ev = await publisher.saveRelays(); publisher.broadcast(ev); publisher.broadcastForBootstrap(ev); - const settingsEv = await publisher.saveRelaysSettings(); - publisher.broadcast(settingsEv); + try { + const relays = await fetch("https://api.nostr.watch/v1/online").then(r => r.json()); + const settingsEv = await publisher.saveRelaysSettings(); + publisher.broadcast(ev); + publisher.broadcastAll(settingsEv, randomSample(relays, 20)); + } catch (error) { + console.error(error); + } } function addRelay() { diff --git a/src/Pages/settings/messages.ts b/src/Pages/settings/messages.ts index c28bc88b..11cdf501 100644 --- a/src/Pages/settings/messages.ts +++ b/src/Pages/settings/messages.ts @@ -55,4 +55,6 @@ export default defineMessages({ DisplayName: { defaultMessage: "Display name" }, Buy: { defaultMessage: "Buy" }, Nip05: { defaultMessage: "NIP-05" }, + ReactionEmoji: { defaultMessage: "Reaction emoji" }, + ReactionEmojiHelp: { defaultMessage: "Emoji to send when reactiong to a note" }, }); diff --git a/src/Util.ts b/src/Util.ts index 2e14ec2b..b63c2168 100644 --- a/src/Util.ts +++ b/src/Util.ts @@ -185,3 +185,8 @@ export function unwrap(v: T | undefined | null): T { } return v; } + +export function randomSample(coll: T[], size: number) { + const random = [...coll]; + return random.sort(() => (Math.random() >= 0.5 ? 1 : -1)).slice(0, size); +} -- 2.45.2 From 8931f2c6c6e72999c124977e7ff9ffb28b223c35 Mon Sep 17 00:00:00 2001 From: Alejandro Gomez Date: Fri, 10 Feb 2023 19:21:52 +0100 Subject: [PATCH 12/17] remove old files --- src/Pages/messages.js | 33 ----------------- src/Pages/settings/Preferences.tsx | 4 +-- src/Pages/settings/messages.js | 57 ------------------------------ 3 files changed, 2 insertions(+), 92 deletions(-) delete mode 100644 src/Pages/messages.js delete mode 100644 src/Pages/settings/messages.js diff --git a/src/Pages/messages.js b/src/Pages/messages.js deleted file mode 100644 index 6794b9ef..00000000 --- a/src/Pages/messages.js +++ /dev/null @@ -1,33 +0,0 @@ -import { defineMessages } from "react-intl"; - -export default defineMessages({ - Login: "Login", - Posts: "Posts", - Conversations: "Conversations", - Global: "Global", - NewUsers: "New users page", - NoFollows: "Hmm nothing here.. Checkout {newUsersPage} to follow some recommended nostrich's!", - Notes: "Notes", - Reactions: "Reactions", - Followers: "Followers", - Follows: "Follows", - Zaps: "Zaps", - Muted: "Muted", - Blocked: "Blocked", - Sats: "{n} {n, plural, =1 {sat} other {sats}}", - Following: "Following {n}", - Settings: "Settings", - Search: "Search", - SearchPlaceholder: "Search...", - Messages: "Messages", - MarkAllRead: "Mark All Read", - GetVerified: "Get Verified", - Nip05: `NIP-05 is a DNS based verification spec which helps to validate you as a real user.`, - Nip05Pros: `Getting NIP-05 verified can help:`, - AvoidImpersonators: "Prevent fake accounts from imitating you", - EasierToFind: "Make your profile easier to find and share", - Funding: "Fund developers and platforms providing NIP-05 verification services", - SnortSocialNip: `Our very own NIP-05 verification service, help support the development of this site and get a shiny special badge on our site!`, - NostrPlebsNip: `Nostr Plebs is one of the first NIP-05 providers in the space and offers a good collection of domains at reasonable prices`, - Relays: "Relays", -}); diff --git a/src/Pages/settings/Preferences.tsx b/src/Pages/settings/Preferences.tsx index 307c0bf7..71db4d9c 100644 --- a/src/Pages/settings/Preferences.tsx +++ b/src/Pages/settings/Preferences.tsx @@ -219,8 +219,8 @@ const PreferencesPage = () => { } as UserPreferences) ) }> -