From 1344d3107ce95069b6dbe71053fe71a575795414 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Mon, 27 Oct 2025 12:30:53 +0100 Subject: [PATCH] fix navigation desktop --- src/PageManager.tsx | 77 ++++++++++++------- .../Profile/ProfileBookmarksAndHashtags.tsx | 60 ++++++++------- src/components/SearchBar/index.tsx | 7 +- src/pages/primary/SearchPage/index.tsx | 1 + .../secondary/GeneralSettingsPage/index.tsx | 2 - src/pages/secondary/HomePage/index.tsx | 3 +- src/pages/secondary/NoteListPage/index.tsx | 51 +++++++++++- src/pages/secondary/SearchPage/index.tsx | 4 +- 8 files changed, 139 insertions(+), 66 deletions(-) diff --git a/src/PageManager.tsx b/src/PageManager.tsx index 7af1aa4..417a775 100644 --- a/src/PageManager.tsx +++ b/src/PageManager.tsx @@ -4,6 +4,7 @@ import { cn } from '@/lib/utils' import logger from '@/lib/logger' import { ChevronLeft } from 'lucide-react' import NoteListPage from '@/pages/primary/NoteListPage' +import SecondaryNoteListPage from '@/pages/secondary/NoteListPage' // Page imports needed for primary note view import SettingsPage from '@/pages/secondary/SettingsPage' import RelaySettingsPage from '@/pages/secondary/RelaySettingsPage' @@ -131,7 +132,7 @@ export function useSmartNoteNavigation() { // Use primary note view to show notes since secondary panel is disabled // Extract note ID from URL (e.g., "/notes/note1..." -> "note1...") const noteId = url.replace('/notes/', '') - window.history.replaceState(null, '', url) + window.history.pushState(null, '', url) setPrimaryNoteView(, 'note') } @@ -145,8 +146,8 @@ export function useSmartRelayNavigation() { const navigateToRelay = (url: string) => { // Use primary note view to show relay pages since secondary panel is disabled // Extract relay URL from the URL (e.g., "/relays/wss://..." -> "wss://...") - const relayUrl = url.replace('/relays/', '') - window.history.replaceState(null, '', url) + const relayUrl = decodeURIComponent(url.replace('/relays/', '')) + window.history.pushState(null, '', url) setPrimaryNoteView(, 'relay') } @@ -160,7 +161,7 @@ export function useSmartProfileNavigation() { const navigateToProfile = (url: string) => { // Use primary note view to show profiles since secondary panel is disabled const profileId = url.replace('/users/', '') - window.history.replaceState(null, '', url) + window.history.pushState(null, '', url) setPrimaryNoteView(, 'profile') } @@ -173,8 +174,8 @@ export function useSmartHashtagNavigation() { const navigateToHashtag = (url: string) => { // Use primary note view to show hashtag feed since secondary panel is disabled - window.history.replaceState(null, '', url) - setPrimaryNoteView(, 'hashtag') + window.history.pushState(null, '', url) + setPrimaryNoteView(, 'hashtag') } return { navigateToHashtag } @@ -187,7 +188,7 @@ export function useSmartFollowingListNavigation() { const navigateToFollowingList = (url: string) => { // Use primary note view to show following list since secondary panel is disabled const profileId = url.replace('/users/', '').replace('/following', '') - window.history.replaceState(null, '', url) + window.history.pushState(null, '', url) setPrimaryNoteView(, 'profile') } @@ -200,7 +201,7 @@ export function useSmartMuteListNavigation() { const navigateToMuteList = (url: string) => { // Use primary note view to show mute list since secondary panel is disabled - window.history.replaceState(null, '', url) + window.history.pushState(null, '', url) setPrimaryNoteView(, 'settings') } @@ -214,7 +215,7 @@ export function useSmartOthersRelaySettingsNavigation() { const navigateToOthersRelaySettings = (url: string) => { // Use primary note view to show others relay settings since secondary panel is disabled const profileId = url.replace('/users/', '').replace('/relays', '') - window.history.replaceState(null, '', url) + window.history.pushState(null, '', url) setPrimaryNoteView(, 'profile') } @@ -228,22 +229,22 @@ export function useSmartSettingsNavigation() { const navigateToSettings = (url: string) => { // Use primary note view to show settings since secondary panel is disabled if (url === '/settings') { - window.history.replaceState(null, '', url) + window.history.pushState(null, '', url) setPrimaryNoteView(, 'settings') } else if (url === '/settings/relays') { - window.history.replaceState(null, '', url) + window.history.pushState(null, '', url) setPrimaryNoteView(, 'settings-sub') } else if (url === '/settings/wallet') { - window.history.replaceState(null, '', url) + window.history.pushState(null, '', url) setPrimaryNoteView(, 'settings-sub') } else if (url === '/settings/posts') { - window.history.replaceState(null, '', url) + window.history.pushState(null, '', url) setPrimaryNoteView(, 'settings-sub') } else if (url === '/settings/general') { - window.history.replaceState(null, '', url) + window.history.pushState(null, '', url) setPrimaryNoteView(, 'settings-sub') } else if (url === '/settings/translation') { - window.history.replaceState(null, '', url) + window.history.pushState(null, '', url) setPrimaryNoteView(, 'settings-sub') } } @@ -285,13 +286,13 @@ function MainContentArea({ currentPrimaryPage, primaryNoteView, primaryViewType, - setPrimaryNoteView + goBack }: { primaryPages: { name: TPrimaryPageName; element: ReactNode; props?: any }[] currentPrimaryPage: TPrimaryPageName primaryNoteView: ReactNode | null primaryViewType: 'note' | 'settings' | 'settings-sub' | 'profile' | 'hashtag' | 'relay' | null - setPrimaryNoteView: (view: ReactNode | null, type?: 'note' | 'settings' | 'settings-sub' | 'profile' | 'hashtag' | 'relay') => void + goBack: () => void }) { logger.debug('MainContentArea rendering:', { currentPrimaryPage, @@ -313,16 +314,7 @@ function MainContentArea({ variant="ghost" size="titlebar-icon" title="Back" - onClick={() => { - if (primaryViewType === 'settings-sub') { - // For settings sub-pages, navigate back to main settings page - window.history.replaceState(null, '', '/settings') - setPrimaryNoteView(, 'settings') - } else { - // For other pages, go back to feed - setPrimaryNoteView(null) - } - }} + onClick={goBack} >
@@ -405,8 +397,37 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { window.history.replaceState(null, '', newUrl) } } + + const goBack = () => { + // Special handling for settings sub-pages - go back to main settings page + if (primaryViewType === 'settings-sub') { + window.history.pushState(null, '', '/settings') + setPrimaryNoteView(, 'settings') + } else { + // Use browser's back functionality for other pages + window.history.back() + } + } const ignorePopStateRef = useRef(false) + // Handle browser back button + useEffect(() => { + const handlePopState = () => { + if (ignorePopStateRef.current) { + ignorePopStateRef.current = false + return + } + + // If we have a primary note view open, close it and go back to the main page + if (primaryNoteView) { + setPrimaryNoteView(null) + } + } + + window.addEventListener('popstate', handlePopState) + return () => window.removeEventListener('popstate', handlePopState) + }, [primaryNoteView]) + useEffect(() => { if (['/npub1', '/nprofile1'].some((prefix) => window.location.pathname.startsWith(prefix))) { window.history.replaceState( @@ -709,7 +730,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { currentPrimaryPage={currentPrimaryPage} primaryNoteView={primaryNoteView} primaryViewType={primaryViewType} - setPrimaryNoteView={setPrimaryNoteView} + goBack={goBack} />
diff --git a/src/components/Profile/ProfileBookmarksAndHashtags.tsx b/src/components/Profile/ProfileBookmarksAndHashtags.tsx index c46a1fc..c3eebe8 100644 --- a/src/components/Profile/ProfileBookmarksAndHashtags.tsx +++ b/src/components/Profile/ProfileBookmarksAndHashtags.tsx @@ -317,15 +317,17 @@ export default function ProfileBookmarksAndHashtags({ } return ( -
- {pinEvents.map((event) => ( - - ))} +
+
+ {pinEvents.map((event) => ( + + ))} +
) } @@ -350,15 +352,17 @@ export default function ProfileBookmarksAndHashtags({ } return ( -
- {bookmarkEvents.map((event) => ( - - ))} +
+
+ {bookmarkEvents.map((event) => ( + + ))} +
) } @@ -383,15 +387,17 @@ export default function ProfileBookmarksAndHashtags({ } return ( -
- {hashtagEvents.map((event) => ( - - ))} +
+
+ {hashtagEvents.map((event) => ( + + ))} +
) } diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx index 7b15ed3..e564a02 100644 --- a/src/components/SearchBar/index.tsx +++ b/src/components/SearchBar/index.tsx @@ -1,10 +1,10 @@ import SearchInput from '@/components/SearchInput' import { useSearchProfiles } from '@/hooks' -import { toNote } from '@/lib/link' +import { toNote, toNoteList } from '@/lib/link' import { randomString } from '@/lib/random' import { normalizeUrl } from '@/lib/url' import { cn } from '@/lib/utils' -import { useSmartNoteNavigation } from '@/PageManager' +import { useSmartNoteNavigation, useSmartHashtagNavigation } from '@/PageManager' import { useScreenSize } from '@/providers/ScreenSizeProvider' import modalManager from '@/services/modal-manager.service' import { TSearchParams } from '@/types' @@ -33,6 +33,7 @@ const SearchBar = forwardRef< >(({ input, setInput, onSearch }, ref) => { const { t } = useTranslation() const { navigateToNote } = useSmartNoteNavigation() + const { navigateToHashtag } = useSmartHashtagNavigation() const { isSmallScreen } = useScreenSize() const [debouncedInput, setDebouncedInput] = useState(input) const { profiles, isFetching: isFetchingProfiles } = useSearchProfiles(debouncedInput, 5) @@ -89,6 +90,8 @@ const SearchBar = forwardRef< if (params.type === 'note') { navigateToNote(toNote(params.search)) + } else if (params.type === 'hashtag') { + navigateToHashtag(toNoteList({ hashtag: params.search })) } else { onSearch(params) } diff --git a/src/pages/primary/SearchPage/index.tsx b/src/pages/primary/SearchPage/index.tsx index 521ac56..1c83e74 100644 --- a/src/pages/primary/SearchPage/index.tsx +++ b/src/pages/primary/SearchPage/index.tsx @@ -39,6 +39,7 @@ const SearchPage = forwardRef((_, ref) => {
diff --git a/src/pages/secondary/GeneralSettingsPage/index.tsx b/src/pages/secondary/GeneralSettingsPage/index.tsx index 6449816..b6e88ce 100644 --- a/src/pages/secondary/GeneralSettingsPage/index.tsx +++ b/src/pages/secondary/GeneralSettingsPage/index.tsx @@ -6,7 +6,6 @@ import { LocalizedLanguageNames, TLanguage } from '@/i18n' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' import { cn, isSupportCheckConnectionType } from '@/lib/utils' import { useContentPolicy } from '@/providers/ContentPolicyProvider' -import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useTheme } from '@/providers/ThemeProvider' import { useUserPreferences } from '@/providers/UserPreferencesProvider' import { useUserTrust } from '@/providers/UserTrustProvider' @@ -20,7 +19,6 @@ const GeneralSettingsPage = forwardRef(({ index, hideTitlebar = false }: { index const { t, i18n } = useTranslation() const [language, setLanguage] = useState(i18n.language as TLanguage) const { themeSetting, setThemeSetting } = useTheme() - const { isSmallScreen } = useScreenSize() const { autoplay, setAutoplay, diff --git a/src/pages/secondary/HomePage/index.tsx b/src/pages/secondary/HomePage/index.tsx index 8dfdee3..bc4fb70 100644 --- a/src/pages/secondary/HomePage/index.tsx +++ b/src/pages/secondary/HomePage/index.tsx @@ -4,10 +4,9 @@ import { Button } from '@/components/ui/button' import { RECOMMENDED_RELAYS } from '@/constants' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' import { toRelay } from '@/lib/link' -import { useUserPreferences } from '@/providers/UserPreferencesProvider' import relayInfoService from '@/services/relay-info.service' import { TRelayInfo } from '@/types' -import { ArrowRight, Server, X } from 'lucide-react' +import { ArrowRight, Server } from 'lucide-react' import { forwardRef, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' diff --git a/src/pages/secondary/NoteListPage/index.tsx b/src/pages/secondary/NoteListPage/index.tsx index 5b095bf..181c7ce 100644 --- a/src/pages/secondary/NoteListPage/index.tsx +++ b/src/pages/secondary/NoteListPage/index.tsx @@ -15,7 +15,12 @@ import { UserRound, Plus } from 'lucide-react' import React, { forwardRef, useEffect, useState, useMemo } from 'react' import { useTranslation } from 'react-i18next' -const NoteListPage = forwardRef(({ index, hideTitlebar = false }: { index?: number; hideTitlebar?: boolean }, ref) => { +interface NoteListPageProps { + index?: number + hideTitlebar?: boolean +} + +const NoteListPage = forwardRef(({ index, hideTitlebar = false }, ref) => { const { t } = useTranslation() const { push } = useSecondaryPage() const { relayList, pubkey } = useNostr() @@ -149,6 +154,40 @@ const NoteListPage = forwardRef(({ index, hideTitlebar = false }: { index?: numb init() }, []) + // Listen for URL changes to re-initialize the page + useEffect(() => { + const handlePopState = () => { + const searchParams = new URLSearchParams(window.location.search) + const hashtag = searchParams.get('t') + if (hashtag) { + setData({ type: 'hashtag' }) + setTitle(`# ${hashtag}`) + setSubRequests([ + { + filter: { '#t': [hashtag] }, + urls: BIG_RELAY_URLS + } + ]) + // Set controls for hashtag subscribe button + if (pubkey) { + setControls( + + ) + } + } + } + + window.addEventListener('popstate', handlePopState) + return () => window.removeEventListener('popstate', handlePopState) + }, [pubkey, isHashtagSubscribed, t]) + // Update controls when subscription status changes useEffect(() => { if (data?.type === 'hashtag' && pubkey) { @@ -183,9 +222,17 @@ const NoteListPage = forwardRef(({ index, hideTitlebar = false }: { index?: numb ref={ref} index={index} title={hideTitlebar ? undefined : title} - controls={controls} + controls={hideTitlebar ? undefined : controls} displayScrollToTopButton > + {hideTitlebar && data?.type === 'hashtag' && ( +
+
+
{title}
+ {controls} +
+
+ )} {content} ) diff --git a/src/pages/secondary/SearchPage/index.tsx b/src/pages/secondary/SearchPage/index.tsx index b5951dc..db3751f 100644 --- a/src/pages/secondary/SearchPage/index.tsx +++ b/src/pages/secondary/SearchPage/index.tsx @@ -1,15 +1,13 @@ import SearchBar, { TSearchBarRef } from '@/components/SearchBar' import SearchResult from '@/components/SearchResult' -import { Button } from '@/components/ui/button' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' import { toSearch } from '@/lib/link' import { useSecondaryPage } from '@/PageManager' import { TSearchParams } from '@/types' -import { ChevronLeft } from 'lucide-react' import { forwardRef, useEffect, useMemo, useRef, useState } from 'react' const SearchPage = forwardRef(({ index, hideTitlebar = false }: { index?: number; hideTitlebar?: boolean }, ref) => { - const { push, pop } = useSecondaryPage() + const { push } = useSecondaryPage() const [input, setInput] = useState('') const searchBarRef = useRef(null) const searchParams = useMemo(() => {