feat: scroll to top when jumping to the current page
This commit is contained in:
@@ -4,7 +4,17 @@ import { Separator } from '@/components/ui/separator'
|
|||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import NoteListPage from '@/pages/primary/NoteListPage'
|
import NoteListPage from '@/pages/primary/NoteListPage'
|
||||||
import HomePage from '@/pages/secondary/HomePage'
|
import HomePage from '@/pages/secondary/HomePage'
|
||||||
import { cloneElement, createContext, ReactNode, useContext, useEffect, useState } from 'react'
|
import { TPageRef } from '@/types'
|
||||||
|
import {
|
||||||
|
cloneElement,
|
||||||
|
createContext,
|
||||||
|
createRef,
|
||||||
|
ReactNode,
|
||||||
|
RefObject,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState
|
||||||
|
} from 'react'
|
||||||
import MePage from './pages/primary/MePage'
|
import MePage from './pages/primary/MePage'
|
||||||
import NotificationListPage from './pages/primary/NotificationListPage'
|
import NotificationListPage from './pages/primary/NotificationListPage'
|
||||||
import { useScreenSize } from './providers/ScreenSizeProvider'
|
import { useScreenSize } from './providers/ScreenSizeProvider'
|
||||||
@@ -26,13 +36,20 @@ type TSecondaryPageContext = {
|
|||||||
type TStackItem = {
|
type TStackItem = {
|
||||||
index: number
|
index: number
|
||||||
url: string
|
url: string
|
||||||
component: React.ReactNode | null
|
component: React.ReactElement | null
|
||||||
|
ref: RefObject<TPageRef> | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const PRIMARY_PAGE_REF_MAP = {
|
||||||
|
home: createRef<TPageRef>(),
|
||||||
|
notifications: createRef<TPageRef>(),
|
||||||
|
me: createRef<TPageRef>()
|
||||||
}
|
}
|
||||||
|
|
||||||
const PRIMARY_PAGE_MAP = {
|
const PRIMARY_PAGE_MAP = {
|
||||||
home: <NoteListPage />,
|
home: <NoteListPage ref={PRIMARY_PAGE_REF_MAP.home} />,
|
||||||
notifications: <NotificationListPage />,
|
notifications: <NotificationListPage ref={PRIMARY_PAGE_REF_MAP.notifications} />,
|
||||||
me: <MePage />
|
me: <MePage ref={PRIMARY_PAGE_REF_MAP.me} />
|
||||||
}
|
}
|
||||||
|
|
||||||
const PrimaryPageContext = createContext<TPrimaryPageContext | undefined>(undefined)
|
const PrimaryPageContext = createContext<TPrimaryPageContext | undefined>(undefined)
|
||||||
@@ -111,13 +128,22 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
|||||||
const topItem = newStack[newStack.length - 1] as TStackItem | undefined
|
const topItem = newStack[newStack.length - 1] as TStackItem | undefined
|
||||||
if (!topItem) {
|
if (!topItem) {
|
||||||
// Create a new stack item if it's not exist (e.g. when the user refreshes the page, the stack will be empty)
|
// Create a new stack item if it's not exist (e.g. when the user refreshes the page, the stack will be empty)
|
||||||
const newComponent = findAndCreateComponent(state.url, state.index)
|
const { component, ref } = findAndCreateComponent(state.url, state.index)
|
||||||
if (newComponent) {
|
if (component) {
|
||||||
newStack.push({ index: state.index, url: state.url, component: newComponent })
|
newStack.push({
|
||||||
|
index: state.index,
|
||||||
|
url: state.url,
|
||||||
|
component,
|
||||||
|
ref
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else if (!topItem.component) {
|
} else if (!topItem.component) {
|
||||||
// Load the component if it's not cached
|
// Load the component if it's not cached
|
||||||
topItem.component = findAndCreateComponent(topItem.url, state.index)
|
const { component, ref } = findAndCreateComponent(topItem.url, state.index)
|
||||||
|
if (component) {
|
||||||
|
topItem.component = component
|
||||||
|
topItem.ref = ref
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (newStack.length === 0) {
|
if (newStack.length === 0) {
|
||||||
window.history.replaceState(null, '', '/')
|
window.history.replaceState(null, '', '/')
|
||||||
@@ -139,6 +165,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
|||||||
setPrimaryPages((prev) => [...prev, { name: page, element: PRIMARY_PAGE_MAP[page] }])
|
setPrimaryPages((prev) => [...prev, { name: page, element: PRIMARY_PAGE_MAP[page] }])
|
||||||
}
|
}
|
||||||
setCurrentPrimaryPage(page)
|
setCurrentPrimaryPage(page)
|
||||||
|
PRIMARY_PAGE_REF_MAP[page].current?.scrollToTop()
|
||||||
if (isSmallScreen) {
|
if (isSmallScreen) {
|
||||||
clearSecondaryPages()
|
clearSecondaryPages()
|
||||||
}
|
}
|
||||||
@@ -146,7 +173,13 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
|||||||
|
|
||||||
const pushSecondaryPage = (url: string, index?: number) => {
|
const pushSecondaryPage = (url: string, index?: number) => {
|
||||||
setSecondaryStack((prevStack) => {
|
setSecondaryStack((prevStack) => {
|
||||||
if (isCurrentPage(prevStack, url)) return prevStack
|
if (isCurrentPage(prevStack, url)) {
|
||||||
|
const currentItem = prevStack[prevStack.length - 1]
|
||||||
|
if (currentItem?.ref?.current) {
|
||||||
|
currentItem.ref.current.scrollToTop()
|
||||||
|
}
|
||||||
|
return prevStack
|
||||||
|
}
|
||||||
|
|
||||||
const { newStack, newItem } = pushNewPageToStack(prevStack, url, maxStackSize, index)
|
const { newStack, newItem } = pushNewPageToStack(prevStack, url, maxStackSize, index)
|
||||||
if (newItem) {
|
if (newItem) {
|
||||||
@@ -302,10 +335,11 @@ function findAndCreateComponent(url: string, index: number) {
|
|||||||
const match = matcher(path)
|
const match = matcher(path)
|
||||||
if (!match) continue
|
if (!match) continue
|
||||||
|
|
||||||
if (!element) return null
|
if (!element) return {}
|
||||||
return cloneElement(element, { ...match.params, index } as any)
|
const ref = createRef<TPageRef>()
|
||||||
|
return { component: cloneElement(element, { ...match.params, index, ref } as any), ref }
|
||||||
}
|
}
|
||||||
return null
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushNewPageToStack(
|
function pushNewPageToStack(
|
||||||
@@ -317,10 +351,10 @@ function pushNewPageToStack(
|
|||||||
const currentItem = stack[stack.length - 1]
|
const currentItem = stack[stack.length - 1]
|
||||||
const currentIndex = specificIndex ?? (currentItem ? currentItem.index + 1 : 0)
|
const currentIndex = specificIndex ?? (currentItem ? currentItem.index + 1 : 0)
|
||||||
|
|
||||||
const component = findAndCreateComponent(url, currentIndex)
|
const { component, ref } = findAndCreateComponent(url, currentIndex)
|
||||||
if (!component) return { newStack: stack, newItem: null }
|
if (!component) return { newStack: stack, newItem: null }
|
||||||
|
|
||||||
const newItem = { component, url, index: currentIndex }
|
const newItem = { component, ref, url, index: currentIndex }
|
||||||
const newStack = [...stack, newItem]
|
const newStack = [...stack, newItem]
|
||||||
const lastCachedIndex = newStack.findIndex((stack) => stack.component)
|
const lastCachedIndex = newStack.findIndex((stack) => stack.component)
|
||||||
// Clear the oldest cached component if there are too many cached components
|
// Clear the oldest cached component if there are too many cached components
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ export default function BottomNavigationBarItem({
|
|||||||
<Button
|
<Button
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex shadow-none items-center bg-transparent w-full h-12 xl:w-full xl:h-auto p-3 m-0 xl:py-2 xl:px-4 rounded-lg xl:justify-start text-lg font-semibold [&_svg]:size-full xl:[&_svg]:size-4',
|
'flex shadow-none items-center bg-transparent w-full h-12 xl:w-full xl:h-auto p-3 m-0 xl:py-2 xl:px-4 rounded-lg xl:justify-start text-lg font-semibold [&_svg]:size-full xl:[&_svg]:size-4',
|
||||||
active && 'text-primary disabled:opacity-100'
|
active && 'text-primary hover:text-primary'
|
||||||
)}
|
)}
|
||||||
disabled={active}
|
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ const SidebarItem = forwardRef<
|
|||||||
<Button
|
<Button
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex shadow-none items-center bg-transparent w-12 h-12 xl:w-full xl:h-auto p-3 m-0 xl:py-2 xl:px-4 rounded-lg xl:justify-start gap-4 text-lg font-semibold [&_svg]:size-full xl:[&_svg]:size-4',
|
'flex shadow-none items-center bg-transparent w-12 h-12 xl:w-full xl:h-auto p-3 m-0 xl:py-2 xl:px-4 rounded-lg xl:justify-start gap-4 text-lg font-semibold [&_svg]:size-full xl:[&_svg]:size-4',
|
||||||
active && 'text-primary disabled:opacity-100',
|
active && 'text-primary hover:text-primary',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
disabled={active}
|
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
title={t(title)}
|
title={t(title)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@@ -30,11 +30,10 @@ const PrimaryPageLayout = forwardRef(
|
|||||||
ref,
|
ref,
|
||||||
() => ({
|
() => ({
|
||||||
scrollToTop: () => {
|
scrollToTop: () => {
|
||||||
if (isSmallScreen) {
|
if (scrollAreaRef.current) {
|
||||||
window.scrollTo({ top: 0 })
|
return scrollAreaRef.current.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
return
|
|
||||||
}
|
}
|
||||||
scrollAreaRef.current?.scrollTo({ top: 0 })
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[]
|
[]
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import { ScrollArea } from '@/components/ui/scroll-area'
|
|||||||
import { useSecondaryPage } from '@/PageManager'
|
import { useSecondaryPage } from '@/PageManager'
|
||||||
import { DeepBrowsingProvider } from '@/providers/DeepBrowsingProvider'
|
import { DeepBrowsingProvider } from '@/providers/DeepBrowsingProvider'
|
||||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||||
import { useEffect, useRef } from 'react'
|
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
|
||||||
|
|
||||||
export default function SecondaryPageLayout({
|
const SecondaryPageLayout = forwardRef(
|
||||||
|
(
|
||||||
|
{
|
||||||
children,
|
children,
|
||||||
index,
|
index,
|
||||||
title,
|
title,
|
||||||
@@ -22,11 +24,26 @@ export default function SecondaryPageLayout({
|
|||||||
controls?: React.ReactNode
|
controls?: React.ReactNode
|
||||||
hideBackButton?: boolean
|
hideBackButton?: boolean
|
||||||
displayScrollToTopButton?: boolean
|
displayScrollToTopButton?: boolean
|
||||||
}): JSX.Element {
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
||||||
const { isSmallScreen } = useScreenSize()
|
const { isSmallScreen } = useScreenSize()
|
||||||
const { currentIndex } = useSecondaryPage()
|
const { currentIndex } = useSecondaryPage()
|
||||||
|
|
||||||
|
useImperativeHandle(
|
||||||
|
ref,
|
||||||
|
() => ({
|
||||||
|
scrollToTop: () => {
|
||||||
|
if (scrollAreaRef.current) {
|
||||||
|
return scrollAreaRef.current.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
|
}
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isSmallScreen) {
|
if (isSmallScreen) {
|
||||||
window.scrollTo({ top: 0 })
|
window.scrollTo({ top: 0 })
|
||||||
@@ -62,13 +79,20 @@ export default function SecondaryPageLayout({
|
|||||||
scrollBarClassName="sm:z-50"
|
scrollBarClassName="sm:z-50"
|
||||||
ref={scrollAreaRef}
|
ref={scrollAreaRef}
|
||||||
>
|
>
|
||||||
<SecondaryPageTitlebar title={title} controls={controls} hideBackButton={hideBackButton} />
|
<SecondaryPageTitlebar
|
||||||
|
title={title}
|
||||||
|
controls={controls}
|
||||||
|
hideBackButton={hideBackButton}
|
||||||
|
/>
|
||||||
{children}
|
{children}
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
{displayScrollToTopButton && <ScrollToTopButton scrollAreaRef={scrollAreaRef} />}
|
{displayScrollToTopButton && <ScrollToTopButton scrollAreaRef={scrollAreaRef} />}
|
||||||
</DeepBrowsingProvider>
|
</DeepBrowsingProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
SecondaryPageLayout.displayName = 'SecondaryPageLayout'
|
||||||
|
export default SecondaryPageLayout
|
||||||
|
|
||||||
export function SecondaryPageTitlebar({
|
export function SecondaryPageTitlebar({
|
||||||
title,
|
title,
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import { cn } from '@/lib/utils'
|
|||||||
import { useSecondaryPage } from '@/PageManager'
|
import { useSecondaryPage } from '@/PageManager'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { ArrowDownUp, ChevronRight, LogOut, Settings, UserRound } from 'lucide-react'
|
import { ArrowDownUp, ChevronRight, LogOut, Settings, UserRound } from 'lucide-react'
|
||||||
import { HTMLProps, useState } from 'react'
|
import { forwardRef, HTMLProps, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function MePage() {
|
const MePage = forwardRef((_, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { push } = useSecondaryPage()
|
const { push } = useSecondaryPage()
|
||||||
const { pubkey } = useNostr()
|
const { pubkey } = useNostr()
|
||||||
@@ -25,7 +25,7 @@ export default function MePage() {
|
|||||||
|
|
||||||
if (!pubkey) {
|
if (!pubkey) {
|
||||||
return (
|
return (
|
||||||
<PrimaryPageLayout pageName="home" titlebar={<MePageTitlebar />}>
|
<PrimaryPageLayout ref={ref} pageName="home" titlebar={<MePageTitlebar />}>
|
||||||
<div className="flex flex-col p-4 gap-4 overflow-auto">
|
<div className="flex flex-col p-4 gap-4 overflow-auto">
|
||||||
<AccountManager />
|
<AccountManager />
|
||||||
</div>
|
</div>
|
||||||
@@ -34,7 +34,7 @@ export default function MePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PrimaryPageLayout pageName="home" titlebar={<MePageTitlebar />}>
|
<PrimaryPageLayout ref={ref} pageName="home" titlebar={<MePageTitlebar />}>
|
||||||
<div className="flex gap-4 items-center p-4">
|
<div className="flex gap-4 items-center p-4">
|
||||||
<SimpleUserAvatar userId={pubkey} size="big" />
|
<SimpleUserAvatar userId={pubkey} size="big" />
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -71,7 +71,9 @@ export default function MePage() {
|
|||||||
<LogoutDialog open={logoutDialogOpen} setOpen={setLogoutDialogOpen} />
|
<LogoutDialog open={logoutDialogOpen} setOpen={setLogoutDialogOpen} />
|
||||||
</PrimaryPageLayout>
|
</PrimaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
MePage.displayName = 'MePage'
|
||||||
|
export default MePage
|
||||||
|
|
||||||
function MePageTitlebar() {
|
function MePageTitlebar() {
|
||||||
const { push } = useSecondaryPage()
|
const { push } = useSecondaryPage()
|
||||||
|
|||||||
@@ -4,16 +4,18 @@ import { Button } from '@/components/ui/button'
|
|||||||
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
||||||
import { useFeed } from '@/providers/FeedProvider'
|
import { useFeed } from '@/providers/FeedProvider'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { useEffect, useRef } from 'react'
|
import { TPageRef } from '@/types'
|
||||||
|
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import FeedButton from './FeedButton'
|
import FeedButton from './FeedButton'
|
||||||
import SearchButton from './SearchButton'
|
import SearchButton from './SearchButton'
|
||||||
|
|
||||||
export default function NoteListPage() {
|
const NoteListPage = forwardRef((_, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const layoutRef = useRef<{ scrollToTop: () => void }>(null)
|
const layoutRef = useRef<TPageRef>(null)
|
||||||
const { pubkey, checkLogin } = useNostr()
|
const { pubkey, checkLogin } = useNostr()
|
||||||
const { feedType, relayUrls, isReady, filter } = useFeed()
|
const { feedType, relayUrls, isReady, filter } = useFeed()
|
||||||
|
useImperativeHandle(ref, () => layoutRef.current)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (layoutRef.current) {
|
if (layoutRef.current) {
|
||||||
@@ -46,7 +48,9 @@ export default function NoteListPage() {
|
|||||||
{content}
|
{content}
|
||||||
</PrimaryPageLayout>
|
</PrimaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
NoteListPage.displayName = 'NoteListPage'
|
||||||
|
export default NoteListPage
|
||||||
|
|
||||||
function NoteListPageTitlebar({ temporaryRelayUrls = [] }: { temporaryRelayUrls?: string[] }) {
|
function NoteListPageTitlebar({ temporaryRelayUrls = [] }: { temporaryRelayUrls?: string[] }) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import NotificationList from '@/components/NotificationList'
|
import NotificationList from '@/components/NotificationList'
|
||||||
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
|
||||||
import { Bell } from 'lucide-react'
|
import { Bell } from 'lucide-react'
|
||||||
|
import { forwardRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function NotificationListPage() {
|
const NotificationListPage = forwardRef((_, ref) => {
|
||||||
return (
|
return (
|
||||||
<PrimaryPageLayout
|
<PrimaryPageLayout
|
||||||
|
ref={ref}
|
||||||
pageName="notifications"
|
pageName="notifications"
|
||||||
titlebar={<NotificationListPageTitlebar />}
|
titlebar={<NotificationListPageTitlebar />}
|
||||||
displayScrollToTopButton
|
displayScrollToTopButton
|
||||||
@@ -15,7 +17,9 @@ export default function NotificationListPage() {
|
|||||||
</div>
|
</div>
|
||||||
</PrimaryPageLayout>
|
</PrimaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
NotificationListPage.displayName = 'NotificationListPage'
|
||||||
|
export default NotificationListPage
|
||||||
|
|
||||||
function NotificationListPageTitlebar() {
|
function NotificationListPageTitlebar() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import UserItem from '@/components/UserItem'
|
import UserItem from '@/components/UserItem'
|
||||||
import { useFetchFollowings, useFetchProfile } from '@/hooks'
|
import { useFetchFollowings, useFetchProfile } from '@/hooks'
|
||||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { forwardRef, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function FollowingListPage({ id, index }: { id?: string; index?: number }) {
|
const FollowingListPage = forwardRef(({ id, index }: { id?: string; index?: number }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { profile } = useFetchProfile(id)
|
const { profile } = useFetchProfile(id)
|
||||||
const { followings } = useFetchFollowings(profile?.pubkey)
|
const { followings } = useFetchFollowings(profile?.pubkey)
|
||||||
@@ -45,6 +45,7 @@ export default function FollowingListPage({ id, index }: { id?: string; index?:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout
|
<SecondaryPageLayout
|
||||||
|
ref={ref}
|
||||||
index={index}
|
index={index}
|
||||||
title={
|
title={
|
||||||
profile?.username
|
profile?.username
|
||||||
@@ -61,4 +62,6 @@ export default function FollowingListPage({ id, index }: { id?: string; index?:
|
|||||||
</div>
|
</div>
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
FollowingListPage.displayName = 'FollowingListPage'
|
||||||
|
export default FollowingListPage
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
|
import { forwardRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function HomePage({ index }: { index?: number }) {
|
const HomePage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout index={index} hideBackButton>
|
<SecondaryPageLayout ref={ref} index={index} hideBackButton>
|
||||||
<div className="text-muted-foreground w-full h-screen flex items-center justify-center">
|
<div className="text-muted-foreground w-full h-screen flex items-center justify-center">
|
||||||
{t('Welcome! 🥳')}
|
{t('Welcome! 🥳')}
|
||||||
</div>
|
</div>
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
HomePage.displayName = 'HomePage'
|
||||||
|
export default HomePage
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
|
import { forwardRef } from 'react'
|
||||||
|
|
||||||
export default function LoadingPage({ title, index }: { title?: string; index?: number }) {
|
const LoadingPage = forwardRef(({ title, index }: { title?: string; index?: number }, ref) => {
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout index={index} title={title}>
|
<SecondaryPageLayout ref={ref} index={index} title={title}>
|
||||||
<div className="text-muted-foreground text-center">
|
<div className="text-muted-foreground text-center">
|
||||||
<div>Loading...</div>
|
<div>Loading...</div>
|
||||||
</div>
|
</div>
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
LoadingPage.displayName = 'LoadingPage'
|
||||||
|
export default LoadingPage
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import { useFetchProfile } from '@/hooks'
|
|||||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
import { useMuteList } from '@/providers/MuteListProvider'
|
import { useMuteList } from '@/providers/MuteListProvider'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { forwardRef, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import NotFoundPage from '../NotFoundPage'
|
import NotFoundPage from '../NotFoundPage'
|
||||||
|
|
||||||
export default function MuteListPage({ index }: { index?: number }) {
|
const MuteListPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { profile } = useNostr()
|
const { profile } = useNostr()
|
||||||
const { mutePubkeys } = useMuteList()
|
const { mutePubkeys } = useMuteList()
|
||||||
@@ -55,6 +55,7 @@ export default function MuteListPage({ index }: { index?: number }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout
|
<SecondaryPageLayout
|
||||||
|
ref={ref}
|
||||||
index={index}
|
index={index}
|
||||||
title={t("username's muted", { username: profile.username })}
|
title={t("username's muted", { username: profile.username })}
|
||||||
displayScrollToTopButton
|
displayScrollToTopButton
|
||||||
@@ -67,7 +68,9 @@ export default function MuteListPage({ index }: { index?: number }) {
|
|||||||
</div>
|
</div>
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
MuteListPage.displayName = 'MuteListPage'
|
||||||
|
export default MuteListPage
|
||||||
|
|
||||||
function UserItem({ pubkey }: { pubkey: string }) {
|
function UserItem({ pubkey }: { pubkey: string }) {
|
||||||
const { profile } = useFetchProfile(pubkey)
|
const { profile } = useFetchProfile(pubkey)
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
|
import { forwardRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function NotFoundPage({ index }: { index?: number }) {
|
const NotFoundPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout index={index} hideBackButton>
|
<SecondaryPageLayout ref={ref} index={index} hideBackButton>
|
||||||
<div className="text-muted-foreground w-full h-full flex flex-col items-center justify-center gap-2">
|
<div className="text-muted-foreground w-full h-full flex flex-col items-center justify-center gap-2">
|
||||||
<div>{t('Lost in the void')} 🌌</div>
|
<div>{t('Lost in the void')} 🌌</div>
|
||||||
<div>(404)</div>
|
<div>(404)</div>
|
||||||
</div>
|
</div>
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
NotFoundPage.displayName = 'NotFoundPage'
|
||||||
|
export default NotFoundPage
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { useFetchRelayInfos, useSearchParams } from '@/hooks'
|
|||||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
import { useFeed } from '@/providers/FeedProvider'
|
import { useFeed } from '@/providers/FeedProvider'
|
||||||
import { Filter } from 'nostr-tools'
|
import { Filter } from 'nostr-tools'
|
||||||
import { useMemo } from 'react'
|
import { forwardRef, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function NoteListPage({ index }: { index?: number }) {
|
const NoteListPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { relayUrls } = useFeed()
|
const { relayUrls } = useFeed()
|
||||||
const { searchableRelayUrls } = useFetchRelayInfos(relayUrls)
|
const { searchableRelayUrls } = useFetchRelayInfos(relayUrls)
|
||||||
@@ -43,8 +43,10 @@ export default function NoteListPage({ index }: { index?: number }) {
|
|||||||
}, [searchParams, JSON.stringify(relayUrls)])
|
}, [searchParams, JSON.stringify(relayUrls)])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout index={index} title={title} displayScrollToTopButton>
|
<SecondaryPageLayout ref={ref} index={index} title={title} displayScrollToTopButton>
|
||||||
<NoteList key={title} filter={filter} relayUrls={urls} />
|
<NoteList key={title} filter={filter} relayUrls={urls} />
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
NoteListPage.displayName = 'NoteListPage'
|
||||||
|
export default NoteListPage
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ import { useFetchEvent } from '@/hooks'
|
|||||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
import { getParentEventId, getRootEventId, isPictureEvent } from '@/lib/event'
|
import { getParentEventId, getRootEventId, isPictureEvent } from '@/lib/event'
|
||||||
import { toNote } from '@/lib/link'
|
import { toNote } from '@/lib/link'
|
||||||
import { useMemo } from 'react'
|
import { forwardRef, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import NotFoundPage from '../NotFoundPage'
|
import NotFoundPage from '../NotFoundPage'
|
||||||
|
|
||||||
export default function NotePage({ id, index }: { id?: string; index?: number }) {
|
const NotePage = forwardRef(({ id, index }: { id?: string; index?: number }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { event, isFetching } = useFetchEvent(id)
|
const { event, isFetching } = useFetchEvent(id)
|
||||||
const parentEventId = useMemo(() => getParentEventId(event), [event])
|
const parentEventId = useMemo(() => getParentEventId(event), [event])
|
||||||
@@ -24,7 +24,7 @@ export default function NotePage({ id, index }: { id?: string; index?: number })
|
|||||||
|
|
||||||
if (!event && isFetching) {
|
if (!event && isFetching) {
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout index={index} title={t('Note')}>
|
<SecondaryPageLayout ref={ref} index={index} title={t('Note')}>
|
||||||
<div className="px-4">
|
<div className="px-4">
|
||||||
<Skeleton className="w-10 h-10 rounded-full" />
|
<Skeleton className="w-10 h-10 rounded-full" />
|
||||||
</div>
|
</div>
|
||||||
@@ -35,7 +35,7 @@ export default function NotePage({ id, index }: { id?: string; index?: number })
|
|||||||
|
|
||||||
if (isPictureEvent(event)) {
|
if (isPictureEvent(event)) {
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout index={index} title={t('Note')} displayScrollToTopButton>
|
<SecondaryPageLayout ref={ref} index={index} title={t('Note')} displayScrollToTopButton>
|
||||||
<PictureNote key={`note-${event.id}`} event={event} fetchNoteStats />
|
<PictureNote key={`note-${event.id}`} event={event} fetchNoteStats />
|
||||||
<Separator className="mb-2 mt-4" />
|
<Separator className="mb-2 mt-4" />
|
||||||
<Nip22ReplyNoteList
|
<Nip22ReplyNoteList
|
||||||
@@ -48,7 +48,7 @@ export default function NotePage({ id, index }: { id?: string; index?: number })
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout index={index} title={t('Note')} displayScrollToTopButton>
|
<SecondaryPageLayout ref={ref} index={index} title={t('Note')} displayScrollToTopButton>
|
||||||
<div className="px-4">
|
<div className="px-4">
|
||||||
{rootEventId !== parentEventId && (
|
{rootEventId !== parentEventId && (
|
||||||
<ParentNote key={`root-note-${event.id}`} eventId={rootEventId} />
|
<ParentNote key={`root-note-${event.id}`} eventId={rootEventId} />
|
||||||
@@ -68,7 +68,9 @@ export default function NotePage({ id, index }: { id?: string; index?: number })
|
|||||||
)}
|
)}
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
NotePage.displayName = 'NotePage'
|
||||||
|
export default NotePage
|
||||||
|
|
||||||
function ParentNote({ eventId }: { eventId?: string }) {
|
function ParentNote({ eventId }: { eventId?: string }) {
|
||||||
const { push } = useSecondaryPage()
|
const { push } = useSecondaryPage()
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import OthersRelayList from '@/components/OthersRelayList'
|
import OthersRelayList from '@/components/OthersRelayList'
|
||||||
import { useFetchProfile } from '@/hooks'
|
import { useFetchProfile } from '@/hooks'
|
||||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
|
import { forwardRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function RelaySettingsPage({ id, index }: { id?: string; index?: number }) {
|
const RelaySettingsPage = forwardRef(({ id, index }: { id?: string; index?: number }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { profile } = useFetchProfile(id)
|
const { profile } = useFetchProfile(id)
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ export default function RelaySettingsPage({ id, index }: { id?: string; index?:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout
|
<SecondaryPageLayout
|
||||||
|
ref={ref}
|
||||||
index={index}
|
index={index}
|
||||||
title={t("username's used relays", { username: profile.username })}
|
title={t("username's used relays", { username: profile.username })}
|
||||||
>
|
>
|
||||||
@@ -21,4 +23,6 @@ export default function RelaySettingsPage({ id, index }: { id?: string; index?:
|
|||||||
</div>
|
</div>
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
RelaySettingsPage.displayName = 'RelaySettingsPage'
|
||||||
|
export default RelaySettingsPage
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import { generateImageByPubkey } from '@/lib/pubkey'
|
|||||||
import { useSecondaryPage } from '@/PageManager'
|
import { useSecondaryPage } from '@/PageManager'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { Loader, Upload } from 'lucide-react'
|
import { Loader, Upload } from 'lucide-react'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { forwardRef, useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function ProfileEditorPage({ index }: { index?: number }) {
|
const ProfileEditorPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { pop } = useSecondaryPage()
|
const { pop } = useSecondaryPage()
|
||||||
const { account, profile, profileEvent, publish, updateProfileEvent } = useNostr()
|
const { account, profile, profileEvent, publish, updateProfileEvent } = useNostr()
|
||||||
@@ -98,7 +98,7 @@ export default function ProfileEditorPage({ index }: { index?: number }) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout index={index} title={profile.username} controls={controls}>
|
<SecondaryPageLayout ref={ref} index={index} title={profile.username} controls={controls}>
|
||||||
<div className="px-4">
|
<div className="px-4">
|
||||||
<div className="relative bg-cover bg-center w-full aspect-[21/9] rounded-lg mb-2">
|
<div className="relative bg-cover bg-center w-full aspect-[21/9] rounded-lg mb-2">
|
||||||
<Uploader
|
<Uploader
|
||||||
@@ -174,7 +174,9 @@ export default function ProfileEditorPage({ index }: { index?: number }) {
|
|||||||
</div>
|
</div>
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
ProfileEditorPage.displayName = 'ProfileEditorPage'
|
||||||
|
export default ProfileEditorPage
|
||||||
|
|
||||||
function ItemTitle({ children }: { children: React.ReactNode }) {
|
function ItemTitle({ children }: { children: React.ReactNode }) {
|
||||||
return <div className="text-sm font-semibold text-muted-foreground pl-3">{children}</div>
|
return <div className="text-sm font-semibold text-muted-foreground pl-3">{children}</div>
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import { useFeed } from '@/providers/FeedProvider'
|
|||||||
import client from '@/services/client.service'
|
import client from '@/services/client.service'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { Filter } from 'nostr-tools'
|
import { Filter } from 'nostr-tools'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { forwardRef, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const LIMIT = 50
|
const LIMIT = 50
|
||||||
|
|
||||||
export default function ProfileListPage({ index }: { index?: number }) {
|
const ProfileListPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { searchParams } = useSearchParams()
|
const { searchParams } = useSearchParams()
|
||||||
const { relayUrls } = useFeed()
|
const { relayUrls } = useFeed()
|
||||||
@@ -80,7 +80,7 @@ export default function ProfileListPage({ index }: { index?: number }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout index={index} title={title} displayScrollToTopButton>
|
<SecondaryPageLayout ref={ref} index={index} title={title} displayScrollToTopButton>
|
||||||
<div className="space-y-2 px-4">
|
<div className="space-y-2 px-4">
|
||||||
{Array.from(pubkeySet).map((pubkey, index) => (
|
{Array.from(pubkeySet).map((pubkey, index) => (
|
||||||
<UserItem key={`${index}-${pubkey}`} pubkey={pubkey} />
|
<UserItem key={`${index}-${pubkey}`} pubkey={pubkey} />
|
||||||
@@ -89,4 +89,6 @@ export default function ProfileListPage({ index }: { index?: number }) {
|
|||||||
</div>
|
</div>
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
ProfileListPage.displayName = 'ProfileListPage'
|
||||||
|
export default ProfileListPage
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ import { useFeed } from '@/providers/FeedProvider'
|
|||||||
import { useFollowList } from '@/providers/FollowListProvider'
|
import { useFollowList } from '@/providers/FollowListProvider'
|
||||||
import { useMuteList } from '@/providers/MuteListProvider'
|
import { useMuteList } from '@/providers/MuteListProvider'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
import { useMemo } from 'react'
|
import { forwardRef, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import NotFoundPage from '../NotFoundPage'
|
import NotFoundPage from '../NotFoundPage'
|
||||||
|
|
||||||
export default function ProfilePage({ id, index }: { id?: string; index?: number }) {
|
const ProfilePage = forwardRef(({ id, index }: { id?: string; index?: number }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { push } = useSecondaryPage()
|
const { push } = useSecondaryPage()
|
||||||
const { profile, isFetching } = useFetchProfile(id)
|
const { profile, isFetching } = useFetchProfile(id)
|
||||||
@@ -59,7 +59,7 @@ export default function ProfilePage({ id, index }: { id?: string; index?: number
|
|||||||
|
|
||||||
if (!profile && isFetching) {
|
if (!profile && isFetching) {
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout index={index}>
|
<SecondaryPageLayout index={index} ref={ref}>
|
||||||
<div className="px-4">
|
<div className="px-4">
|
||||||
<div className="relative bg-cover bg-center w-full aspect-[21/9] rounded-lg mb-2">
|
<div className="relative bg-cover bg-center w-full aspect-[21/9] rounded-lg mb-2">
|
||||||
<Skeleton className="w-full h-full object-cover rounded-lg" />
|
<Skeleton className="w-full h-full object-cover rounded-lg" />
|
||||||
@@ -75,7 +75,7 @@ export default function ProfilePage({ id, index }: { id?: string; index?: number
|
|||||||
|
|
||||||
const { banner, username, about, avatar, pubkey } = profile
|
const { banner, username, about, avatar, pubkey } = profile
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout index={index} title={username} displayScrollToTopButton>
|
<SecondaryPageLayout index={index} title={username} displayScrollToTopButton ref={ref}>
|
||||||
<div className="px-4">
|
<div className="px-4">
|
||||||
<div className="relative bg-cover bg-center w-full aspect-[21/9] rounded-lg mb-2">
|
<div className="relative bg-cover bg-center w-full aspect-[21/9] rounded-lg mb-2">
|
||||||
<ProfileBanner
|
<ProfileBanner
|
||||||
@@ -151,4 +151,6 @@ export default function ProfilePage({ id, index }: { id?: string; index?: number
|
|||||||
)}
|
)}
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
ProfilePage.displayName = 'ProfilePage'
|
||||||
|
export default ProfilePage
|
||||||
|
|||||||
@@ -5,19 +5,20 @@ import { Button } from '@/components/ui/button'
|
|||||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
import { normalizeUrl, simplifyUrl } from '@/lib/url'
|
import { normalizeUrl, simplifyUrl } from '@/lib/url'
|
||||||
import { Check, Copy } from 'lucide-react'
|
import { Check, Copy } from 'lucide-react'
|
||||||
import { useMemo, useState } from 'react'
|
import { forwardRef, useMemo, useState } from 'react'
|
||||||
import NotFoundPage from '../NotFoundPage'
|
import NotFoundPage from '../NotFoundPage'
|
||||||
|
|
||||||
export default function RelayPage({ url, index }: { url?: string; index?: number }) {
|
const RelayPage = forwardRef(({ url, index }: { url?: string; index?: number }, ref) => {
|
||||||
const normalizedUrl = useMemo(() => (url ? normalizeUrl(url) : undefined), [url])
|
const normalizedUrl = useMemo(() => (url ? normalizeUrl(url) : undefined), [url])
|
||||||
const title = useMemo(() => (url ? simplifyUrl(url) : undefined), [url])
|
const title = useMemo(() => (url ? simplifyUrl(url) : undefined), [url])
|
||||||
|
|
||||||
if (!normalizedUrl) {
|
if (!normalizedUrl) {
|
||||||
return <NotFoundPage />
|
return <NotFoundPage ref={ref} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout
|
<SecondaryPageLayout
|
||||||
|
ref={ref}
|
||||||
index={index}
|
index={index}
|
||||||
title={title}
|
title={title}
|
||||||
controls={<RelayPageControls url={normalizedUrl} />}
|
controls={<RelayPageControls url={normalizedUrl} />}
|
||||||
@@ -27,7 +28,9 @@ export default function RelayPage({ url, index }: { url?: string; index?: number
|
|||||||
<NoteList relayUrls={[normalizedUrl]} />
|
<NoteList relayUrls={[normalizedUrl]} />
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
RelayPage.displayName = 'RelayPage'
|
||||||
|
export default RelayPage
|
||||||
|
|
||||||
function RelayPageControls({ url }: { url: string }) {
|
function RelayPageControls({ url }: { url: string }) {
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import MailboxSetting from '@/components/MailboxSetting'
|
|||||||
import RelaySetsSetting from '@/components/RelaySetsSetting'
|
import RelaySetsSetting from '@/components/RelaySetsSetting'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
import { useEffect, useState } from 'react'
|
import { forwardRef, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function RelaySettingsPage({ index }: { index?: number }) {
|
const RelaySettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [tabValue, setTabValue] = useState('relay-sets')
|
const [tabValue, setTabValue] = useState('relay-sets')
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ export default function RelaySettingsPage({ index }: { index?: number }) {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout index={index} title={t('Relay settings')}>
|
<SecondaryPageLayout ref={ref} index={index} title={t('Relay settings')}>
|
||||||
<Tabs value={tabValue} onValueChange={setTabValue} className="px-4 space-y-4">
|
<Tabs value={tabValue} onValueChange={setTabValue} className="px-4 space-y-4">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="relay-sets">{t('Relay Sets')}</TabsTrigger>
|
<TabsTrigger value="relay-sets">{t('Relay Sets')}</TabsTrigger>
|
||||||
@@ -36,4 +36,6 @@ export default function RelaySettingsPage({ index }: { index?: number }) {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
RelaySettingsPage.displayName = 'RelaySettingsPage'
|
||||||
|
export default RelaySettingsPage
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { Check, ChevronRight, Copy, Info, KeyRound, Languages, Server, SunMoon }
|
|||||||
import { forwardRef, HTMLProps, useState } from 'react'
|
import { forwardRef, HTMLProps, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function SettingsPage({ index }: { index?: number }) {
|
const SettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||||
const { t, i18n } = useTranslation()
|
const { t, i18n } = useTranslation()
|
||||||
const { nsec, ncryptsec } = useNostr()
|
const { nsec, ncryptsec } = useNostr()
|
||||||
const { push } = useSecondaryPage()
|
const { push } = useSecondaryPage()
|
||||||
@@ -27,7 +27,7 @@ export default function SettingsPage({ index }: { index?: number }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecondaryPageLayout index={index} title={t('Settings')}>
|
<SecondaryPageLayout ref={ref} index={index} title={t('Settings')}>
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Languages />
|
<Languages />
|
||||||
@@ -112,7 +112,9 @@ export default function SettingsPage({ index }: { index?: number }) {
|
|||||||
</AboutInfoDialog>
|
</AboutInfoDialog>
|
||||||
</SecondaryPageLayout>
|
</SecondaryPageLayout>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
SettingsPage.displayName = 'SettingsPage'
|
||||||
|
export default SettingsPage
|
||||||
|
|
||||||
const SettingItem = forwardRef<HTMLDivElement, HTMLProps<HTMLDivElement>>(
|
const SettingItem = forwardRef<HTMLDivElement, HTMLProps<HTMLDivElement>>(
|
||||||
({ children, className, ...props }, ref) => {
|
({ children, className, ...props }, ref) => {
|
||||||
|
|||||||
@@ -95,3 +95,5 @@ export type TLanguage = 'en' | 'zh'
|
|||||||
export type TImageInfo = { url: string; blurHash?: string; dim?: { width: number; height: number } }
|
export type TImageInfo = { url: string; blurHash?: string; dim?: { width: number; height: number } }
|
||||||
|
|
||||||
export type TNoteListMode = 'posts' | 'postsAndReplies' | 'pictures'
|
export type TNoteListMode = 'posts' | 'postsAndReplies' | 'pictures'
|
||||||
|
|
||||||
|
export type TPageRef = { scrollToTop: () => void }
|
||||||
|
|||||||
Reference in New Issue
Block a user