diff --git a/README.md b/README.md index 8a27743bc..01114470c 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,6 @@ - Add preview tab to note modal - Save note drafts and let users manage them - Add support for relay favicons -- Setup react-helmet to update window title ## Setup diff --git a/src/components/note/index.tsx b/src/components/note/index.tsx index 770c2af6c..86030ff78 100644 --- a/src/components/note/index.tsx +++ b/src/components/note/index.tsx @@ -20,6 +20,7 @@ import { PostModalContext } from "../../providers/post-modal-provider"; import { buildReply } from "../../helpers/nostr-event"; import { UserDnsIdentityIcon } from "../user-dns-identity"; import { useReadonlyMode } from "../../hooks/use-readonly-mode"; +import { convertTimestampToDate } from "../../helpers/date"; export type NoteProps = { event: NostrEvent; @@ -48,7 +49,7 @@ export const Note = React.memo(({ event, maxHeight }: NoteProps) => { {!isMobile && } - {moment(event.created_at * 1000).fromNow()} + {moment(convertTimestampToDate(event.created_at)).fromNow()} diff --git a/src/components/note/note-relays.tsx b/src/components/note/note-relays.tsx index 0410105bf..c5c15ec05 100644 --- a/src/components/note/note-relays.tsx +++ b/src/components/note/note-relays.tsx @@ -20,6 +20,7 @@ import { relayPool } from "../../services/relays"; import settings from "../../services/settings"; import { NostrEvent } from "../../types/nostr-event"; import { RelayIcon, SearchIcon } from "../icons"; +import { RelayFavicon } from "../relay-favicon"; export type NoteRelaysProps = Omit & { event: NostrEvent; @@ -71,7 +72,10 @@ export const NoteRelays = memo(({ event, ...props }: NoteRelaysProps) => { {relays.map((url) => ( - {url} + + + {url} + ))} diff --git a/src/components/relay-favicon.tsx b/src/components/relay-favicon.tsx new file mode 100644 index 000000000..73a83e8fd --- /dev/null +++ b/src/components/relay-favicon.tsx @@ -0,0 +1,18 @@ +import React, { useMemo } from "react"; +import { Avatar, AvatarProps } from "@chakra-ui/react"; +import { RelayIcon } from "./icons"; + +export type RelayFaviconProps = Omit & { + relay: string; +}; +export const RelayFavicon = React.memo(({ relay, ...props }: RelayFaviconProps) => { + const url = useMemo(() => { + const url = new URL(relay); + url.protocol = "https:"; + url.pathname = "/favicon.ico"; + return url.toString(); + }, [relay]); + + return } overflow="hidden" {...props} />; +}); +RelayFavicon.displayName = "RelayFavicon"; diff --git a/src/helpers/thread.ts b/src/helpers/thread.ts index 311dfa4bc..435f04ce7 100644 --- a/src/helpers/thread.ts +++ b/src/helpers/thread.ts @@ -38,6 +38,8 @@ export function linkEvents(events: NostrEvent[]) { reply.reply = reply.refs.replyId ? replies.get(reply.refs.replyId) : undefined; reply.replies = idToChildren[id]?.map((e) => replies.get(e.id) as ThreadItem) ?? []; + + reply.replies.sort((a, b) => a.event.created_at - b.event.created_at); } return replies; diff --git a/src/hooks/use-app-title.ts b/src/hooks/use-app-title.ts new file mode 100644 index 000000000..f911fb800 --- /dev/null +++ b/src/hooks/use-app-title.ts @@ -0,0 +1,13 @@ +import { useEffect } from "react"; + +const appName = "noStrudel"; + +export function useAppTitle(title?: string) { + useEffect(() => { + document.title = [title, appName].filter(Boolean).join(" | "); + + return () => { + document.title = appName; + }; + }, [title]); +} diff --git a/src/views/home/discover-tab.tsx b/src/views/home/discover-tab.tsx index 9bc34983b..eefa80997 100644 --- a/src/views/home/discover-tab.tsx +++ b/src/views/home/discover-tab.tsx @@ -10,8 +10,10 @@ import userContactsService from "../../services/user-contacts"; import { useTimelineLoader } from "../../hooks/use-timeline-loader"; import { isNote } from "../../helpers/nostr-event"; import settings from "../../services/settings"; +import { useAppTitle } from "../../hooks/use-app-title"; function useExtendedContacts(pubkey: string) { + useAppTitle("discover"); const [extendedContacts, setExtendedContacts] = useState([]); const contacts = useUserContacts(pubkey); diff --git a/src/views/home/global-tab.tsx b/src/views/home/global-tab.tsx index 5d7fa9e83..8f9d27e4a 100644 --- a/src/views/home/global-tab.tsx +++ b/src/views/home/global-tab.tsx @@ -4,11 +4,13 @@ import { useSearchParams } from "react-router-dom"; import { Note } from "../../components/note"; import { unique } from "../../helpers/array"; import { isNote } from "../../helpers/nostr-event"; +import { useAppTitle } from "../../hooks/use-app-title"; import useSubject from "../../hooks/use-subject"; import { useTimelineLoader } from "../../hooks/use-timeline-loader"; import settings from "../../services/settings"; export const GlobalTab = () => { + useAppTitle("global"); const defaultRelays = useSubject(settings.relays); const [searchParams, setSearchParams] = useSearchParams(); const selectedRelay = searchParams.get("relay") ?? ""; diff --git a/src/views/settings/index.tsx b/src/views/settings/index.tsx index 34f205498..5e4ec0c64 100644 --- a/src/views/settings/index.tsx +++ b/src/views/settings/index.tsx @@ -3,7 +3,6 @@ import { Flex, FormControl, FormLabel, - Input, Switch, useColorMode, Table, @@ -22,9 +21,10 @@ import { AccordionIcon, ButtonGroup, FormHelperText, + Text, } from "@chakra-ui/react"; import { SyntheticEvent, useState } from "react"; -import { GlobalIcon, TrashIcon } from "../../components/icons"; +import { GlobalIcon, RelayIcon, TrashIcon } from "../../components/icons"; import { RelayStatus } from "./relay-status"; import useSubject from "../../hooks/use-subject"; import settings from "../../services/settings"; @@ -32,6 +32,7 @@ import { clearCacheData, deleteDatabase } from "../../services/db"; import { RelayUrlInput } from "../../components/relay-url-input"; import { useNavigate } from "react-router-dom"; import identity from "../../services/identity"; +import { RelayFavicon } from "../../components/relay-favicon"; export const SettingsView = () => { const navigate = useNavigate(); @@ -94,7 +95,12 @@ export const SettingsView = () => { {relays.map((url) => ( - {url} + + + + {url} + + diff --git a/src/views/user/index.tsx b/src/views/user/index.tsx index 8f50c370f..aac4bb68e 100644 --- a/src/views/user/index.tsx +++ b/src/views/user/index.tsx @@ -10,8 +10,6 @@ import { Text, Link, IconButton, - ButtonGroup, - Button, } from "@chakra-ui/react"; import { Outlet, useLoaderData, useMatches, useNavigate } from "react-router-dom"; import { useUserMetadata } from "../../hooks/use-user-metadata"; @@ -28,6 +26,7 @@ import { KeyIcon, SettingsIcon } from "../../components/icons"; import { CopyIconButton } from "../../components/copy-icon-button"; import identity from "../../services/identity"; import { UserFollowButton } from "../../components/user-follow-button"; +import { useAppTitle } from "../../hooks/use-app-title"; const tabs = [ { label: "Notes", path: "notes" }, @@ -51,6 +50,8 @@ const UserView = () => { const npub = normalizeToBech32(pubkey, Bech32Prefix.Pubkey); const isSelf = pubkey === identity.pubkey.value; + useAppTitle(getUserDisplayName(metadata, npub ?? pubkey)); + const header = (