From e949708ceca60e3dc83a157b97351e18ce576c7c Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Fri, 6 Oct 2023 13:13:49 +0300 Subject: [PATCH 01/19] extract ProfileTab from ProfilePage --- .../src/Pages/{ => Profile}/ProfilePage.css | 0 .../src/Pages/{ => Profile}/ProfilePage.tsx | 170 ++---------------- packages/app/src/Pages/Profile/ProfileTab.tsx | 154 ++++++++++++++++ packages/app/src/index.tsx | 2 +- 4 files changed, 172 insertions(+), 154 deletions(-) rename packages/app/src/Pages/{ => Profile}/ProfilePage.css (100%) rename packages/app/src/Pages/{ => Profile}/ProfilePage.tsx (75%) create mode 100644 packages/app/src/Pages/Profile/ProfileTab.tsx diff --git a/packages/app/src/Pages/ProfilePage.css b/packages/app/src/Pages/Profile/ProfilePage.css similarity index 100% rename from packages/app/src/Pages/ProfilePage.css rename to packages/app/src/Pages/Profile/ProfilePage.css diff --git a/packages/app/src/Pages/ProfilePage.tsx b/packages/app/src/Pages/Profile/ProfilePage.tsx similarity index 75% rename from packages/app/src/Pages/ProfilePage.tsx rename to packages/app/src/Pages/Profile/ProfilePage.tsx index 5c2501ad..67ae1e62 100644 --- a/packages/app/src/Pages/ProfilePage.tsx +++ b/packages/app/src/Pages/Profile/ProfilePage.tsx @@ -6,8 +6,6 @@ import { encodeTLV, encodeTLVEntries, EventKind, - HexKey, - NostrLink, NostrPrefix, TLVEntryType, tryParseNostrLink, @@ -16,22 +14,14 @@ import { LNURL } from "@snort/shared"; import { useUserProfile } from "@snort/system-react"; import { findTag, getReactions, unwrap } from "SnortUtils"; -import { formatShort } from "Number"; import Note from "Element/Event/Note"; -import Bookmarks from "Element/Bookmarks"; -import RelaysMetadata from "Element/Relay/RelaysMetadata"; import { Tab, TabElement } from "Element/Tabs"; import Icon from "Icons/Icon"; import useMutedFeed from "Feed/MuteList"; -import useRelaysFeed from "Feed/RelaysFeed"; import usePinnedFeed from "Feed/PinnedFeed"; -import useBookmarkFeed from "Feed/BookmarkFeed"; -import useFollowersFeed from "Feed/FollowersFeed"; import useFollowsFeed from "Feed/FollowsFeed"; import useProfileBadges from "Feed/BadgesFeed"; import useModeration from "Hooks/useModeration"; -import useZapsFeed from "Feed/ZapsFeed"; -import { default as ZapElement } from "Element/Event/Zap"; import FollowButton from "Element/User/FollowButton"; import { parseId, hexToBech32 } from "SnortUtils"; import Avatar from "Element/User/Avatar"; @@ -57,59 +47,16 @@ import useLogin from "Hooks/useLogin"; import { ZapTarget } from "Zapper"; import { useStatusFeed } from "Feed/StatusFeed"; -import messages from "./messages"; +import messages from "../messages"; import { SpotlightMediaModal } from "Element/Deck/SpotlightMedia"; - -const NOTES = 0; -const REACTIONS = 1; -const FOLLOWERS = 2; -const FOLLOWS = 3; -const ZAPS = 4; -const MUTED = 5; -const BLOCKED = 6; -const RELAYS = 7; -const BOOKMARKS = 8; - -function ZapsProfileTab({ id }: { id: HexKey }) { - const zaps = useZapsFeed(new NostrLink(NostrPrefix.PublicKey, id)); - const zapsTotal = zaps.reduce((acc, z) => acc + z.amount, 0); - return ( -
-

- -

- {zaps.map(z => ( - - ))} -
- ); -} - -function FollowersTab({ id }: { id: HexKey }) { - const followers = useFollowersFeed(id); - return ; -} - -function FollowsTab({ id }: { id: HexKey }) { - const follows = useFollowsFeed(id); - return ; -} - -function RelaysTab({ id }: { id: HexKey }) { - const relays = useRelaysFeed(id); - return ; -} - -function BookMarksTab({ id }: { id: HexKey }) { - const bookmarks = useBookmarkFeed(id); - return ( - e.kind === EventKind.TextNote)} - related={bookmarks.filter(e => e.kind !== EventKind.TextNote)} - /> - ); -} +import ProfileTab, { + BookMarksTab, + FollowersTab, + FollowsTab, + ProfileTabType, + RelaysTab, + ZapsProfileTab +} from "Pages/Profile/ProfileTab"; export default function ProfilePage() { const params = useParams(); @@ -146,89 +93,6 @@ export default function ProfilePage() { const status = useStatusFeed(showStatus ? id : undefined, true); // tabs - const ProfileTab = { - Notes: { - text: ( - <> - - - - ), - value: NOTES, - }, - Reactions: { - text: ( - <> - - - - ), - value: REACTIONS, - }, - Followers: { - text: ( - <> - - - - ), - value: FOLLOWERS, - }, - Follows: { - text: ( - <> - - - - ), - value: FOLLOWS, - }, - Zaps: { - text: ( - <> - - - - ), - value: ZAPS, - }, - Muted: { - text: ( - <> - - - - ), - value: MUTED, - }, - Blocked: { - text: ( - <> - - - - ), - value: BLOCKED, - }, - Relays: { - text: ( - <> - - - - ), - value: RELAYS, - }, - Bookmarks: { - text: ( - <> - - - - ), - value: BOOKMARKS, - }, - } as { [key: string]: Tab }; const [tab, setTab] = useState(ProfileTab.Notes); const optionalTabs = [ProfileTab.Zaps, ProfileTab.Relays, ProfileTab.Bookmarks, ProfileTab.Muted].filter(a => unwrap(a), @@ -369,7 +233,7 @@ export default function ProfilePage() { if (!id) return null; switch (tab.value) { - case NOTES: + case ProfileTabType.NOTES: return ( <> {pinned @@ -399,29 +263,29 @@ export default function ProfilePage() { /> ); - case ZAPS: { + case ProfileTabType.ZAPS: { return ; } - case FOLLOWS: { + case ProfileTabType.FOLLOWS: { if (isMe) { return ; } else { return ; } } - case FOLLOWERS: { + case ProfileTabType.FOLLOWERS: { return ; } - case MUTED: { + case ProfileTabType.MUTED: { return ; } - case BLOCKED: { + case ProfileTabType.BLOCKED: { return ; } - case RELAYS: { + case ProfileTabType.RELAYS: { return ; } - case BOOKMARKS: { + case ProfileTabType.BOOKMARKS: { return ; } } diff --git a/packages/app/src/Pages/Profile/ProfileTab.tsx b/packages/app/src/Pages/Profile/ProfileTab.tsx new file mode 100644 index 00000000..f4e53478 --- /dev/null +++ b/packages/app/src/Pages/Profile/ProfileTab.tsx @@ -0,0 +1,154 @@ +import useZapsFeed from "../../Feed/ZapsFeed"; +import FormattedMessage from "../../Element/FormattedMessage"; +import messages from "../messages"; +import {formatShort} from "../../Number"; +import useFollowersFeed from "../../Feed/FollowersFeed"; +import FollowsList from "../../Element/User/FollowListBase"; +import useFollowsFeed from "../../Feed/FollowsFeed"; +import useRelaysFeed from "../../Feed/RelaysFeed"; +import RelaysMetadata from "../../Element/Relay/RelaysMetadata"; +import useBookmarkFeed from "../../Feed/BookmarkFeed"; +import Bookmarks from "../../Element/Bookmarks"; +import Icon from "../../Icons/Icon"; +import {Tab} from "../../Element/Tabs"; +import {EventKind, HexKey, NostrLink, NostrPrefix} from "@snort/system"; +import { default as ZapElement } from "Element/Event/Zap"; + +export enum ProfileTabType { + NOTES = 0, + REACTIONS = 1, + FOLLOWERS = 2, + FOLLOWS = 3, + ZAPS = 4, + MUTED = 5, + BLOCKED = 6, + RELAYS = 7, + BOOKMARKS = 8, +} + +export function ZapsProfileTab({ id }: { id: HexKey }) { + const zaps = useZapsFeed(new NostrLink(NostrPrefix.PublicKey, id)); + const zapsTotal = zaps.reduce((acc, z) => acc + z.amount, 0); + return ( +
+

+ +

+ {zaps.map(z => ( + + ))} +
+ ); +} + +export function FollowersTab({ id }: { id: HexKey }) { + const followers = useFollowersFeed(id); + return ; +} + +export function FollowsTab({ id }: { id: HexKey }) { + const follows = useFollowsFeed(id); + return ; +} + +export function RelaysTab({ id }: { id: HexKey }) { + const relays = useRelaysFeed(id); + return ; +} + +export function BookMarksTab({ id }: { id: HexKey }) { + const bookmarks = useBookmarkFeed(id); + return ( + e.kind === EventKind.TextNote)} + related={bookmarks.filter(e => e.kind !== EventKind.TextNote)} + /> + ); +} + +const ProfileTab = { + Notes: { + text: ( + <> + + + + ), + value: ProfileTabType.NOTES, + }, + Reactions: { + text: ( + <> + + + + ), + value: ProfileTabType.REACTIONS, + }, + Followers: { + text: ( + <> + + + + ), + value: ProfileTabType.FOLLOWERS, + }, + Follows: { + text: ( + <> + + + + ), + value: ProfileTabType.FOLLOWS, + }, + Zaps: { + text: ( + <> + + + + ), + value: ProfileTabType.ZAPS, + }, + Muted: { + text: ( + <> + + + + ), + value: ProfileTabType.MUTED, + }, + Blocked: { + text: ( + <> + + + + ), + value: ProfileTabType.BLOCKED, + }, + Relays: { + text: ( + <> + + + + ), + value: ProfileTabType.RELAYS, + }, + Bookmarks: { + text: ( + <> + + + + ), + value: ProfileTabType.BOOKMARKS, + }, + } as { [key: string]: Tab }; + +export default ProfileTab; \ No newline at end of file diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 35b5d009..8b599091 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -24,7 +24,7 @@ import { IntlProvider } from "IntlProvider"; import { unwrap } from "SnortUtils"; import Layout from "Pages/Layout"; import LoginPage from "Pages/LoginPage"; -import ProfilePage from "Pages/ProfilePage"; +import ProfilePage from "Pages/Profile/ProfilePage"; import { RootRoutes, RootTabRoutes } from "Pages/Root"; import NotificationsPage from "Pages/Notifications"; import SettingsPage, { SettingsRoutes } from "Pages/SettingsPage"; From 9f5d467745d29cfa3cbf601cccf3d9b9a32975a9 Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Fri, 6 Oct 2023 14:06:12 +0300 Subject: [PATCH 02/19] body overflow-y: scroll to reduce layout shift --- packages/app/src/index.css | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app/src/index.css b/packages/app/src/index.css index a038ce28..daabc122 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -105,6 +105,7 @@ body { color: var(--font-color); font-size: var(--font-size); overflow-x: hidden; + overflow-y: scroll; } code { From 3f7ac9e2d4518ef419a018f63110c442c64b0edd Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Fri, 6 Oct 2023 15:03:18 +0300 Subject: [PATCH 03/19] if username@{NIP05_DOMAIN} valid, change profile page url to /username --- .../app/src/Pages/Profile/ProfilePage.tsx | 33 +++- packages/app/src/Pages/Profile/ProfileTab.tsx | 172 +++++++++--------- 2 files changed, 109 insertions(+), 96 deletions(-) diff --git a/packages/app/src/Pages/Profile/ProfilePage.tsx b/packages/app/src/Pages/Profile/ProfilePage.tsx index 67ae1e62..da23fc3e 100644 --- a/packages/app/src/Pages/Profile/ProfilePage.tsx +++ b/packages/app/src/Pages/Profile/ProfilePage.tsx @@ -2,14 +2,7 @@ import "./ProfilePage.css"; import { useEffect, useState } from "react"; import FormattedMessage from "Element/FormattedMessage"; import { useNavigate, useParams } from "react-router-dom"; -import { - encodeTLV, - encodeTLVEntries, - EventKind, - NostrPrefix, - TLVEntryType, - tryParseNostrLink, -} from "@snort/system"; +import { encodeTLV, encodeTLVEntries, EventKind, NostrPrefix, TLVEntryType, tryParseNostrLink } from "@snort/system"; import { LNURL } from "@snort/shared"; import { useUserProfile } from "@snort/system-react"; @@ -28,7 +21,7 @@ import Avatar from "Element/User/Avatar"; import Timeline from "Element/Feed/Timeline"; import Text from "Element/Text"; import SendSats from "Element/SendSats"; -import Nip05 from "Element/User/Nip05"; +import Nip05, { useIsVerified } from "Element/User/Nip05"; import Copy from "Element/Copy"; import ProfileImage from "Element/User/ProfileImage"; import BlockList from "Element/User/BlockList"; @@ -55,7 +48,7 @@ import ProfileTab, { FollowsTab, ProfileTabType, RelaysTab, - ZapsProfileTab + ZapsProfileTab, } from "Pages/Profile/ProfileTab"; export default function ProfilePage() { @@ -71,6 +64,7 @@ export default function ProfilePage() { const [modalImage, setModalImage] = useState(""); const aboutText = user?.about || ""; const npub = !id?.startsWith(NostrPrefix.PublicKey) ? hexToBech32(NostrPrefix.PublicKey, id || undefined) : id; + const { isVerified } = useIsVerified(user?.pubkey || ""); const lnurl = (() => { try { @@ -139,6 +133,25 @@ export default function ProfilePage() { return inner(); } + useEffect(() => { + const replaceWith = (username: string) => { + const current = window.location.pathname; + const npub = hexToBech32(NostrPrefix.PublicKey, user?.pubkey); + if (current.endsWith(`/${npub}`)) { + window.history.replaceState?.(null, "", `/${username}`); + } + }; + if (user?.nip05) { + if (user.nip05.endsWith(`@${process.env.NIP05_DOMAIN}`)) { + const username = user.nip05?.replace(`@${process.env.NIP05_DOMAIN}`, ""); + replaceWith(username); + } else { + // do we want this? would need to support urls with dots like /fiatjaf.com + // replaceWith(user.nip05?.replace(/^_@/, "")); + } + } + }, [isVerified, user?.pubkey]); + function username() { return ( <> diff --git a/packages/app/src/Pages/Profile/ProfileTab.tsx b/packages/app/src/Pages/Profile/ProfileTab.tsx index f4e53478..c03a536a 100644 --- a/packages/app/src/Pages/Profile/ProfileTab.tsx +++ b/packages/app/src/Pages/Profile/ProfileTab.tsx @@ -1,7 +1,7 @@ import useZapsFeed from "../../Feed/ZapsFeed"; import FormattedMessage from "../../Element/FormattedMessage"; import messages from "../messages"; -import {formatShort} from "../../Number"; +import { formatShort } from "../../Number"; import useFollowersFeed from "../../Feed/FollowersFeed"; import FollowsList from "../../Element/User/FollowListBase"; import useFollowsFeed from "../../Feed/FollowsFeed"; @@ -10,8 +10,8 @@ import RelaysMetadata from "../../Element/Relay/RelaysMetadata"; import useBookmarkFeed from "../../Feed/BookmarkFeed"; import Bookmarks from "../../Element/Bookmarks"; import Icon from "../../Icons/Icon"; -import {Tab} from "../../Element/Tabs"; -import {EventKind, HexKey, NostrLink, NostrPrefix} from "@snort/system"; +import { Tab } from "../../Element/Tabs"; +import { EventKind, HexKey, NostrLink, NostrPrefix } from "@snort/system"; import { default as ZapElement } from "Element/Event/Zap"; export enum ProfileTabType { @@ -68,87 +68,87 @@ export function BookMarksTab({ id }: { id: HexKey }) { } const ProfileTab = { - Notes: { - text: ( - <> - - - - ), - value: ProfileTabType.NOTES, - }, - Reactions: { - text: ( - <> - - - - ), - value: ProfileTabType.REACTIONS, - }, - Followers: { - text: ( - <> - - - - ), - value: ProfileTabType.FOLLOWERS, - }, - Follows: { - text: ( - <> - - - - ), - value: ProfileTabType.FOLLOWS, - }, - Zaps: { - text: ( - <> - - - - ), - value: ProfileTabType.ZAPS, - }, - Muted: { - text: ( - <> - - - - ), - value: ProfileTabType.MUTED, - }, - Blocked: { - text: ( - <> - - - - ), - value: ProfileTabType.BLOCKED, - }, - Relays: { - text: ( - <> - - - - ), - value: ProfileTabType.RELAYS, - }, - Bookmarks: { - text: ( - <> - - - - ), - value: ProfileTabType.BOOKMARKS, - }, - } as { [key: string]: Tab }; + Notes: { + text: ( + <> + + + + ), + value: ProfileTabType.NOTES, + }, + Reactions: { + text: ( + <> + + + + ), + value: ProfileTabType.REACTIONS, + }, + Followers: { + text: ( + <> + + + + ), + value: ProfileTabType.FOLLOWERS, + }, + Follows: { + text: ( + <> + + + + ), + value: ProfileTabType.FOLLOWS, + }, + Zaps: { + text: ( + <> + + + + ), + value: ProfileTabType.ZAPS, + }, + Muted: { + text: ( + <> + + + + ), + value: ProfileTabType.MUTED, + }, + Blocked: { + text: ( + <> + + + + ), + value: ProfileTabType.BLOCKED, + }, + Relays: { + text: ( + <> + + + + ), + value: ProfileTabType.RELAYS, + }, + Bookmarks: { + text: ( + <> + + + + ), + value: ProfileTabType.BOOKMARKS, + }, +} as { [key: string]: Tab }; -export default ProfileTab; \ No newline at end of file +export default ProfileTab; From 5ed096509aa57ff1c042033934bfa575ff3402e4 Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Sun, 8 Oct 2023 15:27:41 +0300 Subject: [PATCH 04/19] use DisplayName on profile page --- packages/app/src/Pages/Profile/ProfilePage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/app/src/Pages/Profile/ProfilePage.tsx b/packages/app/src/Pages/Profile/ProfilePage.tsx index da23fc3e..1a1320a1 100644 --- a/packages/app/src/Pages/Profile/ProfilePage.tsx +++ b/packages/app/src/Pages/Profile/ProfilePage.tsx @@ -50,6 +50,7 @@ import ProfileTab, { RelaysTab, ZapsProfileTab, } from "Pages/Profile/ProfileTab"; +import DisplayName from "../../Element/User/DisplayName"; export default function ProfilePage() { const params = useParams(); @@ -157,7 +158,7 @@ export default function ProfilePage() { <>

- {user?.display_name || user?.name || "Nostrich"} +

{user?.nip05 && } From 091169ae7da89bf5617134380b26f59c08fc28d4 Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Sun, 8 Oct 2023 16:21:56 +0300 Subject: [PATCH 05/19] return note or profile component directly from NostrLinkHandler --- packages/app/src/Element/Event/Thread.tsx | 5 +++-- packages/app/src/Pages/NostrLinkHandler.tsx | 19 ++++++++++++------- .../app/src/Pages/Profile/ProfilePage.tsx | 17 +++++++++++------ 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/packages/app/src/Element/Event/Thread.tsx b/packages/app/src/Element/Event/Thread.tsx index 6fd8192c..20ccb721 100644 --- a/packages/app/src/Element/Event/Thread.tsx +++ b/packages/app/src/Element/Event/Thread.tsx @@ -205,9 +205,10 @@ const TierThree = ({ active, isLastSubthread, notes, related, chains, onNavigate ); }; -export function ThreadRoute() { +export function ThreadRoute({ id }: { id?: string }) { const params = useParams(); - const link = parseNostrLink(params.id ?? "", NostrPrefix.Note); + const resolvedId = id ?? params.id; + const link = parseNostrLink(resolvedId ?? "", NostrPrefix.Note); return ( diff --git a/packages/app/src/Pages/NostrLinkHandler.tsx b/packages/app/src/Pages/NostrLinkHandler.tsx index 99217d26..801ab794 100644 --- a/packages/app/src/Pages/NostrLinkHandler.tsx +++ b/packages/app/src/Pages/NostrLinkHandler.tsx @@ -1,32 +1,33 @@ import { NostrPrefix, tryParseNostrLink } from "@snort/system"; import { useEffect, useState } from "react"; import FormattedMessage from "Element/FormattedMessage"; -import { useNavigate, useParams } from "react-router-dom"; +import { useParams } from "react-router-dom"; import Spinner from "Icons/Spinner"; -import { profileLink } from "SnortUtils"; import { getNip05PubKey } from "Pages/LoginPage"; +import ProfilePage from "Pages/Profile/ProfilePage"; +import { ThreadRoute } from "Element/Event/Thread"; export default function NostrLinkHandler() { const params = useParams(); - const navigate = useNavigate(); - const [loading, setLoading] = useState(true); + const [renderComponent, setRenderComponent] = useState(null); + const link = decodeURIComponent(params["*"] ?? "").toLowerCase(); async function handleLink(link: string) { const nav = tryParseNostrLink(link); if (nav) { if (nav.type === NostrPrefix.Event || nav.type === NostrPrefix.Note || nav.type === NostrPrefix.Address) { - navigate(`/e/${nav.encode()}`); + setRenderComponent(); // Directly render ThreadRoute } else if (nav.type === NostrPrefix.PublicKey || nav.type === NostrPrefix.Profile) { - navigate(`/p/${nav.encode()}`); + setRenderComponent(); // Directly render ProfilePage } } else { try { const pubkey = await getNip05PubKey(`${link}@${process.env.NIP05_DOMAIN}`); if (pubkey) { - navigate(profileLink(pubkey)); + setRenderComponent(); // Directly render ProfilePage } } catch { //ignored @@ -41,6 +42,10 @@ export default function NostrLinkHandler() { } }, [link]); + if (renderComponent) { + return renderComponent; + } + return (
{loading ? ( diff --git a/packages/app/src/Pages/Profile/ProfilePage.tsx b/packages/app/src/Pages/Profile/ProfilePage.tsx index 1a1320a1..d0833a93 100644 --- a/packages/app/src/Pages/Profile/ProfilePage.tsx +++ b/packages/app/src/Pages/Profile/ProfilePage.tsx @@ -52,7 +52,11 @@ import ProfileTab, { } from "Pages/Profile/ProfileTab"; import DisplayName from "../../Element/User/DisplayName"; -export default function ProfilePage() { +interface ProfilePageProps { + id?: string; +} + +export default function ProfilePage({ id: propId }: ProfilePageProps) { const params = useParams(); const navigate = useNavigate(); const [id, setId] = useState(); @@ -95,21 +99,22 @@ export default function ProfilePage() { const horizontalScroll = useHorizontalScroll(); useEffect(() => { - if (params.id?.match(EmailRegex)) { - getNip05PubKey(params.id).then(a => { + const resolvedId = propId || params.id; + if (resolvedId?.match(EmailRegex)) { + getNip05PubKey(resolvedId).then(a => { setId(a); }); } else { - const nav = tryParseNostrLink(params.id ?? ""); + const nav = tryParseNostrLink(resolvedId ?? ""); if (nav?.type === NostrPrefix.PublicKey || nav?.type === NostrPrefix.Profile) { // todo: use relays if any for nprofile setId(nav.id); } else { - setId(parseId(params.id ?? "")); + setId(parseId(resolvedId ?? "")); } } setTab(ProfileTab.Notes); - }, [params]); + }, [propId, params]); function musicStatus() { if (!status.music) return; From 224960a11f476c428b959a05f1721a2675c2bdd1 Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Sun, 8 Oct 2023 16:40:23 +0300 Subject: [PATCH 06/19] add spacing before media and link embeds --- packages/app/src/Element/Text.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/app/src/Element/Text.tsx b/packages/app/src/Element/Text.tsx index 31fea6dc..c96640c5 100644 --- a/packages/app/src/Element/Text.tsx +++ b/packages/app/src/Element/Text.tsx @@ -170,6 +170,9 @@ export default function Text({ } if (element.type === "media" && element.mimeType?.startsWith("image")) { + if (i > 0) { + chunks.push(
); + } if (disableMedia ?? false) { chunks.push(); } else { @@ -231,6 +234,9 @@ export default function Text({ element.type === "media" && (element.mimeType?.startsWith("audio") || element.mimeType?.startsWith("video")) ) { + if (i > 0) { + chunks.push(
); + } if (disableMedia ?? false) { chunks.push(); } else { @@ -247,6 +253,9 @@ export default function Text({ chunks.push(); } if (element.type === "link" || (element.type === "media" && element.mimeType?.startsWith("unknown"))) { + if (i > 0 && !disableLinkPreview) { + chunks.push(
); + } chunks.push( , ); From 79ef1470234b167454cc0b7db6295aa117a6bee3 Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Mon, 9 Oct 2023 15:44:47 +0300 Subject: [PATCH 07/19] simpler profile url nip05 replacement --- .../app/src/Pages/Profile/ProfilePage.tsx | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/packages/app/src/Pages/Profile/ProfilePage.tsx b/packages/app/src/Pages/Profile/ProfilePage.tsx index d0833a93..96d2da78 100644 --- a/packages/app/src/Pages/Profile/ProfilePage.tsx +++ b/packages/app/src/Pages/Profile/ProfilePage.tsx @@ -21,7 +21,7 @@ import Avatar from "Element/User/Avatar"; import Timeline from "Element/Feed/Timeline"; import Text from "Element/Text"; import SendSats from "Element/SendSats"; -import Nip05, { useIsVerified } from "Element/User/Nip05"; +import Nip05 from "Element/User/Nip05"; import Copy from "Element/Copy"; import ProfileImage from "Element/User/ProfileImage"; import BlockList from "Element/User/BlockList"; @@ -69,7 +69,6 @@ export default function ProfilePage({ id: propId }: ProfilePageProps) { const [modalImage, setModalImage] = useState(""); const aboutText = user?.about || ""; const npub = !id?.startsWith(NostrPrefix.PublicKey) ? hexToBech32(NostrPrefix.PublicKey, id || undefined) : id; - const { isVerified } = useIsVerified(user?.pubkey || ""); const lnurl = (() => { try { @@ -140,23 +139,13 @@ export default function ProfilePage({ id: propId }: ProfilePageProps) { } useEffect(() => { - const replaceWith = (username: string) => { - const current = window.location.pathname; - const npub = hexToBech32(NostrPrefix.PublicKey, user?.pubkey); - if (current.endsWith(`/${npub}`)) { - window.history.replaceState?.(null, "", `/${username}`); - } - }; - if (user?.nip05) { + if (user?.nip05 && user?.isNostrAddressValid) { if (user.nip05.endsWith(`@${process.env.NIP05_DOMAIN}`)) { const username = user.nip05?.replace(`@${process.env.NIP05_DOMAIN}`, ""); - replaceWith(username); - } else { - // do we want this? would need to support urls with dots like /fiatjaf.com - // replaceWith(user.nip05?.replace(/^_@/, "")); + navigate(`/${username}`, { replace: true }); } } - }, [isVerified, user?.pubkey]); + }, [user?.isNostrAddressValid, user?.nip05]); function username() { return ( From dbaad8bbb335a49899103a4bc5c817851cbb7069 Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Mon, 9 Oct 2023 15:58:01 +0300 Subject: [PATCH 08/19] make useIsVerified pubkey param optional --- packages/app/src/Element/User/Nip05.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/Element/User/Nip05.tsx b/packages/app/src/Element/User/Nip05.tsx index 2f9da25a..be43cee5 100644 --- a/packages/app/src/Element/User/Nip05.tsx +++ b/packages/app/src/Element/User/Nip05.tsx @@ -2,7 +2,7 @@ import "./Nip05.css"; import { HexKey } from "@snort/system"; import { useUserProfile } from "@snort/system-react"; -export function useIsVerified(pubkey: HexKey, bypassCheck?: boolean) { +export function useIsVerified(pubkey?: HexKey, bypassCheck?: boolean) { const profile = useUserProfile(pubkey); return { isVerified: bypassCheck || profile?.isNostrAddressValid }; } From 15fb4cabdf4b838522d42ffedf96c30bc2076dc9 Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Mon, 9 Oct 2023 16:06:47 +0300 Subject: [PATCH 09/19] css selector based margin-top --- packages/app/src/Element/Text.css | 4 ++++ packages/app/src/Element/Text.tsx | 9 --------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/app/src/Element/Text.css b/packages/app/src/Element/Text.css index 4a458edb..8dbbbffe 100644 --- a/packages/app/src/Element/Text.css +++ b/packages/app/src/Element/Text.css @@ -113,3 +113,7 @@ height: 100%; display: block; } + +.gallery:not(:first-child), img:not(:first-child), video:not(:first-child), .link-preview-container:not(:first-child) { + margin-top: 10px; +} \ No newline at end of file diff --git a/packages/app/src/Element/Text.tsx b/packages/app/src/Element/Text.tsx index c96640c5..31fea6dc 100644 --- a/packages/app/src/Element/Text.tsx +++ b/packages/app/src/Element/Text.tsx @@ -170,9 +170,6 @@ export default function Text({ } if (element.type === "media" && element.mimeType?.startsWith("image")) { - if (i > 0) { - chunks.push(
); - } if (disableMedia ?? false) { chunks.push(); } else { @@ -234,9 +231,6 @@ export default function Text({ element.type === "media" && (element.mimeType?.startsWith("audio") || element.mimeType?.startsWith("video")) ) { - if (i > 0) { - chunks.push(
); - } if (disableMedia ?? false) { chunks.push(); } else { @@ -253,9 +247,6 @@ export default function Text({ chunks.push(); } if (element.type === "link" || (element.type === "media" && element.mimeType?.startsWith("unknown"))) { - if (i > 0 && !disableLinkPreview) { - chunks.push(
); - } chunks.push( , ); From b27bb47007778d2526f3137fa1bc4568d1b9b59e Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 9 Oct 2023 14:35:21 +0100 Subject: [PATCH 10/19] Notification summary --- packages/app/package.json | 2 + packages/app/public/icons.svg | 11 +- packages/app/src/Cache/index.ts | 10 +- packages/app/src/Element/Event/Reactions.css | 2 +- packages/app/src/Element/Tabs.css | 2 +- packages/app/src/Element/Tabs.tsx | 2 +- packages/app/src/Pages/Notifications.css | 26 ++ packages/app/src/Pages/Notifications.tsx | 172 ++++++++- packages/app/src/SnortUtils/index.ts | 4 + packages/app/src/index.css | 47 ++- packages/app/src/index.tsx | 3 +- packages/app/src/lang.json | 10 + packages/app/src/translations/en.json | 5 +- packages/system-web/package.json | 25 ++ .../cache/db.ts => system-web/src/index.ts} | 16 +- packages/system-web/tsconfig.json | 18 + packages/system/src/cache/events.ts | 7 +- packages/system/src/cache/index.ts | 14 +- packages/system/src/cache/relay-metric.ts | 8 +- packages/system/src/cache/user-metadata.ts | 12 +- packages/system/src/cache/user-relays.ts | 8 +- packages/system/src/nostr-system.ts | 13 +- yarn.lock | 353 +++++++++++++++++- 23 files changed, 710 insertions(+), 60 deletions(-) create mode 100644 packages/system-web/package.json rename packages/{system/src/cache/db.ts => system-web/src/index.ts} (61%) create mode 100644 packages/system-web/tsconfig.json diff --git a/packages/app/package.json b/packages/app/package.json index add96928..c6820064 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -13,6 +13,7 @@ "@snort/system": "workspace:*", "@snort/system-react": "workspace:*", "@snort/system-wasm": "workspace:*", + "@snort/system-web": "workspace:*", "@szhsin/react-menu": "^3.3.1", "@types/use-sync-external-store": "^0.0.4", "@void-cat/api": "^1.0.4", @@ -30,6 +31,7 @@ "react-router-dom": "^6.5.0", "react-textarea-autosize": "^8.4.0", "react-twitter-embed": "^4.0.4", + "recharts": "^2.8.0", "use-long-press": "^3.2.0", "use-sync-external-store": "^1.2.0", "uuid": "^9.0.0", diff --git a/packages/app/public/icons.svg b/packages/app/public/icons.svg index f7e27883..ae164d36 100644 --- a/packages/app/public/icons.svg +++ b/packages/app/public/icons.svg @@ -339,9 +339,14 @@ - - - + + + + + + + + \ No newline at end of file diff --git a/packages/app/src/Cache/index.ts b/packages/app/src/Cache/index.ts index 63c1a0f7..ee9a9522 100644 --- a/packages/app/src/Cache/index.ts +++ b/packages/app/src/Cache/index.ts @@ -1,4 +1,6 @@ import { UserProfileCache, UserRelaysCache, RelayMetricCache } from "@snort/system"; +import { SnortSystemDb } from "@snort/system-web"; + import { EventInteractionCache } from "./EventInteractionCache"; import { ChatCache } from "./ChatCache"; import { Payments } from "./PaymentsCache"; @@ -6,9 +8,11 @@ import { GiftWrapCache } from "./GiftWrapCache"; import { NotificationsCache } from "./Notifications"; import { FollowsFeedCache } from "./FollowsFeed"; -export const UserCache = new UserProfileCache(); -export const UserRelays = new UserRelaysCache(); -export const RelayMetrics = new RelayMetricCache(); +export const SystemDb = new SnortSystemDb(); +export const UserCache = new UserProfileCache(SystemDb.users); +export const UserRelays = new UserRelaysCache(SystemDb.userRelays); +export const RelayMetrics = new RelayMetricCache(SystemDb.relayMetrics); + export const Chats = new ChatCache(); export const PaymentsCache = new Payments(); export const InteractionCache = new EventInteractionCache(); diff --git a/packages/app/src/Element/Event/Reactions.css b/packages/app/src/Element/Event/Reactions.css index 1ce3ca5f..f6612b25 100644 --- a/packages/app/src/Element/Event/Reactions.css +++ b/packages/app/src/Element/Event/Reactions.css @@ -1,6 +1,6 @@ .reactions-modal .modal-body { padding: 24px 32px; - background-color: #1b1b1b; + background-color: var(--gray-superdark); border-radius: 16px; position: relative; min-height: 33vh; diff --git a/packages/app/src/Element/Tabs.css b/packages/app/src/Element/Tabs.css index 9ee1a86c..d0e8b380 100644 --- a/packages/app/src/Element/Tabs.css +++ b/packages/app/src/Element/Tabs.css @@ -34,7 +34,7 @@ border-radius: 100px; font-weight: 600; font-size: 16px; - padding: 10px 16px; + padding: 6px 12px; display: flex; align-items: center; justify-items: center; diff --git a/packages/app/src/Element/Tabs.tsx b/packages/app/src/Element/Tabs.tsx index 427610f3..318afefc 100644 --- a/packages/app/src/Element/Tabs.tsx +++ b/packages/app/src/Element/Tabs.tsx @@ -31,7 +31,7 @@ export const TabElement = ({ t, tab, setTab }: TabElementProps) => { const Tabs = ({ tabs, tab, setTab }: TabsProps) => { const horizontalScroll = useHorizontalScroll(); return ( -
+
{tabs.map(t => ( ))} diff --git a/packages/app/src/Pages/Notifications.css b/packages/app/src/Pages/Notifications.css index e4ab399a..b5bb6904 100644 --- a/packages/app/src/Pages/Notifications.css +++ b/packages/app/src/Pages/Notifications.css @@ -52,3 +52,29 @@ max-width: 100%; max-height: 300px; /* Cap images in notifications to 300px height */ } + +.summary-icon { + padding: 4px; + border-radius: 8px; + display: flex; + align-items: center; + cursor: pointer; + color: var(--gray-light) !important; +} + +.summary-icon:not(.active):hover { + background-color: var(--gray-dark); +} + +.summary-icon.active { + background: rgba(255, 255, 255, 0.1); +} + +.summary-tooltip { + display: flex; + gap: 12px; + padding: 12px 16px; + border-radius: 16px; + background: var(--gray-superdark); + box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.05); +} diff --git a/packages/app/src/Pages/Notifications.tsx b/packages/app/src/Pages/Notifications.tsx index a3784007..fbbc835e 100644 --- a/packages/app/src/Pages/Notifications.tsx +++ b/packages/app/src/Pages/Notifications.tsx @@ -1,16 +1,17 @@ import "./Notifications.css"; -import { useEffect, useMemo, useSyncExternalStore } from "react"; +import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react"; import { EventExt, EventKind, NostrEvent, NostrLink, NostrPrefix, TaggedNostrEvent, parseZap } from "@snort/system"; -import { unwrap } from "@snort/shared"; +import { unixNow, unwrap } from "@snort/shared"; import { useUserProfile } from "@snort/system-react"; import { useInView } from "react-intersection-observer"; import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router-dom"; +import { Bar, BarChart, Tooltip, XAxis, YAxis } from "recharts"; import useLogin from "Hooks/useLogin"; import { markNotificationsRead } from "Login"; import { Notifications, UserCache } from "Cache"; -import { dedupe, findTag, orderDescending } from "SnortUtils"; +import { dedupe, findTag, orderAscending, orderDescending } from "SnortUtils"; import Icon from "Icons/Icon"; import ProfileImage from "Element/User/ProfileImage"; import useModeration from "Hooks/useModeration"; @@ -20,6 +21,8 @@ import { formatShort } from "Number"; import { LiveEvent } from "Element/LiveEvent"; import ProfilePreview from "Element/User/ProfilePreview"; import { getDisplayName } from "Element/User/DisplayName"; +import { Day } from "Const"; +import Tabs, { Tab } from "Element/Tabs"; function notificationContext(ev: TaggedNostrEvent) { switch (ev.kind) { @@ -93,12 +96,175 @@ export default function NotificationsPage({ onClick }: { onClick?: (link: NostrL return (
+ + {login.publicKey && [...timeGrouped.entries()].map(([k, g]) => )}
); } +interface StatSlot { + time: string; + reactions: number; + reposts: number; + quotes: number; + mentions: number; + zaps: number; +} + +const enum NotificationSummaryPeriod { + Daily, + Weekly, +} + +const enum NotificationSummaryFilter { + Reactions = 1, + Reposts = 2, + Mentions = 4, + Zaps = 8, + All = 255, +} + +function NotificationSummary({ evs }: { evs: Array }) { + const ref = useRef(null); + const [period, setPeriod] = useState(NotificationSummaryPeriod.Daily); + const [filter, setFilter] = useState(NotificationSummaryFilter.All); + + const periodTabs = [ + { + value: NotificationSummaryPeriod.Daily, + text: , + }, + { + value: NotificationSummaryPeriod.Weekly, + text: , + }, + ] as Array; + + const hasFlag = (v: number, f: NotificationSummaryFilter) => { + return (v & f) > 0; + }; + + const getWeek = (d: Date) => { + const onejan = new Date(d.getFullYear(), 0, 1); + const today = new Date(d.getFullYear(), d.getMonth(), d.getDate()); + const dayOfYear = (today.getTime() - onejan.getTime() + 86400000) / 86400000; + return Math.ceil(dayOfYear / 7); + }; + + const stats = useMemo(() => { + return orderAscending(evs) + .filter(a => (period === NotificationSummaryPeriod.Daily ? a.created_at > unixNow() - 14 * Day : true)) + .reduce( + (acc, v) => { + const date = new Date(v.created_at * 1000); + const key = + period === NotificationSummaryPeriod.Daily + ? `${date.getMonth() + 1}/${date.getDate()}` + : getWeek(date).toString(); + acc[key] ??= { + time: key, + reactions: 0, + reposts: 0, + quotes: 0, + mentions: 0, + zaps: 0, + }; + + if (v.kind === EventKind.Reaction) { + acc[key].reactions++; + } else if (v.kind === EventKind.Repost) { + acc[key].reposts++; + } else if (v.kind === EventKind.ZapReceipt) { + acc[key].zaps++; + } + if (v.kind === EventKind.TextNote) { + acc[key].mentions++; + } + + return acc; + }, + {} as Record, + ); + }, [evs, period]); + + const filterIcon = (f: NotificationSummaryFilter, icon: string, iconActiveClass?: string) => { + const active = hasFlag(filter, f); + return ( +
setFilter(v => v ^ f)}> + +
+ ); + }; + + return ( +
+
+

+ +

+
+ {filterIcon(NotificationSummaryFilter.Reactions, "heart-solid", "text-heart")} + {filterIcon(NotificationSummaryFilter.Zaps, "zap-solid", "text-zap")} + {filterIcon(NotificationSummaryFilter.Reposts, "reverse-left", "text-repost")} + {filterIcon(NotificationSummaryFilter.Mentions, "at-sign", "text-mention")} +
+
+ a.value === period))} setTab={t => setPeriod(t.value)} /> +
+ + + + {hasFlag(filter, NotificationSummaryFilter.Reactions) && ( + + )} + {hasFlag(filter, NotificationSummaryFilter.Reposts) && ( + + )} + {hasFlag(filter, NotificationSummaryFilter.Mentions) && ( + + )} + {hasFlag(filter, NotificationSummaryFilter.Zaps) && } + { + if (active && payload && payload.length) { + return ( +
+
+ + {formatShort(payload.find(a => a.name === "reactions")?.value as number)} +
+
+ + {formatShort(payload.find(a => a.name === "zaps")?.value as number)} +
+
+ + {formatShort(payload.find(a => a.name === "reposts")?.value as number)} +
+
+ + {formatShort(payload.find(a => a.name === "mentions")?.value as number)} +
+
+ ); + } + return null; + }} + /> +
+
+
+ ); +} + function NotificationGroup({ evs, onClick }: { evs: Array; onClick?: (link: NostrLink) => void }) { const { ref, inView } = useInView({ triggerOnce: true }); const { formatMessage } = useIntl(); diff --git a/packages/app/src/SnortUtils/index.ts b/packages/app/src/SnortUtils/index.ts index 65062b2a..52018793 100644 --- a/packages/app/src/SnortUtils/index.ts +++ b/packages/app/src/SnortUtils/index.ts @@ -328,6 +328,10 @@ export function orderDescending(arr: Array) { return arr.sort((a, b) => (b.created_at > a.created_at ? 1 : -1)); } +export function orderAscending(arr: Array) { + return arr.sort((a, b) => (b.created_at > a.created_at ? -1 : 1)); +} + export interface Magnet { dn?: string | string[]; tr?: string | string[]; diff --git a/packages/app/src/index.css b/packages/app/src/index.css index a038ce28..456f4bba 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -15,6 +15,8 @@ --live: #f83838; --heart: #ef4444; --zap: #ff710a; + --mention: #961ee1; + --repost: #1ecbe1; --gray-superlight: #eee; --bg-secondary: #2a2a2a; --gray-light: #999; @@ -137,6 +139,10 @@ code { border: 1px solid var(--border-color); } +.bb { + border-bottom: 1px solid var(--border-color); +} + .bg-primary { background: var(--primary-gradient); } @@ -149,6 +155,10 @@ code { padding: 12px 16px; } +.p4 { + padding: 4px; +} + .p24 { padding: 24px; } @@ -619,6 +629,38 @@ div.form-col { background-color: var(--success); } +.bg-zap { + background-color: var(--zap); +} + +.bg-heart { + background-color: var(--heart); +} + +.bg-mention { + background-color: var(--mention); +} + +.bg-repost { + background-color: var(--repost); +} + +.text-zap { + color: var(--zap); +} + +.text-heart { + color: var(--heart); +} + +.text-mention { + color: var(--mention); +} + +.text-repost { + color: var(--repost); +} + .tweet { display: flex; align-items: center; @@ -664,11 +706,6 @@ div.form-col { font-weight: 600; font-size: 26px; line-height: 36px; - margin: 12px 0 0 0; -} - -.main-content .h4 { - margin-bottom: 25px; } .main-content .profile-preview { diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 35b5d009..d63bad18 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -42,7 +42,7 @@ import { SubscribeRoutes } from "Pages/subscribe"; import ZapPoolPage from "Pages/ZapPool"; import DebugPage from "Pages/Debug"; import { db } from "Db"; -import { preload, RelayMetrics, UserCache, UserRelays } from "Cache"; +import { preload, RelayMetrics, SystemDb, UserCache, UserRelays } from "Cache"; import { LoginStore } from "Login"; import { SnortDeckLayout } from "Pages/DeckLayout"; import FreeNostrAddressPage from "./Pages/FreeNostrAddressPage"; @@ -77,6 +77,7 @@ export const System = new NostrSystem({ profileCache: UserCache, relayMetrics: RelayMetrics, queryOptimizer: WasmQueryOptimizer, + db: SystemDb, authHandler: async (c, r) => { const { id } = LoginStore.snapshot(); const pub = LoginStore.getPublisher(id); diff --git a/packages/app/src/lang.json b/packages/app/src/lang.json index c6ad88b5..db331317 100644 --- a/packages/app/src/lang.json +++ b/packages/app/src/lang.json @@ -42,6 +42,9 @@ "/Xf4UW": { "defaultMessage": "Send anonymous usage metrics" }, + "/clOBU": { + "defaultMessage": "Weekly" + }, "/d6vEc": { "defaultMessage": "Make your profile easier to find and share" }, @@ -704,6 +707,10 @@ "PCSt5T": { "defaultMessage": "Preferences" }, + "PJeJFc": { + "defaultMessage": "Summary", + "description": "Notifications summary" + }, "PLSbmL": { "defaultMessage": "Your mnemonic phrase" }, @@ -1508,5 +1515,8 @@ }, "zwb6LR": { "defaultMessage": "Mint: {url}" + }, + "zxvhnE": { + "defaultMessage": "Daily" } } diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json index bfb58612..70111264 100644 --- a/packages/app/src/translations/en.json +++ b/packages/app/src/translations/en.json @@ -13,6 +13,7 @@ "/PCavi": "Public", "/RD0e2": "Nostr uses digital signature technology to provide tamper proof notes which can safely be replicated to many relays to provide redundant storage of your content.", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "Make your profile easier to find and share", "/n5KSF": "{n} ms", "00LcfG": "Load more", @@ -231,6 +232,7 @@ "P7FD0F": "System (Default)", "P7nJT9": "Total today (UTC): {amount} sats", "PCSt5T": "Preferences", + "PJeJFc": "Summary", "PLSbmL": "Your mnemonic phrase", "PaN7t3": "Preview on {site}", "PamNxw": "Unknown file header: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "You're ready!", "zonsdq": "Failed to load LNURL service", "zvCDao": "Automatically show latest notes", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/system-web/package.json b/packages/system-web/package.json new file mode 100644 index 00000000..08db3588 --- /dev/null +++ b/packages/system-web/package.json @@ -0,0 +1,25 @@ +{ + "name": "@snort/system-web", + "version": "1.0.0", + "description": "Web based components @snort/system", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "repository": "https://git.v0l.io/Kieran/snort", + "author": "Kieran", + "license": "MIT", + "scripts": { + "build": "rm -rf dist && tsc" + }, + "files": [ + "src", + "dist" + ], + "dependencies": { + "@snort/shared": "^1.0.6", + "@snort/system": "^1.0.21", + "dexie": "^3.2.4" + }, + "devDependencies": { + "typescript": "^5.2.2" + } +} diff --git a/packages/system/src/cache/db.ts b/packages/system-web/src/index.ts similarity index 61% rename from packages/system/src/cache/db.ts rename to packages/system-web/src/index.ts index 108c9cbb..1f3366d2 100644 --- a/packages/system/src/cache/db.ts +++ b/packages/system-web/src/index.ts @@ -1,6 +1,5 @@ -import { DexieLike, DexieTableLike } from "@snort/shared"; -import { MetadataCache, RelayMetrics, UsersRelays } from "."; -import { NostrEvent } from "../nostr"; +import { NostrEvent, MetadataCache, RelayMetrics, UsersRelays } from "@snort/system"; +import Dexie, { Table } from "dexie"; const NAME = "snort-system"; const VERSION = 2; @@ -12,13 +11,12 @@ const STORES = { events: "++id, pubkey, created_at", }; -export class SnortSystemDb extends DexieLike { +export class SnortSystemDb extends Dexie { ready = false; - users!: DexieTableLike; - relayMetrics!: DexieTableLike; - userRelays!: DexieTableLike; - events!: DexieTableLike; - dms!: DexieTableLike; + users!: Table; + relayMetrics!: Table; + userRelays!: Table; + events!: Table; constructor() { super(NAME); diff --git a/packages/system-web/tsconfig.json b/packages/system-web/tsconfig.json new file mode 100644 index 00000000..70340f1f --- /dev/null +++ b/packages/system-web/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "baseUrl": "src", + "target": "ESNext", + "moduleResolution": "Bundler", + "esModuleInterop": true, + "noImplicitOverride": true, + "jsx": "react-jsx", + "strict": true, + "declaration": true, + "declarationMap": true, + "inlineSourceMap": true, + "outDir": "dist", + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "files": ["src/index.ts"] +} diff --git a/packages/system/src/cache/events.ts b/packages/system/src/cache/events.ts index 59457cbe..f41ddd5b 100644 --- a/packages/system/src/cache/events.ts +++ b/packages/system/src/cache/events.ts @@ -1,10 +1,9 @@ import { NostrEvent } from "nostr"; -import { db } from "."; -import { FeedCache } from "@snort/shared"; +import { DexieTableLike, FeedCache } from "@snort/shared"; export class EventsCache extends FeedCache { - constructor() { - super("EventsCache", db.events); + constructor(table?: DexieTableLike) { + super("EventsCache", table); } key(of: NostrEvent): string { diff --git a/packages/system/src/cache/index.ts b/packages/system/src/cache/index.ts index 5065a740..f8870e9f 100644 --- a/packages/system/src/cache/index.ts +++ b/packages/system/src/cache/index.ts @@ -1,8 +1,5 @@ import { FullRelaySettings, HexKey, NostrEvent, UserMetadata } from ".."; -import { hexToBech32, unixNowMs } from "@snort/shared"; -import { SnortSystemDb } from "./db"; - -export const db = new SnortSystemDb(); +import { hexToBech32, unixNowMs, DexieTableLike } from "@snort/shared"; export interface MetadataCache extends UserMetadata { /** @@ -71,3 +68,12 @@ export function mapEventToProfile(ev: NostrEvent) { console.error("Failed to parse JSON", ev, e); } } + +export interface SnortSystemDb { + users: DexieTableLike; + relayMetrics: DexieTableLike; + userRelays: DexieTableLike; + events: DexieTableLike; + + isAvailable(): Promise; +} diff --git a/packages/system/src/cache/relay-metric.ts b/packages/system/src/cache/relay-metric.ts index 2eb735bd..dd697c0a 100644 --- a/packages/system/src/cache/relay-metric.ts +++ b/packages/system/src/cache/relay-metric.ts @@ -1,9 +1,9 @@ -import { db, RelayMetrics } from "."; -import { FeedCache } from "@snort/shared"; +import { RelayMetrics } from "."; +import { DexieTableLike, FeedCache } from "@snort/shared"; export class RelayMetricCache extends FeedCache { - constructor() { - super("RelayMetrics", db.relayMetrics); + constructor(table?: DexieTableLike) { + super("RelayMetrics", table); } key(of: RelayMetrics): string { diff --git a/packages/system/src/cache/user-metadata.ts b/packages/system/src/cache/user-metadata.ts index 5693a54b..496ef7f9 100644 --- a/packages/system/src/cache/user-metadata.ts +++ b/packages/system/src/cache/user-metadata.ts @@ -1,12 +1,12 @@ -import { db, MetadataCache } from "."; -import { fetchNip05Pubkey, FeedCache, LNURL } from "@snort/shared"; +import { MetadataCache } from "."; +import { fetchNip05Pubkey, FeedCache, LNURL, DexieTableLike } from "@snort/shared"; export class UserProfileCache extends FeedCache { #zapperQueue: Array<{ pubkey: string; lnurl: string }> = []; #nip5Queue: Array<{ pubkey: string; nip05: string }> = []; - constructor() { - super("UserCache", db.users); + constructor(table?: DexieTableLike) { + super("UserCache", table); this.#processZapperQueue(); this.#processNip5Queue(); } @@ -24,10 +24,10 @@ export class UserProfileCache extends FeedCache { } async search(q: string): Promise> { - if (db.ready) { + if (this.table) { // on-disk cache will always have more data return ( - await db.users + await this.table .where("npub") .startsWithIgnoreCase(q) .or("name") diff --git a/packages/system/src/cache/user-relays.ts b/packages/system/src/cache/user-relays.ts index 7381e341..2e98ab5f 100644 --- a/packages/system/src/cache/user-relays.ts +++ b/packages/system/src/cache/user-relays.ts @@ -1,9 +1,9 @@ -import { db, UsersRelays } from "."; -import { FeedCache } from "@snort/shared"; +import { UsersRelays } from "."; +import { DexieTableLike, FeedCache } from "@snort/shared"; export class UserRelaysCache extends FeedCache { - constructor() { - super("UserRelays", db.userRelays); + constructor(table?: DexieTableLike) { + super("UserRelays", table); } key(of: UsersRelays): string { diff --git a/packages/system/src/nostr-system.ts b/packages/system/src/nostr-system.ts index 31e20987..b06ffed7 100644 --- a/packages/system/src/nostr-system.ts +++ b/packages/system/src/nostr-system.ts @@ -16,8 +16,8 @@ import { UserProfileCache, UserRelaysCache, RelayMetricCache, - db, UsersRelays, + SnortSystemDb, } from "."; import { EventsCache } from "./cache/events"; import { RelayCache } from "./gossip-model"; @@ -87,19 +87,21 @@ export class NostrSystem extends ExternalStore implements System relayMetrics?: FeedCache; eventsCache?: FeedCache; queryOptimizer?: QueryOptimizer; + db?: SnortSystemDb; }) { super(); this.#handleAuth = props.authHandler; - this.#relayCache = props.relayCache ?? new UserRelaysCache(); - this.#profileCache = props.profileCache ?? new UserProfileCache(); - this.#relayMetricsCache = props.relayMetrics ?? new RelayMetricCache(); - this.#eventsCache = props.eventsCache ?? new EventsCache(); + this.#relayCache = props.relayCache ?? new UserRelaysCache(props.db?.userRelays); + this.#profileCache = props.profileCache ?? new UserProfileCache(props.db?.users); + this.#relayMetricsCache = props.relayMetrics ?? new RelayMetricCache(props.db?.relayMetrics); + this.#eventsCache = props.eventsCache ?? new EventsCache(props.db?.events); this.#queryOptimizer = props.queryOptimizer ?? DefaultQueryOptimizer; this.#profileLoader = new ProfileLoaderService(this, this.#profileCache); this.#relayMetrics = new RelayMetricHandler(this.#relayMetricsCache); this.#cleanup(); } + HandleAuth?: AuthHandler | undefined; get ProfileLoader() { @@ -122,7 +124,6 @@ export class NostrSystem extends ExternalStore implements System * Setup caches */ async Init() { - db.ready = await db.isAvailable(); const t = [ this.#relayCache.preload(), this.#profileCache.preload(), diff --git a/yarn.lock b/yarn.lock index 2c26368b..00f048c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1376,6 +1376,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.1.2": + version: 7.23.1 + resolution: "@babel/runtime@npm:7.23.1" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: 0cd0d43e6e7dc7f9152fda8c8312b08321cda2f56ef53d6c22ebdd773abdc6f5d0a69008de90aa41908d00e2c1facb24715ff121274e689305c858355ff02c70 + languageName: node + linkType: hard + "@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.22.6, @babel/runtime@npm:^7.8.4": version: 7.22.11 resolution: "@babel/runtime@npm:7.22.11" @@ -2692,6 +2701,7 @@ __metadata: "@snort/system": "workspace:*" "@snort/system-react": "workspace:*" "@snort/system-wasm": "workspace:*" + "@snort/system-web": "workspace:*" "@szhsin/react-menu": ^3.3.1 "@types/debug": ^4.1.8 "@types/jest": ^29.5.1 @@ -2735,6 +2745,7 @@ __metadata: react-router-dom: ^6.5.0 react-textarea-autosize: ^8.4.0 react-twitter-embed: ^4.0.4 + recharts: ^2.8.0 source-map-loader: ^4.0.1 terser-webpack-plugin: ^5.3.9 tinybench: ^2.5.1 @@ -2799,6 +2810,17 @@ __metadata: languageName: unknown linkType: soft +"@snort/system-web@workspace:*, @snort/system-web@workspace:packages/system-web": + version: 0.0.0-use.local + resolution: "@snort/system-web@workspace:packages/system-web" + dependencies: + "@snort/shared": ^1.0.6 + "@snort/system": ^1.0.21 + dexie: ^3.2.4 + typescript: ^5.2.2 + languageName: unknown + linkType: soft + "@snort/system@^1.0.21, @snort/system@workspace:*, @snort/system@workspace:packages/system": version: 0.0.0-use.local resolution: "@snort/system@workspace:packages/system" @@ -3161,6 +3183,75 @@ __metadata: languageName: node linkType: hard +"@types/d3-array@npm:^3.0.3": + version: 3.0.8 + resolution: "@types/d3-array@npm:3.0.8" + checksum: d5a678f1dc3af05bc6beb675d59a11d9b2ad4ea59fb5b6c2b99980fec947d89a9562f3ac3a8d192a4f38152d3a4b9ee9cf4e2a30788eaacaed5de4a6da514e10 + languageName: node + linkType: hard + +"@types/d3-color@npm:*": + version: 3.1.1 + resolution: "@types/d3-color@npm:3.1.1" + checksum: 1fa67a6d11386c2727c942ab0ddffaca2289ba01d2f3cd0723afc78c291e9515dbdc6de082466d9e9c360d7c67ddbf313707456c0daa9aa14acb2d48cb3bcabb + languageName: node + linkType: hard + +"@types/d3-ease@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/d3-ease@npm:3.0.0" + checksum: 1be7c993643b5a08332e0ee146375a3845545d8deb423db5d152e0b061524385d2345ceccf968f75f605247b940dd3f9a144335fee2e7d935cddaf187afb7095 + languageName: node + linkType: hard + +"@types/d3-interpolate@npm:^3.0.1": + version: 3.0.2 + resolution: "@types/d3-interpolate@npm:3.0.2" + dependencies: + "@types/d3-color": "*" + checksum: 86a1c4853c70663cba970d5c57dca995f604a70684b17bc5ff3ba83ce4e2c13f0105af29bb383ee70c4ccb1920c0dd4aeb352ae8721864d4a503a110260b9b13 + languageName: node + linkType: hard + +"@types/d3-path@npm:*": + version: 3.0.0 + resolution: "@types/d3-path@npm:3.0.0" + checksum: af7f45ea912cddd794c03384baba856f11e1f9b57a49d05a66a61968dafaeb86e0e42394883118b9b8ccadce21a5f25b1f9a88ad05235e1dc6d24c3e34a379ff + languageName: node + linkType: hard + +"@types/d3-scale@npm:^4.0.2": + version: 4.0.5 + resolution: "@types/d3-scale@npm:4.0.5" + dependencies: + "@types/d3-time": "*" + checksum: f462a3f2ec8767bb6762953ed65087b4037d9f8c57c84b1ffc62d55b7633975611e053c2f36cef063bf123196fbb5741b257760b2a745ede9544851f7d150d60 + languageName: node + linkType: hard + +"@types/d3-shape@npm:^3.1.0": + version: 3.1.3 + resolution: "@types/d3-shape@npm:3.1.3" + dependencies: + "@types/d3-path": "*" + checksum: ad17781ab4ce4b796954b86de7e14566c731726d39a1db7d73eaf50668a71e996d715450a0ff9f2720755e1b8643c3e88d47d45101a75c9d4ddbef51a636f6a0 + languageName: node + linkType: hard + +"@types/d3-time@npm:*, @types/d3-time@npm:^3.0.0": + version: 3.0.1 + resolution: "@types/d3-time@npm:3.0.1" + checksum: 32b0c4d33574df167717f37d5d69f60fa1aeebb0218823239734a48e6a33024a7f5aadd079e94d833b42bfc0c3e2d9fa7d7ac93f75981f59ef2a46838d008a61 + languageName: node + linkType: hard + +"@types/d3-timer@npm:^3.0.0": + version: 3.0.0 + resolution: "@types/d3-timer@npm:3.0.0" + checksum: 1ec86b3808de6ecfa93cfdf34254761069658af0cc1d9540e8353dbcba161cdf1296a0724187bd17433b2ff16563115fd20b85fc89d5e809ff28f9b1ab134b42 + languageName: node + linkType: hard + "@types/debug@npm:^4.1.8": version: 4.1.8 resolution: "@types/debug@npm:4.1.8" @@ -5010,6 +5101,13 @@ __metadata: languageName: node linkType: hard +"classnames@npm:^2.2.5": + version: 2.3.2 + resolution: "classnames@npm:2.3.2" + checksum: 2c62199789618d95545c872787137262e741f9db13328e216b093eea91c85ef2bfb152c1f9e63027204e2559a006a92eb74147d46c800a9f96297ae1d9f96f4e + languageName: node + linkType: hard + "clean-css@npm:^5.2.2": version: 5.3.2 resolution: "clean-css@npm:5.3.2" @@ -5553,6 +5651,13 @@ __metadata: languageName: node linkType: hard +"css-unit-converter@npm:^1.1.1": + version: 1.1.2 + resolution: "css-unit-converter@npm:1.1.2" + checksum: 07888033346a5128f34dbe2f72884c966d24e9f29db24416dcde92860242490617ef9a178ac193a92f730834bbeea026cdc7027701d92ba9bbbe59db7a37eb2a + languageName: node + linkType: hard + "css-what@npm:^6.0.1, css-what@npm:^6.1.0": version: 6.1.0 resolution: "css-what@npm:6.1.0" @@ -5675,6 +5780,99 @@ __metadata: languageName: node linkType: hard +"d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:^3.1.6": + version: 3.2.4 + resolution: "d3-array@npm:3.2.4" + dependencies: + internmap: 1 - 2 + checksum: a5976a6d6205f69208478bb44920dd7ce3e788c9dceb86b304dbe401a4bfb42ecc8b04c20facde486e9adcb488b5d1800d49393a3f81a23902b68158e12cddd0 + languageName: node + linkType: hard + +"d3-color@npm:1 - 3": + version: 3.1.0 + resolution: "d3-color@npm:3.1.0" + checksum: 4931fbfda5d7c4b5cfa283a13c91a954f86e3b69d75ce588d06cde6c3628cebfc3af2069ccf225e982e8987c612aa7948b3932163ce15eb3c11cd7c003f3ee3b + languageName: node + linkType: hard + +"d3-ease@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-ease@npm:3.0.1" + checksum: 06e2ee5326d1e3545eab4e2c0f84046a123dcd3b612e68858219aa034da1160333d9ce3da20a1d3486d98cb5c2a06f7d233eee1bc19ce42d1533458bd85dedcd + languageName: node + linkType: hard + +"d3-format@npm:1 - 3": + version: 3.1.0 + resolution: "d3-format@npm:3.1.0" + checksum: f345ec3b8ad3cab19bff5dead395bd9f5590628eb97a389b1dd89f0b204c7c4fc1d9520f13231c2c7cf14b7c9a8cf10f8ef15bde2befbab41454a569bd706ca2 + languageName: node + linkType: hard + +"d3-interpolate@npm:1.2.0 - 3, d3-interpolate@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-interpolate@npm:3.0.1" + dependencies: + d3-color: 1 - 3 + checksum: a42ba314e295e95e5365eff0f604834e67e4a3b3c7102458781c477bd67e9b24b6bb9d8e41ff5521050a3f2c7c0c4bbbb6e187fd586daa3980943095b267e78b + languageName: node + linkType: hard + +"d3-path@npm:^3.1.0": + version: 3.1.0 + resolution: "d3-path@npm:3.1.0" + checksum: 2306f1bd9191e1eac895ec13e3064f732a85f243d6e627d242a313f9777756838a2215ea11562f0c7630c7c3b16a19ec1fe0948b1c82f3317fac55882f6ee5d8 + languageName: node + linkType: hard + +"d3-scale@npm:^4.0.2": + version: 4.0.2 + resolution: "d3-scale@npm:4.0.2" + dependencies: + d3-array: 2.10.0 - 3 + d3-format: 1 - 3 + d3-interpolate: 1.2.0 - 3 + d3-time: 2.1.1 - 3 + d3-time-format: 2 - 4 + checksum: a9c770d283162c3bd11477c3d9d485d07f8db2071665f1a4ad23eec3e515e2cefbd369059ec677c9ac849877d1a765494e90e92051d4f21111aa56791c98729e + languageName: node + linkType: hard + +"d3-shape@npm:^3.1.0": + version: 3.2.0 + resolution: "d3-shape@npm:3.2.0" + dependencies: + d3-path: ^3.1.0 + checksum: de2af5fc9a93036a7b68581ca0bfc4aca2d5a328aa7ba7064c11aedd44d24f310c20c40157cb654359d4c15c3ef369f95ee53d71221017276e34172c7b719cfa + languageName: node + linkType: hard + +"d3-time-format@npm:2 - 4": + version: 4.1.0 + resolution: "d3-time-format@npm:4.1.0" + dependencies: + d3-time: 1 - 3 + checksum: 7342bce28355378152bbd4db4e275405439cabba082d9cd01946d40581140481c8328456d91740b0fe513c51ec4a467f4471ffa390c7e0e30ea30e9ec98fcdf4 + languageName: node + linkType: hard + +"d3-time@npm:1 - 3, d3-time@npm:2.1.1 - 3, d3-time@npm:^3.0.0": + version: 3.1.0 + resolution: "d3-time@npm:3.1.0" + dependencies: + d3-array: 2 - 3 + checksum: 613b435352a78d9f31b7f68540788186d8c331b63feca60ad21c88e9db1989fe888f97f242322ebd6365e45ec3fb206a4324cd4ca0dfffa1d9b5feb856ba00a7 + languageName: node + linkType: hard + +"d3-timer@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-timer@npm:3.0.1" + checksum: 1cfddf86d7bca22f73f2c427f52dfa35c49f50d64e187eb788dcad6e927625c636aa18ae4edd44d084eb9d1f81d8ca4ec305dae7f733c15846a824575b789d73 + languageName: node + linkType: hard + "dargs@npm:^7.0.0": version: 7.0.0 resolution: "dargs@npm:7.0.0" @@ -5728,6 +5926,13 @@ __metadata: languageName: node linkType: hard +"decimal.js-light@npm:^2.4.1": + version: 2.5.1 + resolution: "decimal.js-light@npm:2.5.1" + checksum: f5a2c7eac1c4541c8ab8a5c8abea64fc1761cefc7794bd5f8afd57a8a78d1b51785e0c4e4f85f4895a043eaa90ddca1edc3981d1263eb6ddce60f32bf5fe66c9 + languageName: node + linkType: hard + "decimal.js@npm:^10.4.2": version: 10.4.3 resolution: "decimal.js@npm:10.4.3" @@ -5947,6 +6152,15 @@ __metadata: languageName: node linkType: hard +"dom-helpers@npm:^3.4.0": + version: 3.4.0 + resolution: "dom-helpers@npm:3.4.0" + dependencies: + "@babel/runtime": ^7.1.2 + checksum: 58d9f1c4a96daf77eddc63ae1236b826e1cddd6db66bbf39b18d7e21896d99365b376593352d52a60969d67fa4a8dbef26adc1439fa2c1b355efa37cacbaf637 + languageName: node + linkType: hard + "dom-serializer@npm:^1.0.1": version: 1.4.1 resolution: "dom-serializer@npm:1.4.1" @@ -6490,7 +6704,7 @@ __metadata: languageName: node linkType: hard -"eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.4": +"eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.1, eventemitter3@npm:^4.0.4": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" checksum: 1875311c42fcfe9c707b2712c32664a245629b42bb0a5a84439762dd0fd637fc54d078155ea83c2af9e0323c9ac13687e03cfba79b03af9f40c89b4960099374 @@ -6605,6 +6819,13 @@ __metadata: languageName: node linkType: hard +"fast-equals@npm:^5.0.0": + version: 5.0.1 + resolution: "fast-equals@npm:5.0.1" + checksum: fbb3b6a74f3a0fa930afac151ff7d01639159b4fddd2678b5d50708e0ba38e9ec14602222d10dadb8398187342692c04fbef5a62b1cfcc7942fe03e754e064bc + languageName: node + linkType: hard + "fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0": version: 3.3.1 resolution: "fast-glob@npm:3.3.1" @@ -7689,6 +7910,13 @@ __metadata: languageName: node linkType: hard +"internmap@npm:1 - 2": + version: 2.0.3 + resolution: "internmap@npm:2.0.3" + checksum: 7ca41ec6aba8f0072fc32fa8a023450a9f44503e2d8e403583c55714b25efd6390c38a87161ec456bf42d7bc83aab62eb28f5aef34876b1ac4e60693d5e1d241 + languageName: node + linkType: hard + "interpret@npm:^1.0.0": version: 1.4.0 resolution: "interpret@npm:1.4.0" @@ -9058,7 +9286,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.20, lodash@npm:^4.17.21": +"lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 @@ -10879,6 +11107,13 @@ __metadata: languageName: node linkType: hard +"postcss-value-parser@npm:^3.3.0": + version: 3.3.1 + resolution: "postcss-value-parser@npm:3.3.1" + checksum: 62cd26e1cdbcf2dcc6bcedf3d9b409c9027bc57a367ae20d31dd99da4e206f730689471fd70a2abe866332af83f54dc1fa444c589e2381bf7f8054c46209ce16 + languageName: node + linkType: hard + "postcss-value-parser@npm:^4.1.0, postcss-value-parser@npm:^4.2.0": version: 4.2.0 resolution: "postcss-value-parser@npm:4.2.0" @@ -11040,7 +11275,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -11217,7 +11452,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.1, react-is@npm:^16.7.0": +"react-is@npm:^16.10.2, react-is@npm:^16.13.1, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f @@ -11231,6 +11466,25 @@ __metadata: languageName: node linkType: hard +"react-lifecycles-compat@npm:^3.0.4": + version: 3.0.4 + resolution: "react-lifecycles-compat@npm:3.0.4" + checksum: a904b0fc0a8eeb15a148c9feb7bc17cec7ef96e71188280061fc340043fd6d8ee3ff233381f0e8f95c1cf926210b2c4a31f38182c8f35ac55057e453d6df204f + languageName: node + linkType: hard + +"react-resize-detector@npm:^8.0.4": + version: 8.1.0 + resolution: "react-resize-detector@npm:8.1.0" + dependencies: + lodash: ^4.17.21 + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 45e6b87ea7331406bed2a806d0cea98c1467d53a7cfcdf19c2dd55a3460047917d3b175d9cceea6f314b65eb54858cbb981acffd007d67aa16388e517dafb83e + languageName: node + linkType: hard + "react-router-dom@npm:^6.5.0": version: 6.15.0 resolution: "react-router-dom@npm:6.15.0" @@ -11255,6 +11509,20 @@ __metadata: languageName: node linkType: hard +"react-smooth@npm:^2.0.2": + version: 2.0.4 + resolution: "react-smooth@npm:2.0.4" + dependencies: + fast-equals: ^5.0.0 + react-transition-group: 2.9.0 + peerDependencies: + prop-types: ^15.6.0 + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 21731e2f9ebc9594eae0f0d875526185392a87c00abf013c9769ed642a4077b62c04c1001b2527a196aabafb87af208f6c7107db674538c4bb95c253ed123447 + languageName: node + linkType: hard + "react-textarea-autosize@npm:^8.4.0": version: 8.5.3 resolution: "react-textarea-autosize@npm:8.5.3" @@ -11268,6 +11536,21 @@ __metadata: languageName: node linkType: hard +"react-transition-group@npm:2.9.0": + version: 2.9.0 + resolution: "react-transition-group@npm:2.9.0" + dependencies: + dom-helpers: ^3.4.0 + loose-envify: ^1.4.0 + prop-types: ^15.6.2 + react-lifecycles-compat: ^3.0.4 + peerDependencies: + react: ">=15.0.0" + react-dom: ">=15.0.0" + checksum: d8c9e50aabdc2cfc324e5cdb0ad1c6eecb02e1c0cd007b26d5b30ccf49015e900683dd489348c71fba4055858308d9ba7019e0d37d0e8d37bd46ed098788f670 + languageName: node + linkType: hard + "react-transition-state@npm:^1.1.5": version: 1.1.5 resolution: "react-transition-state@npm:1.1.5" @@ -11421,6 +11704,36 @@ __metadata: languageName: node linkType: hard +"recharts-scale@npm:^0.4.4": + version: 0.4.5 + resolution: "recharts-scale@npm:0.4.5" + dependencies: + decimal.js-light: ^2.4.1 + checksum: e970377190a610e684a32c7461c7684ac3603c2e0ac0020bbba1eea9d099b38138143a8e80bf769bb49c0b7cecf22a2f5c6854885efed2d56f4540d4aa7052bd + languageName: node + linkType: hard + +"recharts@npm:^2.8.0": + version: 2.8.0 + resolution: "recharts@npm:2.8.0" + dependencies: + classnames: ^2.2.5 + eventemitter3: ^4.0.1 + lodash: ^4.17.19 + react-is: ^16.10.2 + react-resize-detector: ^8.0.4 + react-smooth: ^2.0.2 + recharts-scale: ^0.4.4 + reduce-css-calc: ^2.1.8 + victory-vendor: ^36.6.8 + peerDependencies: + prop-types: ^15.6.0 + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 4638bd5c6c2af8f5c79de5e13cce0e38f06e0bbb0a3c4df27a9b12632fd72c0a0604c8246f55e830f323dfa84a3da7cb2634c2243bb9c775d899fd71f9d4c87a + languageName: node + linkType: hard + "rechoir@npm:^0.6.2": version: 0.6.2 resolution: "rechoir@npm:0.6.2" @@ -11439,6 +11752,16 @@ __metadata: languageName: node linkType: hard +"reduce-css-calc@npm:^2.1.8": + version: 2.1.8 + resolution: "reduce-css-calc@npm:2.1.8" + dependencies: + css-unit-converter: ^1.1.1 + postcss-value-parser: ^3.3.0 + checksum: 8fd27c06c4b443b84749a69a8b97d10e6ec7d142b625b41923a8807abb22b9e37e44df14e26cc606a802957be07bdce5e8ee2976a6952a7b438a7727007101e9 + languageName: node + linkType: hard + "regenerate-unicode-properties@npm:^10.1.0": version: 10.1.0 resolution: "regenerate-unicode-properties@npm:10.1.0" @@ -13401,6 +13724,28 @@ __metadata: languageName: node linkType: hard +"victory-vendor@npm:^36.6.8": + version: 36.6.11 + resolution: "victory-vendor@npm:36.6.11" + dependencies: + "@types/d3-array": ^3.0.3 + "@types/d3-ease": ^3.0.0 + "@types/d3-interpolate": ^3.0.1 + "@types/d3-scale": ^4.0.2 + "@types/d3-shape": ^3.1.0 + "@types/d3-time": ^3.0.0 + "@types/d3-timer": ^3.0.0 + d3-array: ^3.1.6 + d3-ease: ^3.0.1 + d3-interpolate: ^3.0.1 + d3-scale: ^4.0.2 + d3-shape: ^3.1.0 + d3-time: ^3.0.0 + d3-timer: ^3.0.1 + checksum: 55800076dfa6abedf7758840986a302778a904678d4b66fe47d977c48b6f9484276b780871e6e5105b31c1eb936e9f1331ee39afcc2869bf65ceb7d456143172 + languageName: node + linkType: hard + "vinyl-file@npm:^3.0.0": version: 3.0.0 resolution: "vinyl-file@npm:3.0.0" From 8f153b042860508bade72e37af41b993e509edd2 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 9 Oct 2023 13:36:42 +0000 Subject: [PATCH 11/19] chore: Update translations --- packages/app/src/translations/ar_SA.json | 5 ++++- packages/app/src/translations/az_AZ.json | 5 ++++- packages/app/src/translations/de_DE.json | 5 ++++- packages/app/src/translations/es_ES.json | 5 ++++- packages/app/src/translations/fa_IR.json | 5 ++++- packages/app/src/translations/fi_FI.json | 5 ++++- packages/app/src/translations/fr_FR.json | 5 ++++- packages/app/src/translations/hr_HR.json | 5 ++++- packages/app/src/translations/hu_HU.json | 5 ++++- packages/app/src/translations/id_ID.json | 5 ++++- packages/app/src/translations/it_IT.json | 5 ++++- packages/app/src/translations/ja_JP.json | 5 ++++- packages/app/src/translations/nl_NL.json | 5 ++++- packages/app/src/translations/pt_BR.json | 5 ++++- packages/app/src/translations/ru_RU.json | 5 ++++- packages/app/src/translations/sv_SE.json | 5 ++++- packages/app/src/translations/sw_KE.json | 5 ++++- packages/app/src/translations/ta_IN.json | 5 ++++- packages/app/src/translations/th_TH.json | 5 ++++- packages/app/src/translations/zh_CN.json | 5 ++++- packages/app/src/translations/zh_TW.json | 5 ++++- 21 files changed, 84 insertions(+), 21 deletions(-) diff --git a/packages/app/src/translations/ar_SA.json b/packages/app/src/translations/ar_SA.json index 73a7b80a..4b3951e7 100644 --- a/packages/app/src/translations/ar_SA.json +++ b/packages/app/src/translations/ar_SA.json @@ -13,6 +13,7 @@ "/PCavi": "علنية", "/RD0e2": "يستخدم نوستر تقنية التوقيع الرقمي لنشر المنشورات دون أي تلاعب ويمكن إرسالها بأمان إلى العديد من الموصلات لتكرار تخزينها وضمان استمرارية المحتوى الخاص بك.", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "اجعل ملفك الشخصي أسهل في العثور عليه ومشاركته", "/n5KSF": "{n} مللي ثانية", "00LcfG": "Load more", @@ -231,6 +232,7 @@ "P7FD0F": "مطابق لاعدادات جهازك", "P7nJT9": "الإجمالي اليوم (بالتوقيت العالمي ): {amount} ساتوشي", "PCSt5T": "التفضيلات", + "PJeJFc": "Summary", "PLSbmL": "عبارة الاسترداد الخاصة بك", "PaN7t3": "Preview on {site}", "PamNxw": "عنوان الملف غير معروف: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "انت جاهز!", "zonsdq": "فشل تحميل خدمة LNURL", "zvCDao": "تظهر تلقائيا أحدث الملاحظات", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/az_AZ.json b/packages/app/src/translations/az_AZ.json index 8a659c3b..f38205cf 100644 --- a/packages/app/src/translations/az_AZ.json +++ b/packages/app/src/translations/az_AZ.json @@ -13,6 +13,7 @@ "/PCavi": "İctimai", "/RD0e2": "Nostr, məzmununuzun lazımsız saxlanmasını təmin etmək üçün bir çox relelərə təhlükəsiz şəkildə təkrarlana bilən saxta qeydləri təmin etmək üçün rəqəmsal imza texnologiyasından istifadə edir.", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "Profilinizi tapmağı və paylaşmağı asanlaşdırın", "/n5KSF": "{n} ms", "00LcfG": "Daha çox", @@ -231,6 +232,7 @@ "P7FD0F": "System (Default)", "P7nJT9": "Total today (UTC): {amount} sats", "PCSt5T": "Preferences", + "PJeJFc": "Summary", "PLSbmL": "Your mnemonic phrase", "PaN7t3": "Preview on {site}", "PamNxw": "Unknown file header: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "Hazırsan!", "zonsdq": "LNURL xidmətini yükləmək alınmadı", "zvCDao": "Ən son qeydləri avtomatik göstərin", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/de_DE.json b/packages/app/src/translations/de_DE.json index 5d04177d..a876fafa 100644 --- a/packages/app/src/translations/de_DE.json +++ b/packages/app/src/translations/de_DE.json @@ -13,6 +13,7 @@ "/PCavi": "Öffentlich", "/RD0e2": "Nostr nutzt digitale Signaturen, um manipulationssichere Notes zu erstellen, welche sicher auf viele Relais repliziert werden können, um eine redundante Speicherung deiner Inhalte zu bieten.", "/Xf4UW": "Anonyme Nutzungsmetriken senden", + "/clOBU": "Weekly", "/d6vEc": "Mach dein Profil leichter zu finden und zu teilen", "/n5KSF": "{n} ms", "00LcfG": "Mehr laden", @@ -231,6 +232,7 @@ "P7FD0F": "System (Standard)", "P7nJT9": "Gesamt heute (UTC): {amount} sats", "PCSt5T": "Einstellungen", + "PJeJFc": "Summary", "PLSbmL": "Ihre mnemonische Passphrase", "PaN7t3": "Vorschau auf {site}", "PamNxw": "Unbekannter Datei-Header: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "Du bist bereit!", "zonsdq": "Fehler beim Laden des LNURL-Dienstes", "zvCDao": "Neueste Notes automatisch anzeigen", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/es_ES.json b/packages/app/src/translations/es_ES.json index 21c921b3..d442d74b 100644 --- a/packages/app/src/translations/es_ES.json +++ b/packages/app/src/translations/es_ES.json @@ -13,6 +13,7 @@ "/PCavi": "Público", "/RD0e2": "Nostr utiliza firmas digitales para proveer de notas inmutables que pueden ser replicadas a muchos relays para que tu contenido esté distribuido redundantemente.", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "Haz tu perfil más fácil de encontrar y compartir", "/n5KSF": "{n} ms", "00LcfG": "Load more", @@ -231,6 +232,7 @@ "P7FD0F": "Sistema (por defecto)", "P7nJT9": "Total hoy (UTC): {amount} sáb", "PCSt5T": "Preferencias", + "PJeJFc": "Summary", "PLSbmL": "Frase mnemotécnica", "PaN7t3": "Preview on {site}", "PamNxw": "Encabezado de archivo desconocido: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "Estás listo!", "zonsdq": "Error al contactar con el servicio LNURL", "zvCDao": "Mostrar notas nuevas automáticamente", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/fa_IR.json b/packages/app/src/translations/fa_IR.json index 38bf123a..fa9d16e8 100644 --- a/packages/app/src/translations/fa_IR.json +++ b/packages/app/src/translations/fa_IR.json @@ -13,6 +13,7 @@ "/PCavi": "عمومی", "/RD0e2": "ناستر از تکنولوژی امضای دیجیتال برای فراهم نمودن یادداشت های غیرقابل دستکاری استفاده می کند که می تواند به طور امن در رله های زیادی تکرار شود تا انبار اضافی برای محتوایتان تامین کند.", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "یافتن و اشتراک گذاری نمایه خود را آسان تر کنید", "/n5KSF": "{n} میلی ثانیه", "00LcfG": "بارگیری بیشتر", @@ -231,6 +232,7 @@ "P7FD0F": "سیستم (پیش فرض)", "P7nJT9": "جمع امروز (UTC): {amount} ساتوشی", "PCSt5T": "ترجیحات", + "PJeJFc": "Summary", "PLSbmL": "عبارت بازیابی یادسپاری شما", "PaN7t3": "Preview on {site}", "PamNxw": "سرفایل ناشناس: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "شما آماده هستید!", "zonsdq": "خدمات LNURL بارگیری نشد", "zvCDao": "به طور خودکار یادداشت های اخیر را نشان بده", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/fi_FI.json b/packages/app/src/translations/fi_FI.json index 906f9bfd..74e5606f 100644 --- a/packages/app/src/translations/fi_FI.json +++ b/packages/app/src/translations/fi_FI.json @@ -13,6 +13,7 @@ "/PCavi": "Julkinen", "/RD0e2": "Nostr käyttää digitaalista allekirjoitusteknologiaa tarjotakseen väärentämättömiä viestejä, joita voidaan turvallisesti replikoida monille välittäjille tarjoten näin varastointia sisällöllesi.", "/Xf4UW": "Lähetä nimettömiä käyttötietoja", + "/clOBU": "Weekly", "/d6vEc": "Tee profiilistasi helpompi löytää ja jakaa", "/n5KSF": "{n} ms", "00LcfG": "Lataa lisää", @@ -231,6 +232,7 @@ "P7FD0F": "Järjestelmä (oletus)", "P7nJT9": "Tänään yhteensä (UTC): {amount} satsia", "PCSt5T": "Asetukset", + "PJeJFc": "Summary", "PLSbmL": "Sinun mnemonic-lauseesi", "PaN7t3": "Preview on {site}", "PamNxw": "Tuntematon tiedostotunniste: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "Olet valmis!", "zonsdq": "Epäonnistunut LNURL-palvelun lataus", "zvCDao": "Näytä uusimmat viestit automaattisesti", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/fr_FR.json b/packages/app/src/translations/fr_FR.json index 428d3c95..48ce774c 100644 --- a/packages/app/src/translations/fr_FR.json +++ b/packages/app/src/translations/fr_FR.json @@ -13,6 +13,7 @@ "/PCavi": "Publique", "/RD0e2": "Nostr utilise la technologie de signature numérique pour fournir des notes inviolables qui peuvent être répliquées en toute sécurité sur de nombreux relais pour fournir un stockage redondant de votre contenu.", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "Rendez votre profil plus facile à trouver et à partager", "/n5KSF": "{n} ms", "00LcfG": "Charger plus", @@ -231,6 +232,7 @@ "P7FD0F": "Système (Défaut)", "P7nJT9": "Total aujourd'hui (UTC) : {amount} sats", "PCSt5T": "Préférences", + "PJeJFc": "Summary", "PLSbmL": "Votre phrase mnémonique", "PaN7t3": "Preview on {site}", "PamNxw": "En-tête du fichier inconnu : {name}", @@ -493,5 +495,6 @@ "zjJZBd": "Tu es prêt!", "zonsdq": "Échec du chargement du service LNURL", "zvCDao": "Afficher automatiquement les dernières notes", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/hr_HR.json b/packages/app/src/translations/hr_HR.json index 2758ff47..e0e8c0dd 100644 --- a/packages/app/src/translations/hr_HR.json +++ b/packages/app/src/translations/hr_HR.json @@ -13,6 +13,7 @@ "/PCavi": "Javno", "/RD0e2": "Nostr koristi tehnologiju digitalnog potpisa kako bi pružio bilješke o zaštiti od neovlaštenog mijenjanja koje se mogu sigurno replicirati na mnoge releje kako bi se osigurala nepotrebna pohrana vašeg sadržaja.", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "Olakšajte pronalazak i dijeljenje svog profila", "/n5KSF": "{n} mikrosekundi", "00LcfG": "Load more", @@ -231,6 +232,7 @@ "P7FD0F": "Sustav (zadano)", "P7nJT9": "Ukupno danas (UTC): {amount} sati", "PCSt5T": "Postavke", + "PJeJFc": "Summary", "PLSbmL": "Vaša mnemotehnička fraza", "PaN7t3": "Preview on {site}", "PamNxw": "Nepoznati header file-a: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "Spremni ste!", "zonsdq": "Neuspješno učitavanje LNURL usluge", "zvCDao": "Automatski prikaži najnovije bilješke", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/hu_HU.json b/packages/app/src/translations/hu_HU.json index 5d147f21..fb3281c6 100644 --- a/packages/app/src/translations/hu_HU.json +++ b/packages/app/src/translations/hu_HU.json @@ -13,6 +13,7 @@ "/PCavi": "Nyilvános", "/RD0e2": "A Nostr digitális aláírásokat használ, amivel megmásíthatatlan bejegyzéseket lehet létrehozni. Ezeket bárhány másolatban a különböző csomópontokra szétszórhatóak, ezzel biztosítva a tartalom redundanciáját.", "/Xf4UW": "Anonimizált használati adatok küldése", + "/clOBU": "Weekly", "/d6vEc": "Legyen a fiókod könnyebben megtalálható és megosztható", "/n5KSF": "{n} ms", "00LcfG": "Továbbiak betöltése", @@ -231,6 +232,7 @@ "P7FD0F": "Rendszer (Alapértelmezett)", "P7nJT9": "Összesen ma (UTC): {amount} sat", "PCSt5T": "Preferenciák", + "PJeJFc": "Summary", "PLSbmL": "Fiók helyreállító (mnemonikus) szavaid", "PaN7t3": "Előnézet a(z) {site}-n", "PamNxw": "Ismeretlen fejléc: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "Készen vagy!", "zonsdq": "Az LNURL szolgáltatás betöltése nem sikerült", "zvCDao": "Automatikusan a legfrissebb bejegyzéseket mutassa", - "zwb6LR": "Pénzverde: {url}" + "zwb6LR": "Pénzverde: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/id_ID.json b/packages/app/src/translations/id_ID.json index 7dba2901..916a7fa6 100644 --- a/packages/app/src/translations/id_ID.json +++ b/packages/app/src/translations/id_ID.json @@ -13,6 +13,7 @@ "/PCavi": "Publik", "/RD0e2": "Nostr menggunakan teknologi tanda tangan digital untuk menyediakan catatan anti pengerusakan yang dapat dengan aman direplikasi ke banyak relai untuk menyediakan penyimpanan konten Anda yang berulang.", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "Jadikan profil Anda lebih mudah untuk ditemukan dan dibagikan", "/n5KSF": "{n} ms", "00LcfG": "Load more", @@ -231,6 +232,7 @@ "P7FD0F": "Sistem (Bawaan)", "P7nJT9": "Total hari ini (UTC): {amount} sat", "PCSt5T": "Preferensi", + "PJeJFc": "Summary", "PLSbmL": "Your mnemonic phrase", "PaN7t3": "Preview on {site}", "PamNxw": "Unknown file header: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "Anda siap!", "zonsdq": "Gagal memuat layanan LNURL", "zvCDao": "Tampilkan catatan terbaru secara otomatis", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/it_IT.json b/packages/app/src/translations/it_IT.json index 8dc3cbf0..28915095 100644 --- a/packages/app/src/translations/it_IT.json +++ b/packages/app/src/translations/it_IT.json @@ -13,6 +13,7 @@ "/PCavi": "Pubblica", "/RD0e2": "Nostr utilizza la tecnologia della firma digitale per fornire note a prova di manomissione che possono essere replicate in modo sicuro su molti relè per fornire l'archiviazione ridondante dei tuoi contenuti.", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "Rendi il tuo profilo più facile da trovare e condividere", "/n5KSF": "{n} ms", "00LcfG": "Load more", @@ -231,6 +232,7 @@ "P7FD0F": "Sistema (Predefinito)", "P7nJT9": "Totale oggi (UTC): {amount} sat", "PCSt5T": "Preferenze", + "PJeJFc": "Summary", "PLSbmL": "La tua frase mnemonica", "PaN7t3": "Preview on {site}", "PamNxw": "Intestazione del file sconosciuta: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "Tutto fatto!", "zonsdq": "Caricamento del servizio LNURL fallito", "zvCDao": "Visualizza automaticamente le ultime note", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/ja_JP.json b/packages/app/src/translations/ja_JP.json index d7f34ac4..56560dd0 100644 --- a/packages/app/src/translations/ja_JP.json +++ b/packages/app/src/translations/ja_JP.json @@ -13,6 +13,7 @@ "/PCavi": "公開", "/RD0e2": "Nostrはデジタル署名技術を使って投稿の改竄防止を図り、多数のリレーに安全に複製してコンテンツの冗長ストレージを提供しています。", "/Xf4UW": "匿名で利用状況を送信します", + "/clOBU": "Weekly", "/d6vEc": "プロフィールを見つけやすく、共有しやすくなる", "/n5KSF": "{n}ミリ秒", "00LcfG": "さらに読み込む", @@ -231,6 +232,7 @@ "P7FD0F": "システム (デフォルト)", "P7nJT9": "本日の合計額 (UTC): {amount} sats", "PCSt5T": "ユーザー設定", + "PJeJFc": "Summary", "PLSbmL": "ニーモニックフレーズ", "PaN7t3": "Preview on {site}", "PamNxw": "不明なファイルヘッダー: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "準備完了!", "zonsdq": "LNURLサービスの読み込みに失敗しました", "zvCDao": "最新の記事を自動で表示する", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/nl_NL.json b/packages/app/src/translations/nl_NL.json index 897e353f..bacfc3c3 100644 --- a/packages/app/src/translations/nl_NL.json +++ b/packages/app/src/translations/nl_NL.json @@ -13,6 +13,7 @@ "/PCavi": "Openbaar", "/RD0e2": "Nostr maakt gebruik van digitale handtekeningentechnologie om manipulatiebeveiligde notes te realiseren. Zo kunnen de notes consistent naar veel relays tegelijk worden verzonden en wordt overbodige opslag voorkomen.", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "Maak je profiel gemakkelijker om te vinden en te delen", "/n5KSF": "{n} ms", "00LcfG": "Meer laden", @@ -231,6 +232,7 @@ "P7FD0F": "Systeem (Standaard)", "P7nJT9": "Totaal vandaag (UTC): {amount} sats", "PCSt5T": "Voorkeuren", + "PJeJFc": "Summary", "PLSbmL": "Uw wachtwoord om te onthouden", "PaN7t3": "Preview on {site}", "PamNxw": "Onbekende bestandsheader: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "Je bent er klaar voor!", "zonsdq": "Laden van LNURL service mislukt", "zvCDao": "Recente notities automatisch weergeven", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/pt_BR.json b/packages/app/src/translations/pt_BR.json index c851a14f..3048c0d0 100644 --- a/packages/app/src/translations/pt_BR.json +++ b/packages/app/src/translations/pt_BR.json @@ -13,6 +13,7 @@ "/PCavi": "Público", "/RD0e2": "Nostr usa tecnologia de assinatura digital para prover notas à prova de adulteração que podem ser repostadas em diferentes relays para que haja armazenamento redundante de seu conteúdo.", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "Faça seu perfil seja mais fácil de achar e compartilhar", "/n5KSF": "{n} ms", "00LcfG": "Carregar mais", @@ -231,6 +232,7 @@ "P7FD0F": "Sistema (Padrão)", "P7nJT9": "Total hoje (UTC): {amount} sats", "PCSt5T": "Preferências", + "PJeJFc": "Summary", "PLSbmL": "A sua senha mnemônica", "PaN7t3": "Preview on {site}", "PamNxw": "Cabeçalho de arquivo desconhecido: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "Você está pronto!", "zonsdq": "Falha ao carregar o serviço LNURL", "zvCDao": "Mostrar automaticamente as últimas notas", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/ru_RU.json b/packages/app/src/translations/ru_RU.json index 1f12f7f4..98ca5af6 100644 --- a/packages/app/src/translations/ru_RU.json +++ b/packages/app/src/translations/ru_RU.json @@ -13,6 +13,7 @@ "/PCavi": "Публичный", "/RD0e2": "Nostr использует технологию цифровой подписи для создания защищенных от несанкционированного доступа заметок, которые можно безопасно копировать на множество релеев, обеспечивая резервное хранение Вашего контента.", "/Xf4UW": "Отправлять показатели анонимного использования", + "/clOBU": "Weekly", "/d6vEc": "Упростить поиск и распространение вашего профиля в Nostr", "/n5KSF": "{n} мс", "00LcfG": "Загрузить больше", @@ -231,6 +232,7 @@ "P7FD0F": "Системный (по умолчанию)", "P7nJT9": "Всего сегодня (UTC): {amount} сат", "PCSt5T": "Настройки", + "PJeJFc": "Summary", "PLSbmL": "Ваша мнемоническая фраза", "PaN7t3": "Preview on {site}", "PamNxw": "Неизвестный заголовок файла: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "Готово!", "zonsdq": "Не удалось загрузить службу LNURL", "zvCDao": "Автоматически показывать новые заметки", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/sv_SE.json b/packages/app/src/translations/sv_SE.json index 0835de4a..ee311d98 100644 --- a/packages/app/src/translations/sv_SE.json +++ b/packages/app/src/translations/sv_SE.json @@ -13,6 +13,7 @@ "/PCavi": "Publik", "/RD0e2": "Nostr använder digital signatur teknik för att ge manipuleringssäkra anteckningar som säkert kan replikeras till många reläer för att ge redundant lagring av ditt innehåll.", "/Xf4UW": "Skicka anonyma användarvärden", + "/clOBU": "Weekly", "/d6vEc": "Gör din profil enklare att hitta och dela", "/n5KSF": "{n} ms", "00LcfG": "Ladda fler", @@ -231,6 +232,7 @@ "P7FD0F": "System (standard)", "P7nJT9": "Totalt idag (UTC): {amount} sats", "PCSt5T": "Inställningar", + "PJeJFc": "Summary", "PLSbmL": "Din mnemoniska lösenfras", "PaN7t3": "Förhandsgranska på {site}", "PamNxw": "Okänd filrubrik: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "Du är redo!", "zonsdq": "Det gick inte att ladda LNURL-tjänsten", "zvCDao": "Visa automatiskt de senaste anteckningarna", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/sw_KE.json b/packages/app/src/translations/sw_KE.json index e9ca4e59..37954221 100644 --- a/packages/app/src/translations/sw_KE.json +++ b/packages/app/src/translations/sw_KE.json @@ -13,6 +13,7 @@ "/PCavi": "Umma", "/RD0e2": "Nostr hutumia teknolojia ya sahihi ya dijiti kutoa madokezo ya uthibitisho ambayo yanaweza kuigwa kwa usalama kwenye relay nyingi ili kutoa uhifadhi mwingi wa maudhui yako.", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "Rahisisha wasifu wako kupata na kushiriki", "/n5KSF": "{n} ms", "00LcfG": "Pakia zaidi", @@ -231,6 +232,7 @@ "P7FD0F": "Mfumo (Chaguo-msingi)", "P7nJT9": "Jumla ya leo (UTC): {amount} sats", "PCSt5T": "Preferences", + "PJeJFc": "Summary", "PLSbmL": "Maneno yako ya mnemonic", "PaN7t3": "Preview on {site}", "PamNxw": "Kijajuu cha faili kisichojulikana: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "Uko tayari!", "zonsdq": "Imeshindwa kupakia huduma ya LNURL", "zvCDao": "Onyesha madokezo mapya kiotomatiki", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/ta_IN.json b/packages/app/src/translations/ta_IN.json index 62ffb269..4fd84a9b 100644 --- a/packages/app/src/translations/ta_IN.json +++ b/packages/app/src/translations/ta_IN.json @@ -13,6 +13,7 @@ "/PCavi": "பொது", "/RD0e2": "சேதப்படுத்த முடியாத குறிப்புகளை வழங்க, நாஸ்டர் டிஜிட்டல் கையொப்ப தொழில் நுட்பத்தைப் பயன் படுத்துகிறது. இதனால் பல ரிலேகளில் குறிப்புகள் பிரதியெடுக்கப் பட்டு, குறிப்பின் உள்ளடக்கம் கூடுதல் சேமிப்பு அடைகிறது.", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "உங்கள் சுயவிவரத்தை கண்டறிவதையும் பகிர்வதையும் எளிதாக்குங்கள்", "/n5KSF": "{n} மில்லி வினாடிகள்", "00LcfG": "மேலும் காண்க", @@ -231,6 +232,7 @@ "P7FD0F": "கணினி (இயல்புநிலை)", "P7nJT9": "இன்றைய (UTC) மொத்தம்: {amount} சாட்கள்", "PCSt5T": "விருப்பங்கள்", + "PJeJFc": "Summary", "PLSbmL": "உங்கள் நினைவூட்டும் சொற்றொடர்", "PaN7t3": "Preview on {site}", "PamNxw": "தெரியாத கோப்புத் தலைப்பு: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "நீங்கள் தயார்!", "zonsdq": "LNURL சேவையை ஏற்றுவதில் தோல்வி", "zvCDao": "சமீபத்திய குறிப்புகளைத் தானாகக் காட்டு", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/th_TH.json b/packages/app/src/translations/th_TH.json index b07c85f7..d003689a 100644 --- a/packages/app/src/translations/th_TH.json +++ b/packages/app/src/translations/th_TH.json @@ -13,6 +13,7 @@ "/PCavi": "สาธารณะ", "/RD0e2": "Nostr ใช้เทคโนโลยีลายเซ็นดิจิทัลเพื่อแสดงบันทึกหลักฐานการปลอมแปลง ซึ่งสามารถทำซ้ำได้อย่างปลอดภัยไปยังรีเลย์จำนวนมาก เพื่อกระจายการจัดเก็บเนื้อหาที่ซ้ำซ้อนของคุณ", "/Xf4UW": "Send anonymous usage metrics", + "/clOBU": "Weekly", "/d6vEc": "ทำให้ค้นหาและแบ่งปันโปรไฟล์ได้ง่ายขึ้น", "/n5KSF": "{n} ms", "00LcfG": "Load more", @@ -231,6 +232,7 @@ "P7FD0F": "ระบบ (ค่าเริ่มต้น)", "P7nJT9": "ยอดรวมวันนี้ (UTC): {amount} sats", "PCSt5T": "การตั้งค่า", + "PJeJFc": "Summary", "PLSbmL": "Mnemonic phrase ของคุณ", "PaN7t3": "Preview on {site}", "PamNxw": "ส่วนต้นของไฟล์ที่ไม่รู้จัก: {name}", @@ -493,5 +495,6 @@ "zjJZBd": "คุณพร้อมแล้ว!", "zonsdq": "ไม่สามารถโหลดบริการ LNURL", "zvCDao": "โชว์โน้ตแบบย่ออัตโนมัติ", - "zwb6LR": "Mint: {url}" + "zwb6LR": "Mint: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/zh_CN.json b/packages/app/src/translations/zh_CN.json index fc90d316..66a74157 100644 --- a/packages/app/src/translations/zh_CN.json +++ b/packages/app/src/translations/zh_CN.json @@ -13,6 +13,7 @@ "/PCavi": "公开", "/RD0e2": "Nostr 利用数字签名技术实现防篡改的笔记并可令其安全的复制到大量中继节点,给你的内容作为冗余存储。", "/Xf4UW": "发送匿名使用资料", + "/clOBU": "Weekly", "/d6vEc": "使你的帐号可更方便地被找到及分享", "/n5KSF": "{n} 毫秒", "00LcfG": "加载更多", @@ -231,6 +232,7 @@ "P7FD0F": "系统(默认)", "P7nJT9": "今天总计 (UTC):{amount} 聪", "PCSt5T": "选项", + "PJeJFc": "Summary", "PLSbmL": "你的助记词句", "PaN7t3": "Preview on {site}", "PamNxw": "未知文件标头:{name}", @@ -493,5 +495,6 @@ "zjJZBd": "你已准备就绪!", "zonsdq": "加载 LNURL 服务失败", "zvCDao": "自动显示最新笔记", - "zwb6LR": "铸币厂: {url}" + "zwb6LR": "铸币厂: {url}", + "zxvhnE": "Daily" } diff --git a/packages/app/src/translations/zh_TW.json b/packages/app/src/translations/zh_TW.json index 261cf57f..c3e96629 100644 --- a/packages/app/src/translations/zh_TW.json +++ b/packages/app/src/translations/zh_TW.json @@ -13,6 +13,7 @@ "/PCavi": "公開", "/RD0e2": "Nostr 利用數字簽名技術實現防篡改的筆記並可令其安全的複製到大量的中繼節點,給你的內容作為冗余存儲。", "/Xf4UW": "傳送匿名使用資料", + "/clOBU": "Weekly", "/d6vEc": "使你的帳號可更方便地被找到及分享", "/n5KSF": "{n} 毫秒", "00LcfG": "加載更多", @@ -231,6 +232,7 @@ "P7FD0F": "系統(默認)", "P7nJT9": "今天總計(UTC):{amount} 聰", "PCSt5T": "選項", + "PJeJFc": "Summary", "PLSbmL": "你的助記詞句", "PaN7t3": "Preview on {site}", "PamNxw": "未知文件標頭:{name}", @@ -493,5 +495,6 @@ "zjJZBd": "你已經準備就緒!", "zonsdq": "加載 LNURL 服務失敗", "zvCDao": "自動顯示最新筆記", - "zwb6LR": "鑄幣廠: {url}" + "zwb6LR": "鑄幣廠: {url}", + "zxvhnE": "Daily" } From b5499f516c2bf13c0ebbda1aecf4cd31f0d65d28 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 9 Oct 2023 14:50:18 +0100 Subject: [PATCH 12/19] fix: build scripts --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0d26662c..a3de6f6e 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,9 @@ "packages/*" ], "scripts": { - "build": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app build", - "start": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app start", - "test": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/app test && yarn workspace @snort/system test", + "build": "yarn workspace @snort/shared build && yarn workspace @snort/system-web build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app build", + "start": "yarn build && yarn workspace @snort/app start", + "test": "yarn build && yarn workspace @snort/app test && yarn workspace @snort/system test", "pre:commit": "yarn workspace @snort/app intl-extract && yarn workspace @snort/app intl-compile && yarn prettier --write ." }, "prettier": { From 01234d7be7408a9267a002f4e823582280b867cd Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 9 Oct 2023 13:52:12 +0000 Subject: [PATCH 13/19] chore: Update translations --- packages/app/src/translations/sv_SE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/translations/sv_SE.json b/packages/app/src/translations/sv_SE.json index ee311d98..c4366d59 100644 --- a/packages/app/src/translations/sv_SE.json +++ b/packages/app/src/translations/sv_SE.json @@ -13,7 +13,7 @@ "/PCavi": "Publik", "/RD0e2": "Nostr använder digital signatur teknik för att ge manipuleringssäkra anteckningar som säkert kan replikeras till många reläer för att ge redundant lagring av ditt innehåll.", "/Xf4UW": "Skicka anonyma användarvärden", - "/clOBU": "Weekly", + "/clOBU": "Veckovis", "/d6vEc": "Gör din profil enklare att hitta och dela", "/n5KSF": "{n} ms", "00LcfG": "Ladda fler", From b62b877f5a516c76f1340645793aaf43b491a7c9 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 9 Oct 2023 14:54:54 +0100 Subject: [PATCH 14/19] fix: build command order --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a3de6f6e..fa26554d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "packages/*" ], "scripts": { - "build": "yarn workspace @snort/shared build && yarn workspace @snort/system-web build && yarn workspace @snort/system build && yarn workspace @snort/system-react build && yarn workspace @snort/app build", + "build": "yarn workspace @snort/shared build && yarn workspace @snort/system build && yarn workspace @snort/system-web build && yarn workspace @snort/system-react build && yarn workspace @snort/app build", "start": "yarn build && yarn workspace @snort/app start", "test": "yarn build && yarn workspace @snort/app test && yarn workspace @snort/system test", "pre:commit": "yarn workspace @snort/app intl-extract && yarn workspace @snort/app intl-compile && yarn prettier --write ." From 8d882a08448881bed7a692874bdcbb2411cb88a7 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 9 Oct 2023 16:32:14 +0100 Subject: [PATCH 15/19] Profile hover cards --- packages/app/package.json | 1 + packages/app/src/Element/Text.tsx | 10 ++- .../app/src/Element/User/FollowButton.css | 2 - .../app/src/Element/User/FollowButton.tsx | 10 ++- .../app/src/Element/User/ProfileImage.css | 12 +++ .../app/src/Element/User/ProfileImage.tsx | 87 ++++++++++++++++--- .../app/src/Element/User/ProfilePreview.css | 4 - .../app/src/Element/User/ProfilePreview.tsx | 2 + .../app/src/Element/User/UserWebsiteLink.css | 13 +++ .../app/src/Element/User/UserWebsiteLink.tsx | 29 +++++++ .../app/src/Hooks/useTextTransformCache.tsx | 13 +-- packages/app/src/Pages/ProfilePage.css | 8 -- packages/app/src/Pages/ProfilePage.tsx | 25 +----- packages/app/src/index.css | 30 +++---- packages/system/src/text.ts | 2 +- yarn.lock | 11 +++ 16 files changed, 184 insertions(+), 75 deletions(-) delete mode 100644 packages/app/src/Element/User/FollowButton.css create mode 100644 packages/app/src/Element/User/UserWebsiteLink.css create mode 100644 packages/app/src/Element/User/UserWebsiteLink.tsx diff --git a/packages/app/package.json b/packages/app/package.json index c6820064..0a5dd253 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -16,6 +16,7 @@ "@snort/system-web": "workspace:*", "@szhsin/react-menu": "^3.3.1", "@types/use-sync-external-store": "^0.0.4", + "@uidotdev/usehooks": "^2.3.1", "@void-cat/api": "^1.0.4", "debug": "^4.3.4", "dexie": "^3.2.4", diff --git a/packages/app/src/Element/Text.tsx b/packages/app/src/Element/Text.tsx index 06f00ffd..fb31ad9b 100644 --- a/packages/app/src/Element/Text.tsx +++ b/packages/app/src/Element/Text.tsx @@ -248,9 +248,13 @@ export default function Text({ chunks.push(); } if (element.type === "link" || (element.type === "media" && element.mimeType?.startsWith("unknown"))) { - chunks.push( - , - ); + if (disableMedia ?? false) { + chunks.push(); + } else { + chunks.push( + , + ); + } } if (element.type === "custom_emoji") { chunks.push(); diff --git a/packages/app/src/Element/User/FollowButton.css b/packages/app/src/Element/User/FollowButton.css deleted file mode 100644 index 66f05526..00000000 --- a/packages/app/src/Element/User/FollowButton.css +++ /dev/null @@ -1,2 +0,0 @@ -.follow-button { -} diff --git a/packages/app/src/Element/User/FollowButton.tsx b/packages/app/src/Element/User/FollowButton.tsx index 5952b7fd..89c7059a 100644 --- a/packages/app/src/Element/User/FollowButton.tsx +++ b/packages/app/src/Element/User/FollowButton.tsx @@ -1,4 +1,3 @@ -import "./FollowButton.css"; import FormattedMessage from "Element/FormattedMessage"; import { HexKey } from "@snort/system"; @@ -20,7 +19,7 @@ export default function FollowButton(props: FollowButtonProps) { const publisher = useEventPublisher(); const { follows, relays, readonly } = useLogin(s => ({ follows: s.follows, relays: s.relays, readonly: s.readonly })); const isFollowing = follows.item.includes(pubkey); - const baseClassname = `${props.className ? ` ${props.className}` : ""}follow-button`; + const baseClassname = props.className ? `${props.className} ` : ""; async function follow(pubkey: HexKey) { if (publisher) { @@ -42,9 +41,12 @@ export default function FollowButton(props: FollowButtonProps) { return ( (isFollowing ? unfollow(pubkey) : follow(pubkey))}> + onClick={e => { + e.stopPropagation(); + isFollowing ? unfollow(pubkey) : follow(pubkey); + }}> {isFollowing ? : } ); diff --git a/packages/app/src/Element/User/ProfileImage.css b/packages/app/src/Element/User/ProfileImage.css index 29d1c906..a6f28988 100644 --- a/packages/app/src/Element/User/ProfileImage.css +++ b/packages/app/src/Element/User/ProfileImage.css @@ -43,3 +43,15 @@ a.pfp { background-color: var(--gray-superdark); transform: rotate(135deg); } + +.profile-card { + width: 360px; + border-radius: 16px; + background: var(--gray-superdark); + box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.05); +} + +.profile-card > div { + color: white; + padding: 8px 12px; +} diff --git a/packages/app/src/Element/User/ProfileImage.tsx b/packages/app/src/Element/User/ProfileImage.tsx index 7fc6d6b1..1a7b97aa 100644 --- a/packages/app/src/Element/User/ProfileImage.tsx +++ b/packages/app/src/Element/User/ProfileImage.tsx @@ -1,9 +1,11 @@ import "./ProfileImage.css"; -import React, { ReactNode } from "react"; +import React, { ReactNode, useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { HexKey, UserMetadata } from "@snort/system"; import { useUserProfile } from "@snort/system-react"; +import { useHover } from "@uidotdev/usehooks"; +import { ControlledMenu } from "@szhsin/react-menu"; import { profileLink } from "SnortUtils"; import Avatar from "Element/User/Avatar"; @@ -11,6 +13,9 @@ import Nip05 from "Element/User/Nip05"; import useLogin from "Hooks/useLogin"; import Icon from "Icons/Icon"; import DisplayName from "./DisplayName"; +import Text from "Element/Text"; +import FollowButton from "Element/User/FollowButton"; +import { UserWebsiteLink } from "Element/User/UserWebsiteLink"; export interface ProfileImageProps { pubkey: HexKey; @@ -27,6 +32,7 @@ export interface ProfileImageProps { imageOverlay?: ReactNode; showFollowingMark?: boolean; icons?: ReactNode; + showProfileCard?: boolean; } export default function ProfileImage({ @@ -44,11 +50,29 @@ export default function ProfileImage({ onClick, showFollowingMark = true, icons, + showProfileCard, }: ProfileImageProps) { const user = useUserProfile(profile ? "" : pubkey) ?? profile; const nip05 = defaultNip ? defaultNip : user?.nip05; const { follows } = useLogin(); const doesFollow = follows.item.includes(pubkey); + const [ref, hovering] = useHover(); + const [showProfileMenu, setShowProfileMenu] = useState(false); + const [t, setT] = useState>(); + + useEffect(() => { + if (hovering) { + const tn = setTimeout(() => { + setShowProfileMenu(true); + }, 1000); + setT(tn); + } else { + if (t) { + clearTimeout(t); + setT(undefined); + } + } + }, [hovering]); function handleClick(e: React.MouseEvent) { if (link === "") { @@ -60,7 +84,7 @@ export default function ProfileImage({ function inner() { return ( <> -
+
setShowProfileMenu(false)}> +
+
+ +
+ {/**/} + +
+
+ + +
+ + ); + } + return null; + } + if (link === "") { return ( -
- {inner()} -
+ <> +
+ {inner()} +
+ {profileCard()} + ); } else { return ( - - {inner()} - + <> + + {inner()} + + {profileCard()} + ); } } diff --git a/packages/app/src/Element/User/ProfilePreview.css b/packages/app/src/Element/User/ProfilePreview.css index 00211cc0..67f7cc3e 100644 --- a/packages/app/src/Element/User/ProfilePreview.css +++ b/packages/app/src/Element/User/ProfilePreview.css @@ -15,7 +15,3 @@ overflow: hidden; text-overflow: ellipsis; } - -.profile-preview button { - min-width: 98px; -} diff --git a/packages/app/src/Element/User/ProfilePreview.tsx b/packages/app/src/Element/User/ProfilePreview.tsx index 1fd65517..19ba98b1 100644 --- a/packages/app/src/Element/User/ProfilePreview.tsx +++ b/packages/app/src/Element/User/ProfilePreview.tsx @@ -12,6 +12,7 @@ export interface ProfilePreviewProps { options?: { about?: boolean; linkToProfile?: boolean; + profileCards?: boolean; }; profile?: UserMetadata; actions?: ReactNode; @@ -45,6 +46,7 @@ export default function ProfilePreview(props: ProfilePreviewProps) { profile={props.profile} link={options.linkToProfile ?? true ? undefined : ""} subHeader={options.about ?
{user?.about}
: undefined} + showProfileCard={options.profileCards} /> {props.actions ?? (
diff --git a/packages/app/src/Element/User/UserWebsiteLink.css b/packages/app/src/Element/User/UserWebsiteLink.css new file mode 100644 index 00000000..0f81ef93 --- /dev/null +++ b/packages/app/src/Element/User/UserWebsiteLink.css @@ -0,0 +1,13 @@ +.user-profile-link { + display: flex; + align-items: center; + gap: 8px; +} + +.user-profile-link a { + text-decoration: none; +} + +.user-profile-link a:hover { + text-decoration: underline; +} diff --git a/packages/app/src/Element/User/UserWebsiteLink.tsx b/packages/app/src/Element/User/UserWebsiteLink.tsx new file mode 100644 index 00000000..e5dbbd7a --- /dev/null +++ b/packages/app/src/Element/User/UserWebsiteLink.tsx @@ -0,0 +1,29 @@ +import "./UserWebsiteLink.css"; +import { MetadataCache, UserMetadata } from "@snort/system"; +import Icon from "Icons/Icon"; + +export function UserWebsiteLink({ user }: { user?: MetadataCache | UserMetadata }) { + const website_url = + user?.website && !user.website.startsWith("http") ? "https://" + user.website : user?.website || ""; + + function tryFormatWebsite(url: string) { + try { + const u = new URL(url); + return `${u.hostname}${u.pathname !== "/" ? u.pathname : ""}`; + } catch { + // ignore + } + return url; + } + + if (user?.website) { + return ( + + ); + } +} diff --git a/packages/app/src/Hooks/useTextTransformCache.tsx b/packages/app/src/Hooks/useTextTransformCache.tsx index 07442043..4c562658 100644 --- a/packages/app/src/Hooks/useTextTransformCache.tsx +++ b/packages/app/src/Hooks/useTextTransformCache.tsx @@ -3,11 +3,14 @@ import { ParsedFragment, transformText } from "@snort/system"; const TextCache = new Map>(); export function transformTextCached(id: string, content: string, tags: Array>) { - const cached = TextCache.get(id); - if (cached) return cached; - const newCache = transformText(content, tags); - TextCache.set(id, newCache); - return newCache; + if (content.length > 0) { + const cached = TextCache.get(id); + if (cached) return cached; + const newCache = transformText(content, tags); + TextCache.set(id, newCache); + return newCache; + } + return []; } export function useTextTransformer(id: string, content: string, tags: Array>) { diff --git a/packages/app/src/Pages/ProfilePage.css b/packages/app/src/Pages/ProfilePage.css index eb4e3c5e..6c0851eb 100644 --- a/packages/app/src/Pages/ProfilePage.css +++ b/packages/app/src/Pages/ProfilePage.css @@ -138,14 +138,6 @@ gap: 8px; } -.profile .website a { - text-decoration: none; -} - -.profile .website a:hover { - text-decoration: underline; -} - .profile .link svg { color: var(--highlight); } diff --git a/packages/app/src/Pages/ProfilePage.tsx b/packages/app/src/Pages/ProfilePage.tsx index 5c2501ad..1660f462 100644 --- a/packages/app/src/Pages/ProfilePage.tsx +++ b/packages/app/src/Pages/ProfilePage.tsx @@ -59,6 +59,7 @@ import { useStatusFeed } from "Feed/StatusFeed"; import messages from "./messages"; import { SpotlightMediaModal } from "Element/Deck/SpotlightMedia"; +import { UserWebsiteLink } from "Element/User/UserWebsiteLink"; const NOTES = 0; const REACTIONS = 1; @@ -135,8 +136,6 @@ export default function ProfilePage() { const showBadges = login.preferences.showBadges ?? false; const showStatus = login.preferences.showStatus ?? true; - const website_url = - user?.website && !user.website.startsWith("http") ? "https://" + user.website : user?.website || ""; // feeds const { blocked } = useModeration(); const pinned = usePinnedFeed(id); @@ -295,28 +294,10 @@ export default function ProfilePage() { ); } - function tryFormatWebsite(url: string) { - try { - const u = new URL(url); - return `${u.hostname}${u.pathname !== "/" ? u.pathname : ""}`; - } catch { - // ignore - } - return url; - } - function links() { return ( <> - {user?.website && ( - - )} - + {lnurl && (
setShowLnQr(true)}> @@ -433,7 +414,7 @@ export default function ProfilePage() { setModalImage(user?.picture || "")} className="pointer" />
{renderIcons()} - {!isMe && id && } + {!isMe && id && }
); diff --git a/packages/app/src/index.css b/packages/app/src/index.css index 456f4bba..3f498f57 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -113,6 +113,21 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } +a { + color: inherit; + line-height: 1.3em; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +a.ext { + word-break: break-all; + white-space: initial; +} + #root { overflow-x: hidden; } @@ -498,21 +513,6 @@ input:disabled { max-width: -moz-available; } -a { - color: inherit; - line-height: 1.3em; - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -a.ext { - word-break: break-all; - white-space: initial; -} - div.form { display: grid; grid-auto-flow: row; diff --git a/packages/system/src/text.ts b/packages/system/src/text.ts index 5988b40a..b6aebd7d 100644 --- a/packages/system/src/text.ts +++ b/packages/system/src/text.ts @@ -233,7 +233,7 @@ export function transformText(body: string, tags: Array>) { fragments = fragments .map(a => { if (typeof a === "string") { - if (a.trim().length > 0) { + if (a.length > 0) { return { type: "text", content: a } as ParsedFragment; } } else { diff --git a/yarn.lock b/yarn.lock index 00f048c7..5f2ef34f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2714,6 +2714,7 @@ __metadata: "@types/webtorrent": ^0.109.3 "@typescript-eslint/eslint-plugin": ^6.1.0 "@typescript-eslint/parser": ^6.1.0 + "@uidotdev/usehooks": ^2.3.1 "@void-cat/api": ^1.0.4 "@webbtc/webln-types": ^1.0.10 "@webpack-cli/generators": ^3.0.4 @@ -3852,6 +3853,16 @@ __metadata: languageName: node linkType: hard +"@uidotdev/usehooks@npm:^2.3.1": + version: 2.3.1 + resolution: "@uidotdev/usehooks@npm:2.3.1" + peerDependencies: + react: ">=18.0.0" + react-dom: ">=18.0.0" + checksum: a1339b91bdb4176f59fc2dd8273065fccacb17749b7022879982ff874bda8e4e54a3f8d74f126e6224164fb2ad422f1cc40dac8705467960df525b207fcd3a79 + languageName: node + linkType: hard + "@void-cat/api@npm:^1.0.4": version: 1.0.7 resolution: "@void-cat/api@npm:1.0.7" From 9d33abbf1ef3df27fba7199699d192d96e0c29a5 Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 10 Oct 2023 10:37:53 +0100 Subject: [PATCH 16/19] feature flags config / typed app config --- packages/app/config/default.json | 5 +- packages/app/config/iris.json | 5 +- packages/app/custom.d.ts | 14 +++ packages/app/src/Element/FormattedMessage.tsx | 2 +- packages/app/src/Element/Logo.tsx | 2 +- packages/app/src/Element/PinPrompt.tsx | 2 +- packages/app/src/Element/User/DisplayName.tsx | 4 +- packages/app/src/Feed/LoginFeed.ts | 2 +- packages/app/src/Pages/DonatePage.tsx | 4 +- packages/app/src/Pages/Layout.tsx | 2 +- packages/app/src/Pages/LoginPage.tsx | 6 +- packages/app/src/Pages/NostrLinkHandler.tsx | 2 +- .../app/src/Pages/Profile/ProfilePage.tsx | 4 +- packages/app/src/Pages/new/messages.ts | 10 +-- packages/app/src/Pages/settings/Root.tsx | 14 +-- packages/app/src/Tasks/DonateTask.tsx | 2 +- packages/app/src/index.tsx | 26 +++++- packages/app/webpack.config.js | 6 +- packages/system-react/src/useUserProfile.ts | 18 ---- packages/system/src/profile-cache.ts | 87 +++++++++---------- 20 files changed, 118 insertions(+), 99 deletions(-) diff --git a/packages/app/config/default.json b/packages/app/config/default.json index 59de7693..8c5714d2 100644 --- a/packages/app/config/default.json +++ b/packages/app/config/default.json @@ -6,5 +6,8 @@ "favicon": "public/favicon.ico", "appleTouchIconUrl": "/nostrich_512.png", "httpCache": "", - "animalNamePlaceholders": false + "animalNamePlaceholders": false, + "features": { + "subscriptions": true + } } diff --git a/packages/app/config/iris.json b/packages/app/config/iris.json index 823281ed..b1f59e8c 100644 --- a/packages/app/config/iris.json +++ b/packages/app/config/iris.json @@ -6,5 +6,8 @@ "favicon": "public/iris/favicon.ico", "appleTouchIconUrl": "/img/apple-touch-icon.png", "httpCache": "https://api.iris.to", - "animalNamePlaceholders": true + "animalNamePlaceholders": true, + "features": { + "subscriptions": false + } } diff --git a/packages/app/custom.d.ts b/packages/app/custom.d.ts index 8e871bb8..17267e74 100644 --- a/packages/app/custom.d.ts +++ b/packages/app/custom.d.ts @@ -34,3 +34,17 @@ declare module "emojilib" { const value: Record; export default value; } + +declare const CONFIG: { + appName: string; + appNameCapitalized: string; + appTitle: string; + nip05Domain: string; + favicon: string; + appleTouchIconUrl: string; + httpCache: string; + animalNamePlaceholders: boolean; + features: { + subscriptions: boolean; + }; +}; diff --git a/packages/app/src/Element/FormattedMessage.tsx b/packages/app/src/Element/FormattedMessage.tsx index e2978482..77141d39 100644 --- a/packages/app/src/Element/FormattedMessage.tsx +++ b/packages/app/src/Element/FormattedMessage.tsx @@ -12,7 +12,7 @@ const ExtendedFormattedMessage: FC = props => { useEffect(() => { const translatedMessage = formatMessage({ id, defaultMessage }, values); if (typeof translatedMessage === "string") { - setProcessedMessage(translatedMessage.replace("Snort", process.env.APP_NAME_CAPITALIZED || "Snort")); + setProcessedMessage(translatedMessage.replace("Snort", CONFIG.appNameCapitalized || "Snort")); } }, [id, defaultMessage, values, formatMessage]); diff --git a/packages/app/src/Element/Logo.tsx b/packages/app/src/Element/Logo.tsx index d33e3f95..7647e2ab 100644 --- a/packages/app/src/Element/Logo.tsx +++ b/packages/app/src/Element/Logo.tsx @@ -4,7 +4,7 @@ const Logo = () => { const navigate = useNavigate(); return (

navigate("/")}> - {process.env.APP_NAME} + {CONFIG.appNameCapitalized}

); }; diff --git a/packages/app/src/Element/PinPrompt.tsx b/packages/app/src/Element/PinPrompt.tsx index a9412931..fa8d5e2e 100644 --- a/packages/app/src/Element/PinPrompt.tsx +++ b/packages/app/src/Element/PinPrompt.tsx @@ -145,7 +145,7 @@ export function LoginUnlock() {

diff --git a/packages/app/src/Element/User/DisplayName.tsx b/packages/app/src/Element/User/DisplayName.tsx index b7e58f8f..e3fe57fa 100644 --- a/packages/app/src/Element/User/DisplayName.tsx +++ b/packages/app/src/Element/User/DisplayName.tsx @@ -1,6 +1,6 @@ import "./DisplayName.css"; -import React, { useMemo } from "react"; +import { useMemo } from "react"; import { HexKey, UserMetadata, NostrPrefix } from "@snort/system"; import AnimalName from "Element/User/AnimalName"; import { hexToBech32 } from "SnortUtils"; @@ -22,7 +22,7 @@ export function getDisplayNameOrPlaceHolder(user: UserMetadata | undefined, pubk name = user.display_name; } else if (typeof user?.name === "string" && user.name.length > 0) { name = user.name; - } else if (pubkey && process.env.ANIMAL_NAME_PLACEHOLDERS) { + } else if (pubkey && CONFIG.animalNamePlaceholders) { name = AnimalName(pubkey); isPlaceHolder = true; } diff --git a/packages/app/src/Feed/LoginFeed.ts b/packages/app/src/Feed/LoginFeed.ts index 14f06539..400bb6dc 100644 --- a/packages/app/src/Feed/LoginFeed.ts +++ b/packages/app/src/Feed/LoginFeed.ts @@ -49,7 +49,7 @@ export default function useLoginFeed() { leaveOpen: true, }); b.withFilter().authors([pubKey]).kinds([EventKind.ContactList]); - if (!login.readonly) { + if (CONFIG.features.subscriptions && !login.readonly) { b.withFilter().authors([pubKey]).kinds([EventKind.AppData]).tag("d", ["snort"]); b.withFilter() .relay("wss://relay.snort.social") diff --git a/packages/app/src/Pages/DonatePage.tsx b/packages/app/src/Pages/DonatePage.tsx index f1df62c8..7617286b 100644 --- a/packages/app/src/Pages/DonatePage.tsx +++ b/packages/app/src/Pages/DonatePage.tsx @@ -94,13 +94,13 @@ const DonatePage = () => {

diff --git a/packages/app/src/Pages/Layout.tsx b/packages/app/src/Pages/Layout.tsx index a02150fd..001d2822 100644 --- a/packages/app/src/Pages/Layout.tsx +++ b/packages/app/src/Pages/Layout.tsx @@ -209,7 +209,7 @@ function LogoHeader() { return ( -

{process.env.APP_NAME}

+

{CONFIG.appName}

{currentSubscription && ( diff --git a/packages/app/src/Pages/LoginPage.tsx b/packages/app/src/Pages/LoginPage.tsx index ba068cc3..c8e0a899 100644 --- a/packages/app/src/Pages/LoginPage.tsx +++ b/packages/app/src/Pages/LoginPage.tsx @@ -143,7 +143,7 @@ export default function LoginPage() { function generateNip46() { const meta = { - name: process.env.APP_NAME_CAPITALIZED, + name: CONFIG.appNameCapitalized, url: window.location.href, }; @@ -287,7 +287,7 @@ export default function LoginPage() {

navigate("/")}> - {process.env.APP_NAME} + {CONFIG.appName}

@@ -342,7 +342,7 @@ export default function LoginPage() {

diff --git a/packages/app/src/Pages/NostrLinkHandler.tsx b/packages/app/src/Pages/NostrLinkHandler.tsx index 801ab794..d716bfca 100644 --- a/packages/app/src/Pages/NostrLinkHandler.tsx +++ b/packages/app/src/Pages/NostrLinkHandler.tsx @@ -25,7 +25,7 @@ export default function NostrLinkHandler() { } } else { try { - const pubkey = await getNip05PubKey(`${link}@${process.env.NIP05_DOMAIN}`); + const pubkey = await getNip05PubKey(`${link}@${CONFIG.nip05Domain}`); if (pubkey) { setRenderComponent(); // Directly render ProfilePage } diff --git a/packages/app/src/Pages/Profile/ProfilePage.tsx b/packages/app/src/Pages/Profile/ProfilePage.tsx index 81b15b6e..fb515ddc 100644 --- a/packages/app/src/Pages/Profile/ProfilePage.tsx +++ b/packages/app/src/Pages/Profile/ProfilePage.tsx @@ -139,8 +139,8 @@ export default function ProfilePage({ id: propId }: ProfilePageProps) { useEffect(() => { if (user?.nip05 && user?.isNostrAddressValid) { - if (user.nip05.endsWith(`@${process.env.NIP05_DOMAIN}`)) { - const username = user.nip05?.replace(`@${process.env.NIP05_DOMAIN}`, ""); + if (user.nip05.endsWith(`@${CONFIG.nip05Domain}`)) { + const username = user.nip05?.replace(`@${CONFIG.nip05Domain}`, ""); navigate(`/${username}`, { replace: true }); } } diff --git a/packages/app/src/Pages/new/messages.ts b/packages/app/src/Pages/new/messages.ts index 5e7d0452..2a86b565 100644 --- a/packages/app/src/Pages/new/messages.ts +++ b/packages/app/src/Pages/new/messages.ts @@ -14,11 +14,11 @@ export default defineMessages({ KeysSaved: { defaultMessage: "I have saved my keys, continue" }, WhatIsSnort: { defaultMessage: "What is {site} and how does it work?", - values: { site: process.env.APP_NAME_CAPITALIZED }, + values: { site: CONFIG.appNameCapitalized }, }, WhatIsSnortIntro: { defaultMessage: `{site} is a Nostr UI, nostr is a decentralised protocol for saving and distributing "notes".`, - values: { site: process.env.APP_NAME_CAPITALIZED }, + values: { site: CONFIG.appNameCapitalized }, }, WhatIsSnortNotes: { defaultMessage: `Notes hold text content, the most popular usage of these notes is to store "tweet like" messages.`, @@ -26,7 +26,7 @@ export default defineMessages({ WhatIsSnortExperience: { defaultMessage: "{site} is designed to have a similar experience to Twitter.", - values: { site: process.env.APP_NAME_CAPITALIZED }, + values: { site: CONFIG.appNameCapitalized }, }, HowKeysWork: { defaultMessage: "How do keys work?" }, DigitalSignatures: { @@ -70,9 +70,9 @@ export default defineMessages({ NameSquatting: { defaultMessage: "Name-squatting and impersonation is not allowed. {site} and our partners reserve the right to terminate your handle (not your account - nobody can take that away) for violating this rule.", - values: { site: process.env.APP_NAME_CAPITALIZED }, + values: { site: CONFIG.appNameCapitalized }, }, - PreviewOnSnort: { defaultMessage: "Preview on {site}", values: { site: process.env.APP_NAME_CAPITALIZED } }, + PreviewOnSnort: { defaultMessage: "Preview on {site}", values: { site: CONFIG.appNameCapitalized } }, GetSnortId: { defaultMessage: "Get a Snort identifier" }, GetSnortIdHelp: { defaultMessage: diff --git a/packages/app/src/Pages/settings/Root.tsx b/packages/app/src/Pages/settings/Root.tsx index 8edab2b8..3383b886 100644 --- a/packages/app/src/Pages/settings/Root.tsx +++ b/packages/app/src/Pages/settings/Root.tsx @@ -6,9 +6,9 @@ import Icon from "Icons/Icon"; import { LoginStore, logout } from "Login"; import useLogin from "Hooks/useLogin"; import { getCurrentSubscription } from "Subscription"; +import usePageWidth from "Hooks/usePageWidth"; import messages from "./messages"; -import usePageWidth from "Hooks/usePageWidth"; const SettingsIndex = () => { const login = useLogin(); @@ -61,11 +61,13 @@ const SettingsIndex = () => {

-
navigate("/subscribe/manage")}> - - - -
+ {CONFIG.features.subscriptions && ( +
navigate("/subscribe/manage")}> + + + +
+ )} {sub && (
navigate("accounts")}> diff --git a/packages/app/src/Tasks/DonateTask.tsx b/packages/app/src/Tasks/DonateTask.tsx index f6431f49..041e18df 100644 --- a/packages/app/src/Tasks/DonateTask.tsx +++ b/packages/app/src/Tasks/DonateTask.tsx @@ -15,7 +15,7 @@ export class DonateTask extends BaseUITask {

diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index 417a9395..2a52a23c 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -16,6 +16,7 @@ import { ReqFilter, PowMiner, NostrEvent, + mapEventToProfile, } from "@snort/system"; import { SnortContext } from "@snort/system-react"; @@ -87,6 +88,29 @@ export const System = new NostrSystem({ }, }); +async function fetchProfile(key: string) { + const rsp = await fetch(`${CONFIG.httpCache}/profile/${key}`); + if (rsp.ok) { + try { + const data = (await rsp.json()) as NostrEvent; + if (data) { + return mapEventToProfile(data); + } + } catch (e) { + console.error(e); + } + } +} + +/** + * Add profile loader fn + */ +if (CONFIG.httpCache) { + System.ProfileLoader.loaderFn = async (keys: Array) => { + return (await Promise.all(keys.map(a => fetchProfile(a)))).filter(a => a !== undefined).map(a => unwrap(a)); + }; +} + /** * Singleton user profile loader */ @@ -191,7 +215,7 @@ export const router = createBrowserRouter([ }, ...NewUserRoutes, ...WalletRoutes, - ...SubscribeRoutes, + ...(CONFIG.features.subscriptions ? SubscribeRoutes : []), { path: "/debug", element: , diff --git a/packages/app/webpack.config.js b/packages/app/webpack.config.js index c20183fa..4f40b87f 100644 --- a/packages/app/webpack.config.js +++ b/packages/app/webpack.config.js @@ -88,11 +88,7 @@ const config = { }) : false, new DefinePlugin({ - "process.env.APP_NAME": JSON.stringify(appConfig.get("appName")), - "process.env.APP_NAME_CAPITALIZED": JSON.stringify(appConfig.get("appNameCapitalized")), - "process.env.NIP05_DOMAIN": JSON.stringify(appConfig.get("nip05Domain")), - "process.env.HTTP_CACHE": JSON.stringify(appConfig.get("httpCache")), - "process.env.ANIMAL_NAME_PLACEHOLDERS": JSON.stringify(appConfig.get("animalNamePlaceholders")), + CONFIG: JSON.stringify(appConfig), }), ], module: { diff --git a/packages/system-react/src/useUserProfile.ts b/packages/system-react/src/useUserProfile.ts index d6b12585..5d718761 100644 --- a/packages/system-react/src/useUserProfile.ts +++ b/packages/system-react/src/useUserProfile.ts @@ -11,24 +11,6 @@ export function useUserProfile(pubKey?: HexKey): MetadataCache | undefined { h => { if (pubKey) { system.ProfileLoader.TrackMetadata(pubKey); - if (process.env.HTTP_CACHE && !system.ProfileLoader.Cache.getFromCache(pubKey)) { - fetch(`${process.env.HTTP_CACHE}/profile/${pubKey}`) - .then(async r => { - if (r.ok) { - try { - const data = await r.json(); - if (data) { - system.ProfileLoader.onProfileEvent(data); - } - } catch (e) { - console.error(e); - } - } - }) - .catch(e => { - console.error(e); - }); - } } const release = system.ProfileLoader.Cache.hook(h, pubKey); return () => { diff --git a/packages/system/src/profile-cache.ts b/packages/system/src/profile-cache.ts index 6118a8ae..ee5646c8 100644 --- a/packages/system/src/profile-cache.ts +++ b/packages/system/src/profile-cache.ts @@ -1,8 +1,9 @@ import debug from "debug"; import { unixNowMs, FeedCache } from "@snort/shared"; -import { EventKind, HexKey, SystemInterface, TaggedNostrEvent, NoteCollection, RequestBuilder } from "."; +import { EventKind, HexKey, SystemInterface, TaggedNostrEvent, RequestBuilder } from "."; import { ProfileCacheExpire } from "./const"; import { mapEventToProfile, MetadataCache } from "./cache"; +import { v4 as uuid } from "uuid"; const MetadataRelays = ["wss://purplepag.es"]; @@ -23,6 +24,11 @@ export class ProfileLoaderService { readonly #log = debug("ProfileCache"); + /** + * Custom loader function for fetching profiles from alternative sources + */ + loaderFn?: (pubkeys: Array) => Promise>; + constructor(system: SystemInterface, cache: FeedCache) { this.#system = system; this.#cache = cache; @@ -92,50 +98,8 @@ export class ProfileLoaderService { if (missing.size > 0) { this.#log("Wants profiles: %d missing, %d expired", missingFromCache.length, expired.length); - const sub = new RequestBuilder("profiles"); - sub - .withOptions({ - skipDiff: true, - }) - .withFilter() - .kinds([EventKind.SetMetadata]) - .authors([...missing]); + const results = await this.#loadProfiles([...missing]); - if (this.#missingLastRun.size > 0) { - const fMissing = sub - .withFilter() - .kinds([EventKind.SetMetadata]) - .authors([...this.#missingLastRun]); - MetadataRelays.forEach(r => fMissing.relay(r)); - } - const newProfiles = new Set(); - const q = this.#system.Query(NoteCollection, sub); - const feed = (q?.feed as NoteCollection) ?? new NoteCollection(); - // never release this callback, it will stop firing anyway after eose - const releaseOnEvent = feed.onEvent(async e => { - for (const pe of e) { - newProfiles.add(pe.id); - await this.onProfileEvent(pe); - } - }); - const results = await new Promise>>(resolve => { - let timeout: ReturnType | undefined = undefined; - const release = feed.hook(() => { - if (!feed.loading) { - clearTimeout(timeout); - resolve(feed.getSnapshotData() ?? []); - this.#log("Profiles finished: %s", sub.id); - release(); - } - }); - timeout = setTimeout(() => { - release(); - resolve(feed.getSnapshotData() ?? []); - this.#log("Profiles timeout: %s", sub.id); - }, 5_000); - }); - - releaseOnEvent(); const couldNotFetch = [...missing].filter(a => !results.some(b => b.pubkey === a)); this.#missingLastRun = new Set(couldNotFetch); if (couldNotFetch.length > 0) { @@ -150,12 +114,43 @@ export class ProfileLoaderService { await Promise.all(empty); } - // When we fetch an expired profile and its the same as what we already have + /* When we fetch an expired profile and its the same as what we already have // onEvent is not fired and the loaded timestamp never gets updated const expiredSame = results.filter(a => !newProfiles.has(a.id) && expired.includes(a.pubkey)); - await Promise.all(expiredSame.map(v => this.onProfileEvent(v))); + await Promise.all(expiredSame.map(v => this.onProfileEvent(v)));*/ } setTimeout(() => this.#FetchMetadata(), 500); } + + async #loadProfiles(missing: Array) { + if (this.loaderFn) { + const results = await this.loaderFn(missing); + await Promise.all(results.map(a => this.#cache.update(a))); + return results; + } else { + const sub = new RequestBuilder(`profiles-${uuid()}`); + sub + .withOptions({ + skipDiff: true, + }) + .withFilter() + .kinds([EventKind.SetMetadata]) + .authors(missing); + + if (this.#missingLastRun.size > 0) { + const fMissing = sub + .withFilter() + .kinds([EventKind.SetMetadata]) + .authors([...this.#missingLastRun]); + MetadataRelays.forEach(r => fMissing.relay(r)); + } + const results = (await this.#system.Fetch(sub, async e => { + for (const pe of e) { + await this.onProfileEvent(pe); + } + })) as ReadonlyArray; + return results; + } + } } From 672255187f7709d2dedc54f8a3c6fc2717956af9 Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 10 Oct 2023 09:43:20 +0000 Subject: [PATCH 17/19] chore: Update translations --- packages/app/src/translations/de_DE.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/app/src/translations/de_DE.json b/packages/app/src/translations/de_DE.json index a876fafa..0a343004 100644 --- a/packages/app/src/translations/de_DE.json +++ b/packages/app/src/translations/de_DE.json @@ -13,7 +13,7 @@ "/PCavi": "Öffentlich", "/RD0e2": "Nostr nutzt digitale Signaturen, um manipulationssichere Notes zu erstellen, welche sicher auf viele Relais repliziert werden können, um eine redundante Speicherung deiner Inhalte zu bieten.", "/Xf4UW": "Anonyme Nutzungsmetriken senden", - "/clOBU": "Weekly", + "/clOBU": "Wöchentlich", "/d6vEc": "Mach dein Profil leichter zu finden und zu teilen", "/n5KSF": "{n} ms", "00LcfG": "Mehr laden", @@ -232,7 +232,7 @@ "P7FD0F": "System (Standard)", "P7nJT9": "Gesamt heute (UTC): {amount} sats", "PCSt5T": "Einstellungen", - "PJeJFc": "Summary", + "PJeJFc": "Übersicht", "PLSbmL": "Ihre mnemonische Passphrase", "PaN7t3": "Vorschau auf {site}", "PamNxw": "Unbekannter Datei-Header: {name}", @@ -496,5 +496,5 @@ "zonsdq": "Fehler beim Laden des LNURL-Dienstes", "zvCDao": "Neueste Notes automatisch anzeigen", "zwb6LR": "Mint: {url}", - "zxvhnE": "Daily" + "zxvhnE": "Täglich" } From 50bfd9eaa0a2ab1d9429961269f06e33f56cab54 Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 10 Oct 2023 12:20:37 +0100 Subject: [PATCH 18/19] setup stalker mode --- packages/app/src/Cache/GiftWrapCache.ts | 2 +- packages/app/src/Cache/Notifications.ts | 17 +++++++---- packages/app/src/Cache/RefreshFeedCache.ts | 10 +++++-- packages/app/src/Db/index.ts | 8 +++-- .../app/src/Element/User/ProfileImage.tsx | 6 ++-- .../app/src/Hooks/useRefreshFeedcache.tsx | 6 ++-- packages/app/src/Login/LoginSession.ts | 5 ++++ packages/app/src/Login/MultiAccountStore.ts | 3 ++ packages/app/src/Pages/Layout.css | 19 ++++++++++++ packages/app/src/Pages/Layout.tsx | 13 ++++++++ packages/app/src/Pages/Notifications.tsx | 30 +++++++++++-------- 11 files changed, 88 insertions(+), 31 deletions(-) diff --git a/packages/app/src/Cache/GiftWrapCache.ts b/packages/app/src/Cache/GiftWrapCache.ts index 2c999ac9..25a3ee9f 100644 --- a/packages/app/src/Cache/GiftWrapCache.ts +++ b/packages/app/src/Cache/GiftWrapCache.ts @@ -24,7 +24,7 @@ export class GiftWrapCache extends RefreshFeedCache { return [...this.cache.values()]; } - override async onEvent(evs: Array, pub?: EventPublisher) { + override async onEvent(evs: Array, _: string, pub?: EventPublisher) { if (!pub) return; const unwrapped = ( diff --git a/packages/app/src/Cache/Notifications.ts b/packages/app/src/Cache/Notifications.ts index 6f606f19..15ef558a 100644 --- a/packages/app/src/Cache/Notifications.ts +++ b/packages/app/src/Cache/Notifications.ts @@ -1,11 +1,11 @@ import { EventKind, NostrEvent, RequestBuilder, TaggedNostrEvent } from "@snort/system"; import { RefreshFeedCache, TWithCreated } from "./RefreshFeedCache"; import { LoginSession } from "Login"; -import { db } from "Db"; +import { NostrEventForSession, db } from "Db"; import { Day } from "Const"; import { unixNow } from "@snort/shared"; -export class NotificationsCache extends RefreshFeedCache { +export class NotificationsCache extends RefreshFeedCache { #kinds = [EventKind.TextNote, EventKind.Reaction, EventKind.Repost, EventKind.ZapReceipt]; constructor() { @@ -14,7 +14,7 @@ export class NotificationsCache extends RefreshFeedCache { buildSub(session: LoginSession, rb: RequestBuilder) { if (session.publicKey) { - const newest = this.newest(); + const newest = this.newest(v => v.tags.some(a => a[0] === "p" && a[1] === session.publicKey)); rb.withFilter() .kinds(this.#kinds) .tag("p", [session.publicKey]) @@ -22,10 +22,15 @@ export class NotificationsCache extends RefreshFeedCache { } } - async onEvent(evs: readonly TaggedNostrEvent[]) { + async onEvent(evs: readonly TaggedNostrEvent[], pubKey: string) { const filtered = evs.filter(a => this.#kinds.includes(a.kind) && a.tags.some(b => b[0] === "p")); if (filtered.length > 0) { - await this.bulkSet(filtered); + await this.bulkSet( + filtered.map(v => ({ + ...v, + forSession: pubKey, + })), + ); this.notifyChange(filtered.map(v => this.key(v))); } } @@ -34,7 +39,7 @@ export class NotificationsCache extends RefreshFeedCache { return of.id; } - takeSnapshot(): TWithCreated[] { + takeSnapshot() { return [...this.cache.values()]; } } diff --git a/packages/app/src/Cache/RefreshFeedCache.ts b/packages/app/src/Cache/RefreshFeedCache.ts index 29372ffb..f1eb75d5 100644 --- a/packages/app/src/Cache/RefreshFeedCache.ts +++ b/packages/app/src/Cache/RefreshFeedCache.ts @@ -6,14 +6,18 @@ export type TWithCreated = (T | Readonly) & { created_at: number }; export abstract class RefreshFeedCache extends FeedCache> { abstract buildSub(session: LoginSession, rb: RequestBuilder): void; - abstract onEvent(evs: Readonly>, pub?: EventPublisher): void; + abstract onEvent(evs: Readonly>, pubKey: string, pub?: EventPublisher): void; /** * Get latest event */ - protected newest() { + protected newest(filter?: (e: TWithCreated) => boolean) { let ret = 0; - this.cache.forEach(v => (ret = v.created_at > ret ? v.created_at : ret)); + this.cache.forEach(v => { + if (!filter || filter(v)) { + ret = v.created_at > ret ? v.created_at : ret; + } + }); return ret; } diff --git a/packages/app/src/Db/index.ts b/packages/app/src/Db/index.ts index ec85977a..11fe611c 100644 --- a/packages/app/src/Db/index.ts +++ b/packages/app/src/Db/index.ts @@ -2,7 +2,7 @@ import Dexie, { Table } from "dexie"; import { HexKey, NostrEvent, TaggedNostrEvent, u256 } from "@snort/system"; export const NAME = "snortDB"; -export const VERSION = 14; +export const VERSION = 15; export interface SubCache { id: string; @@ -35,6 +35,10 @@ export interface UnwrappedGift { tags?: Array>; // some tags extracted } +export type NostrEventForSession = TaggedNostrEvent & { + forSession: string; +}; + const STORES = { chats: "++id", eventInteraction: "++id", @@ -50,7 +54,7 @@ export class SnortDB extends Dexie { eventInteraction!: Table; payments!: Table; gifts!: Table; - notifications!: Table; + notifications!: Table; followsFeed!: Table; constructor() { diff --git a/packages/app/src/Element/User/ProfileImage.tsx b/packages/app/src/Element/User/ProfileImage.tsx index 1a7b97aa..b06e56b8 100644 --- a/packages/app/src/Element/User/ProfileImage.tsx +++ b/packages/app/src/Element/User/ProfileImage.tsx @@ -129,8 +129,10 @@ export default function ProfileImage({
- {/**/}
diff --git a/packages/app/src/Hooks/useRefreshFeedcache.tsx b/packages/app/src/Hooks/useRefreshFeedcache.tsx index 00fb2098..7ad793e3 100644 --- a/packages/app/src/Hooks/useRefreshFeedcache.tsx +++ b/packages/app/src/Hooks/useRefreshFeedcache.tsx @@ -5,6 +5,7 @@ import { NoopStore, RequestBuilder, TaggedNostrEvent } from "@snort/system"; import { RefreshFeedCache } from "Cache/RefreshFeedCache"; import useLogin from "./useLogin"; import useEventPublisher from "./useEventPublisher"; +import { unwrap } from "@snort/shared"; export function useRefreshFeedCache(c: RefreshFeedCache, leaveOpen = false) { const system = useContext(SnortContext); @@ -33,7 +34,7 @@ export function useRefreshFeedCache(c: RefreshFeedCache, leaveOpen = false tBuf = [...evs]; t = setTimeout(() => { t = undefined; - c.onEvent(tBuf, publisher); + c.onEvent(tBuf, unwrap(login.publicKey), publisher); }, 100); } else { tBuf.push(...evs); @@ -46,8 +47,5 @@ export function useRefreshFeedCache(c: RefreshFeedCache, leaveOpen = false releaseOnEvent(); }; } - return () => { - // noop - }; }, [sub]); } diff --git a/packages/app/src/Login/LoginSession.ts b/packages/app/src/Login/LoginSession.ts index 890d566b..766b341e 100644 --- a/packages/app/src/Login/LoginSession.ts +++ b/packages/app/src/Login/LoginSession.ts @@ -128,4 +128,9 @@ export interface LoginSession { * A list of chats which we have joined (NIP-28/NIP-29) */ extraChats: Array; + + /** + * Is login session in stalker mode + */ + stalker: boolean; } diff --git a/packages/app/src/Login/MultiAccountStore.ts b/packages/app/src/Login/MultiAccountStore.ts index 9496961a..a471744b 100644 --- a/packages/app/src/Login/MultiAccountStore.ts +++ b/packages/app/src/Login/MultiAccountStore.ts @@ -53,6 +53,7 @@ const LoggedOut = { timestamp: 0, }, extraChats: [], + stalker: false, } as LoginSession; export class MultiAccountStore extends ExternalStore { @@ -125,6 +126,7 @@ export class MultiAccountStore extends ExternalStore { relays?: Record, remoteSignerRelays?: Array, privateKey?: KeyStorage, + stalker?: boolean, ) { if (this.#accounts.has(key)) { throw new Error("Already logged in with this pubkey"); @@ -143,6 +145,7 @@ export class MultiAccountStore extends ExternalStore { preferences: deepClone(DefaultPreferences), remoteSignerRelays, privateKeyData: privateKey, + stalker: stalker ?? false, } as LoginSession; const pub = createPublisher(newSession); diff --git a/packages/app/src/Pages/Layout.css b/packages/app/src/Pages/Layout.css index 9e7600d1..7b5e9897 100644 --- a/packages/app/src/Pages/Layout.css +++ b/packages/app/src/Pages/Layout.css @@ -90,3 +90,22 @@ header { display: none; } } + +.stalker { + position: fixed; + top: 0; + width: 100vw; + height: 100vh; + box-shadow: 0px 0px 26px 0px rgba(139, 92, 246, 0.7) inset; + pointer-events: none; +} + +.stalker button { + position: absolute; + top: 50px; + right: 50px; + color: black; + background-color: var(--btn-color); + padding: 12px; + pointer-events: all !important; +} diff --git a/packages/app/src/Pages/Layout.tsx b/packages/app/src/Pages/Layout.tsx index 001d2822..c543f200 100644 --- a/packages/app/src/Pages/Layout.tsx +++ b/packages/app/src/Pages/Layout.tsx @@ -22,10 +22,12 @@ import { useTheme } from "Hooks/useTheme"; import { useLoginRelays } from "Hooks/useLoginRelays"; import { useNoteCreator } from "State/NoteCreator"; import { LoginUnlock } from "Element/PinPrompt"; +import { LoginStore } from "Login"; export default function Layout() { const location = useLocation(); const [pageClass, setPageClass] = useState("page"); + const { id, stalker } = useLogin(s => ({ id: s.id, stalker: s.stalker ?? false })); useLoginFeed(); useTheme(); @@ -60,6 +62,17 @@ export default function Layout() {
+ {stalker && ( +
{ + LoginStore.removeSession(id); + }}> + +
+ )} ); } diff --git a/packages/app/src/Pages/Notifications.tsx b/packages/app/src/Pages/Notifications.tsx index fbbc835e..d7984314 100644 --- a/packages/app/src/Pages/Notifications.tsx +++ b/packages/app/src/Pages/Notifications.tsx @@ -80,23 +80,27 @@ export default function NotificationsPage({ onClick }: { onClick?: (link: NostrL return onHour.toString(); }; + const myNotifications = useMemo(() => { + return orderDescending([...notifications]).filter( + a => !isMuted(a.pubkey) && a.tags.some(b => b[0] === "p" && b[1] === login.publicKey), + ); + }, [notifications, login.publicKey]); + const timeGrouped = useMemo(() => { - return orderDescending([...notifications]) - .filter(a => !isMuted(a.pubkey) && a.tags.some(b => b[0] === "p" && b[1] === login.publicKey)) - .reduce((acc, v) => { - const key = `${timeKey(v)}:${notificationContext(v as TaggedNostrEvent)?.encode()}:${v.kind}`; - if (acc.has(key)) { - unwrap(acc.get(key)).push(v as TaggedNostrEvent); - } else { - acc.set(key, [v as TaggedNostrEvent]); - } - return acc; - }, new Map>()); - }, [notifications]); + return myNotifications.reduce((acc, v) => { + const key = `${timeKey(v)}:${notificationContext(v as TaggedNostrEvent)?.encode()}:${v.kind}`; + if (acc.has(key)) { + unwrap(acc.get(key)).push(v as TaggedNostrEvent); + } else { + acc.set(key, [v as TaggedNostrEvent]); + } + return acc; + }, new Map>()); + }, [myNotifications]); return (
- + {login.publicKey && [...timeGrouped.entries()].map(([k, g]) => )} From 2c31a37b6adcdbee9bac1a4f984e0108806f6694 Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 10 Oct 2023 12:40:18 +0100 Subject: [PATCH 19/19] fix: gallery skip empty text elements --- packages/app/src/Element/Text.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/app/src/Element/Text.tsx b/packages/app/src/Element/Text.tsx index fb31ad9b..682c9fd7 100644 --- a/packages/app/src/Element/Text.tsx +++ b/packages/app/src/Element/Text.tsx @@ -184,6 +184,8 @@ export default function Text({ if (nextElement && nextElement.type === "media" && nextElement.mimeType?.startsWith("image")) { galleryImages.push(nextElement); i++; + } else if (nextElement && nextElement.type === "text" && nextElement.content.trim().length === 0) { + i++; //skip over empty space text } else { break; }