Browse Source

fix single/double pane modes

imwald
Silberengel 3 months ago
parent
commit
b27efafd4a
  1. 1380
      src/PageManager.tsx
  2. 22
      src/components/NoteDrawer/index.tsx
  3. 14
      src/components/NoteOptions/useMenuActions.tsx
  4. 37
      src/components/PaneModeToggle/index.tsx
  5. 3
      src/components/ScrollToTopButton/index.tsx
  6. 39
      src/components/Sidebar/PaneModeToggle.tsx
  7. 6
      src/components/Sidebar/index.tsx
  8. 8
      src/components/ui/command.tsx
  9. 2
      src/components/ui/sheet.tsx
  10. 1
      src/constants.ts
  11. 2
      src/i18n/locales/ar.ts
  12. 2
      src/i18n/locales/de.ts
  13. 2
      src/i18n/locales/en.ts
  14. 2
      src/i18n/locales/es.ts
  15. 2
      src/i18n/locales/fa.ts
  16. 2
      src/i18n/locales/fr.ts
  17. 2
      src/i18n/locales/hi.ts
  18. 2
      src/i18n/locales/it.ts
  19. 2
      src/i18n/locales/ja.ts
  20. 2
      src/i18n/locales/ko.ts
  21. 2
      src/i18n/locales/pl.ts
  22. 2
      src/i18n/locales/pt-BR.ts
  23. 2
      src/i18n/locales/pt-PT.ts
  24. 2
      src/i18n/locales/ru.ts
  25. 2
      src/i18n/locales/th.ts
  26. 2
      src/i18n/locales/zh.ts
  27. 7
      src/pages/primary/NoteListPage/index.tsx
  28. 41
      src/providers/DeepBrowsingProvider.tsx
  29. 8
      src/routes.tsx
  30. 13
      src/services/local-storage.service.ts

1380
src/PageManager.tsx

File diff suppressed because it is too large Load Diff

22
src/components/NoteDrawer/index.tsx

@ -0,0 +1,22 @@
import { Sheet, SheetContent } from '@/components/ui/sheet'
import NotePage from '@/pages/secondary/NotePage'
interface NoteDrawerProps {
open: boolean
onOpenChange: (open: boolean) => void
noteId: string | null
}
export default function NoteDrawer({ open, onOpenChange, noteId }: NoteDrawerProps) {
if (!noteId) return null
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent side="right" className="w-full sm:max-w-[1042px] overflow-y-auto p-0">
<div className="h-full">
<NotePage id={noteId} index={0} hideTitlebar={false} />
</div>
</SheetContent>
</Sheet>
)
}

14
src/components/NoteOptions/useMenuActions.tsx

@ -1,7 +1,7 @@
import { ExtendedKind } from '@/constants' import { ExtendedKind } from '@/constants'
import { getNoteBech32Id, isProtectedEvent, getRootEventHexId } from '@/lib/event' import { getNoteBech32Id, isProtectedEvent, getRootEventHexId } from '@/lib/event'
import { getLongFormArticleMetadataFromEvent } from '@/lib/event-metadata' import { getLongFormArticleMetadataFromEvent } from '@/lib/event-metadata'
import { toNjump, toAlexandria } from '@/lib/link' import { toAlexandria } from '@/lib/link'
import logger from '@/lib/logger' import logger from '@/lib/logger'
import { pubkeyToNpub } from '@/lib/pubkey' import { pubkeyToNpub } from '@/lib/pubkey'
import { normalizeUrl, simplifyUrl } from '@/lib/url' import { normalizeUrl, simplifyUrl } from '@/lib/url'
@ -18,6 +18,7 @@ import { useMemo, useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { toast } from 'sonner' import { toast } from 'sonner'
import RelayIcon from '../RelayIcon' import RelayIcon from '../RelayIcon'
import { usePrimaryPage } from '@/PageManager'
export interface SubMenuAction { export interface SubMenuAction {
label: React.ReactNode label: React.ReactNode
@ -55,6 +56,7 @@ export function useMenuActions({
openHighlightEditor openHighlightEditor
}: UseMenuActionsProps) { }: UseMenuActionsProps) {
const { t } = useTranslation() const { t } = useTranslation()
const { current: currentPrimaryPage } = usePrimaryPage()
const { pubkey, attemptDelete, publish } = useNostr() const { pubkey, attemptDelete, publish } = useNostr()
const { relayUrls: currentBrowsingRelayUrls } = useCurrentRelays() const { relayUrls: currentBrowsingRelayUrls } = useCurrentRelays()
const { relaySets, favoriteRelays } = useFavoriteRelays() const { relaySets, favoriteRelays } = useFavoriteRelays()
@ -455,9 +457,15 @@ export function useMenuActions({
}, },
{ {
icon: Link, icon: Link,
label: t('Share with Njump'), label: t('Share with Jumble'),
onClick: () => { onClick: () => {
navigator.clipboard.writeText(toNjump(getNoteBech32Id(event))) const noteId = getNoteBech32Id(event)
// Only include context for discussions page, use plain /notes/{id} for others
const path = currentPrimaryPage === 'discussions'
? `/discussions/notes/${noteId}`
: `/notes/${noteId}`
const jumbleUrl = `https://jumble.imwald.eu${path}`
navigator.clipboard.writeText(jumbleUrl)
closeDrawer() closeDrawer()
} }
}, },

37
src/components/PaneModeToggle/index.tsx

@ -0,0 +1,37 @@
import { Button } from '@/components/ui/button'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import storage from '@/services/local-storage.service'
import { PanelLeft, PanelsLeftRight } from 'lucide-react'
import { useState } from 'react'
export default function PaneModeToggle() {
const { isSmallScreen } = useScreenSize()
const [panelMode, setPanelMode] = useState<'single' | 'double'>(() => storage.getPanelMode())
// Hide on mobile
if (isSmallScreen) return null
const toggleMode = () => {
const newMode = panelMode === 'single' ? 'double' : 'single'
setPanelMode(newMode)
storage.setPanelMode(newMode)
}
return (
<Button
variant="ghost"
className="flex shadow-none items-center transition-colors duration-500 bg-transparent w-12 h-12 xl:w-full xl:h-auto p-3 m-0 xl:py-2 xl:px-3 rounded-lg xl:justify-start gap-4 text-lg font-semibold [&_svg]:size-full xl:[&_svg]:size-4"
title={panelMode === 'single' ? 'Switch to double-pane mode' : 'Switch to single-pane mode'}
onClick={toggleMode}
>
{panelMode === 'single' ? (
<PanelLeft strokeWidth={3} />
) : (
<PanelsLeftRight strokeWidth={3} />
)}
<div className="max-xl:hidden">
{panelMode === 'single' ? 'Single-pane' : 'Double-pane'}
</div>
</Button>
)
}

3
src/components/ScrollToTopButton/index.tsx

@ -32,7 +32,8 @@ export default function ScrollToTopButton({
style={{ style={{
bottom: isSmallScreen bottom: isSmallScreen
? 'calc(env(safe-area-inset-bottom) + 3.75rem)' ? 'calc(env(safe-area-inset-bottom) + 3.75rem)'
: 'calc(env(safe-area-inset-bottom) + 0.75rem)' : 'calc(env(safe-area-inset-bottom) + 0.75rem)',
willChange: 'opacity' // Hint to browser for better scroll performance
}} }}
> >
<Button <Button

39
src/components/Sidebar/PaneModeToggle.tsx

@ -0,0 +1,39 @@
import { Button } from '@/components/ui/button'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import storage from '@/services/local-storage.service'
import { PanelLeft, PanelsLeftRight } from 'lucide-react'
import { useState } from 'react'
export default function PaneModeToggle() {
const { isSmallScreen } = useScreenSize()
const [panelMode, setPanelMode] = useState<'single' | 'double'>(() => storage.getPanelMode())
// Hide on mobile
if (isSmallScreen) return null
const toggleMode = () => {
const newMode = panelMode === 'single' ? 'double' : 'single'
setPanelMode(newMode)
storage.setPanelMode(newMode)
// Dispatch event to notify PageManager of the change
window.dispatchEvent(new CustomEvent('panelModeChanged', { detail: { mode: newMode } }))
}
return (
<Button
variant="ghost"
className="flex shadow-none items-center transition-colors duration-500 bg-transparent w-12 h-12 xl:w-full xl:h-auto p-3 m-0 xl:py-2 xl:px-3 rounded-lg xl:justify-start gap-4 text-lg font-semibold [&_svg]:size-full xl:[&_svg]:size-4"
title={panelMode === 'single' ? 'Switch to double-pane mode' : 'Switch to single-pane mode'}
onClick={toggleMode}
>
{panelMode === 'single' ? (
<PanelLeft strokeWidth={3} />
) : (
<PanelsLeftRight strokeWidth={3} />
)}
<div className="max-xl:hidden">
{panelMode === 'single' ? 'Single-pane' : 'Double-pane'}
</div>
</Button>
)
}

6
src/components/Sidebar/index.tsx

@ -11,6 +11,7 @@ import ProfileButton from './ProfileButton'
import RssButton from './RssButton' import RssButton from './RssButton'
import SearchButton from './SearchButton' import SearchButton from './SearchButton'
import SettingsButton from './SettingsButton' import SettingsButton from './SettingsButton'
import PaneModeToggle from './PaneModeToggle'
import storage from '@/services/local-storage.service' import storage from '@/services/local-storage.service'
export default function PrimaryPageSidebar() { export default function PrimaryPageSidebar() {
@ -40,7 +41,10 @@ export default function PrimaryPageSidebar() {
<SettingsButton /> <SettingsButton />
<PostButton /> <PostButton />
</div> </div>
<AccountButton /> <div className="space-y-2">
<AccountButton />
<PaneModeToggle />
</div>
</div> </div>
) )
} }

8
src/components/ui/command.tsx

@ -35,16 +35,16 @@ const CommandDialog = ({
}: DialogProps & { classNames?: { content?: string } }) => { }: DialogProps & { classNames?: { content?: string } }) => {
return ( return (
<Dialog {...props}> <Dialog {...props}>
<DialogHeader className="sr-only">
<DialogTitle>Command Menu</DialogTitle>
<DialogDescription>Search and select a command</DialogDescription>
</DialogHeader>
<DialogContent <DialogContent
className={cn( className={cn(
'overflow-hidden p-0 shadow-lg top-4 translate-y-0 data-[state=closed]:slide-out-to-top-4 data-[state=open]:slide-in-from-top-4', 'overflow-hidden p-0 shadow-lg top-4 translate-y-0 data-[state=closed]:slide-out-to-top-4 data-[state=open]:slide-in-from-top-4',
classNames?.content classNames?.content
)} )}
> >
<DialogHeader className="sr-only">
<DialogTitle>Command Menu</DialogTitle>
<DialogDescription>Search and select a command</DialogDescription>
</DialogHeader>
<Command <Command
shouldFilter={false} shouldFilter={false}
className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5" className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"

2
src/components/ui/sheet.tsx

@ -98,7 +98,7 @@ const SheetContent = React.forwardRef<
<SheetOverlay /> <SheetOverlay />
<SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}> <SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
{!hideClose && ( {!hideClose && (
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary"> <SheetPrimitive.Close className="absolute right-4 top-4 z-[60] rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" /> <X className="h-4 w-4" />
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</SheetPrimitive.Close> </SheetPrimitive.Close>

1
src/constants.ts

@ -54,6 +54,7 @@ export const StorageKey = {
RESPECT_QUIET_TAGS: 'respectQuietTags', RESPECT_QUIET_TAGS: 'respectQuietTags',
GLOBAL_QUIET_MODE: 'globalQuietMode', GLOBAL_QUIET_MODE: 'globalQuietMode',
SHOW_RSS_FEED: 'showRssFeed', SHOW_RSS_FEED: 'showRssFeed',
PANE_MODE: 'paneMode',
MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated
HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated
ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated

2
src/i18n/locales/ar.ts

@ -63,7 +63,7 @@ export default {
Add: 'إضافة', Add: 'إضافة',
'n relays': '{{n}} ريلايات', 'n relays': '{{n}} ريلايات',
Rename: 'إعادة تسمية', Rename: 'إعادة تسمية',
'Share with Njump': 'مشاركة مع Njump', 'Share with Jumble': 'مشاركة مع Jumble',
'Share with Alexandria': 'مشاركة مع Alexandria', 'Share with Alexandria': 'مشاركة مع Alexandria',
Delete: 'حذف', Delete: 'حذف',
'Relay already exists': 'الريلاي موجود بالفعل', 'Relay already exists': 'الريلاي موجود بالفعل',

2
src/i18n/locales/de.ts

@ -63,7 +63,7 @@ export default {
Add: 'Hinzufügen', Add: 'Hinzufügen',
'n relays': '{{n}} Relays', 'n relays': '{{n}} Relays',
Rename: 'Umbenennen', Rename: 'Umbenennen',
'Share with Njump': 'Mit Njump teilen', 'Share with Jumble': 'Mit Jumble teilen',
'Share with Alexandria': 'Mit Alexandria teilen', 'Share with Alexandria': 'Mit Alexandria teilen',
Delete: 'Löschen', Delete: 'Löschen',
'Relay already exists': 'Relay existiert bereits', 'Relay already exists': 'Relay existiert bereits',

2
src/i18n/locales/en.ts

@ -66,7 +66,7 @@ export default {
'n relays': '{{n}} relays', 'n relays': '{{n}} relays',
Rename: 'Rename', Rename: 'Rename',
'Copy share link': 'Copy share link', 'Copy share link': 'Copy share link',
'Share with Njump': 'Share with Njump', 'Share with Jumble': 'Share with Jumble',
'Share with Alexandria': 'Share with Alexandria', 'Share with Alexandria': 'Share with Alexandria',
Delete: 'Delete', Delete: 'Delete',
'Relay already exists': 'Relay already exists', 'Relay already exists': 'Relay already exists',

2
src/i18n/locales/es.ts

@ -63,7 +63,7 @@ export default {
Add: 'Agregar', Add: 'Agregar',
'n relays': '{{n}} relés', 'n relays': '{{n}} relés',
Rename: 'Renombrar', Rename: 'Renombrar',
'Share with Njump': 'Compartir con Njump', 'Share with Jumble': 'Compartir con Jumble',
'Share with Alexandria': 'Compartir con Alexandria', 'Share with Alexandria': 'Compartir con Alexandria',
Delete: 'Eliminar', Delete: 'Eliminar',
'Relay already exists': 'El relé ya existe', 'Relay already exists': 'El relé ya existe',

2
src/i18n/locales/fa.ts

@ -62,7 +62,7 @@ export default {
Add: 'افزودن', Add: 'افزودن',
'n relays': '{{n}} رله', 'n relays': '{{n}} رله',
Rename: 'تغییر نام', Rename: 'تغییر نام',
'Share with Njump': 'اشتراکگذاری با Njump', 'Share with Jumble': 'اشتراکگذاری با Jumble',
'Share with Alexandria': 'اشتراکگذاری با Alexandria', 'Share with Alexandria': 'اشتراکگذاری با Alexandria',
Delete: 'حذف', Delete: 'حذف',
'Relay already exists': 'رله از قبل موجود است', 'Relay already exists': 'رله از قبل موجود است',

2
src/i18n/locales/fr.ts

@ -63,7 +63,7 @@ export default {
Add: 'Ajouter', Add: 'Ajouter',
'n relays': '{{n}} relais', 'n relays': '{{n}} relais',
Rename: 'Renommer', Rename: 'Renommer',
'Share with Njump': 'Partager avec Njump', 'Share with Jumble': 'Partager avec Jumble',
'Share with Alexandria': 'Partager avec Alexandria', 'Share with Alexandria': 'Partager avec Alexandria',
Delete: 'Supprimer', Delete: 'Supprimer',
'Relay already exists': 'Le relais existe déjà', 'Relay already exists': 'Le relais existe déjà',

2
src/i18n/locales/hi.ts

@ -62,7 +62,7 @@ export default {
Add: 'ज', Add: 'ज',
'n relays': '{{n}} रि', 'n relays': '{{n}} रि',
Rename: 'नम बदल', Rename: 'नम बदल',
'Share with Njump': 'Njumpथ शयर कर', 'Share with Jumble': 'Jumbleथ शयर कर',
'Share with Alexandria': 'Alexandria कथ शयर कर', 'Share with Alexandria': 'Alexandria कथ शयर कर',
Delete: 'हट', Delete: 'हट',
'Relay already exists': 'रि पहलद ह', 'Relay already exists': 'रि पहलद ह',

2
src/i18n/locales/it.ts

@ -62,7 +62,7 @@ export default {
Add: 'Aggiungi', Add: 'Aggiungi',
'n relays': '{{n}} relays', 'n relays': '{{n}} relays',
Rename: 'Rinomina', Rename: 'Rinomina',
'Share with Njump': 'Condividi con Njump', 'Share with Jumble': 'Condividi con Jumble',
'Share with Alexandria': 'Condividi con Alexandria', 'Share with Alexandria': 'Condividi con Alexandria',
Delete: 'Cancella', Delete: 'Cancella',
'Relay already exists': 'Relay già esistente', 'Relay already exists': 'Relay già esistente',

2
src/i18n/locales/ja.ts

@ -63,7 +63,7 @@ export default {
Add: '追加', Add: '追加',
'n relays': '{{n}} 個のリレイ', 'n relays': '{{n}} 個のリレイ',
Rename: '名前変更', Rename: '名前変更',
'Share with Njump': 'Njumpで共有', 'Share with Jumble': 'Jumbleで共有',
'Share with Alexandria': 'Alexandriaで共有', 'Share with Alexandria': 'Alexandriaで共有',
Delete: '削除', Delete: '削除',
'Relay already exists': 'リレイは既に存在します', 'Relay already exists': 'リレイは既に存在します',

2
src/i18n/locales/ko.ts

@ -62,7 +62,7 @@ export default {
Add: '추가', Add: '추가',
'n relays': '{{n}}개의 릴레이', 'n relays': '{{n}}개의 릴레이',
Rename: '이름 변경', Rename: '이름 변경',
'Share with Njump': 'Njump로 공유', 'Share with Jumble': 'Jumble로 공유',
'Share with Alexandria': 'Alexandria로 공유', 'Share with Alexandria': 'Alexandria로 공유',
Delete: '삭제', Delete: '삭제',
'Relay already exists': '릴레이가 이미 존재합니다', 'Relay already exists': '릴레이가 이미 존재합니다',

2
src/i18n/locales/pl.ts

@ -62,7 +62,7 @@ export default {
Add: 'Dodaj', Add: 'Dodaj',
'n relays': '{{n}} szt.', 'n relays': '{{n}} szt.',
Rename: 'Zmień nazwę', Rename: 'Zmień nazwę',
'Share with Njump': 'Udostępnij przez Njump', 'Share with Jumble': 'Udostępnij przez Jumble',
'Share with Alexandria': 'Udostępnij przez Alexandria', 'Share with Alexandria': 'Udostępnij przez Alexandria',
Delete: 'Usuń', Delete: 'Usuń',
'Relay already exists': 'Transmiter już istnieje', 'Relay already exists': 'Transmiter już istnieje',

2
src/i18n/locales/pt-BR.ts

@ -62,7 +62,7 @@ export default {
Add: 'Adicionar', Add: 'Adicionar',
'n relays': '{{n}} relays', 'n relays': '{{n}} relays',
Rename: 'Renomear', Rename: 'Renomear',
'Share with Njump': 'Compartilhar com Njump', 'Share with Jumble': 'Compartilhar com Jumble',
'Share with Alexandria': 'Compartilhar com Alexandria', 'Share with Alexandria': 'Compartilhar com Alexandria',
Delete: 'Excluir', Delete: 'Excluir',
'Relay already exists': 'Relay já existe', 'Relay already exists': 'Relay já existe',

2
src/i18n/locales/pt-PT.ts

@ -63,7 +63,7 @@ export default {
Add: 'Adicionar', Add: 'Adicionar',
'n relays': '{{n}} relés', 'n relays': '{{n}} relés',
Rename: 'Renomear', Rename: 'Renomear',
'Share with Njump': 'Compartilhar com Njump', 'Share with Jumble': 'Compartilhar com Jumble',
'Share with Alexandria': 'Compartilhar com Alexandria', 'Share with Alexandria': 'Compartilhar com Alexandria',
Delete: 'Excluir', Delete: 'Excluir',
'Relay already exists': 'Relé já existe', 'Relay already exists': 'Relé já existe',

2
src/i18n/locales/ru.ts

@ -63,7 +63,7 @@ export default {
Add: 'Добавить', Add: 'Добавить',
'n relays': '{{n}} ретрансляторов', 'n relays': '{{n}} ретрансляторов',
Rename: 'Переименовать', Rename: 'Переименовать',
'Share with Njump': 'Поделиться через Njump', 'Share with Jumble': 'Поделиться через Jumble',
'Share with Alexandria': 'Поделиться через Alexandria', 'Share with Alexandria': 'Поделиться через Alexandria',
Delete: 'Удалить', Delete: 'Удалить',
'Relay already exists': 'Ретранслятор уже существует', 'Relay already exists': 'Ретранслятор уже существует',

2
src/i18n/locales/th.ts

@ -62,7 +62,7 @@ export default {
Add: 'เพม', Add: 'เพม',
'n relays': '{{n}} รเลย', 'n relays': '{{n}} รเลย',
Rename: 'เปลยนชอ', Rename: 'เปลยนชอ',
'Share with Njump': 'แชราน Njump', 'Share with Jumble': 'แชราน Jumble',
'Share with Alexandria': 'แชราน Alexandria', 'Share with Alexandria': 'แชราน Alexandria',
Delete: 'ลบ', Delete: 'ลบ',
'Relay already exists': 'รเลยอยแลว', 'Relay already exists': 'รเลยอยแลว',

2
src/i18n/locales/zh.ts

@ -62,7 +62,7 @@ export default {
Add: '添加', Add: '添加',
'n relays': '{{n}} 个服务器', 'n relays': '{{n}} 个服务器',
Rename: '重命名', Rename: '重命名',
'Share with Njump': '通过Njump分享', 'Share with Jumble': '通过Jumble分享',
'Share with Alexandria': '通过Alexandria分享', 'Share with Alexandria': '通过Alexandria分享',
Delete: '删除', Delete: '删除',
'Relay already exists': '服务器已存在', 'Relay already exists': '服务器已存在',

7
src/pages/primary/NoteListPage/index.tsx

@ -38,11 +38,8 @@ const NoteListPage = forwardRef((_, ref) => {
const [showRelayDetails, setShowRelayDetails] = useState(false) const [showRelayDetails, setShowRelayDetails] = useState(false)
useImperativeHandle(ref, () => layoutRef.current) useImperativeHandle(ref, () => layoutRef.current)
useEffect(() => { // REMOVED: Scroll-to-top logic - feed should NEVER scroll to top when drawer opens/closes
if (layoutRef.current) { // The feed stays mounted and maintains scroll position at all times
layoutRef.current.scrollToTop('instant')
}
}, [JSON.stringify(relayUrls), feedInfo])
useEffect(() => { useEffect(() => {
if (relayUrls.length) { if (relayUrls.length) {

41
src/providers/DeepBrowsingProvider.tsx

@ -33,30 +33,41 @@ export function DeepBrowsingProvider({
useEffect(() => { useEffect(() => {
if (!active) return if (!active) return
let rafId: number | null = null
const handleScroll = () => { const handleScroll = () => {
const scrollTop = (!scrollAreaRef ? window.scrollY : scrollAreaRef.current?.scrollTop) || 0 // Use requestAnimationFrame to throttle scroll updates and prevent scroll-linked positioning warnings
const diff = scrollTop - lastScrollTopRef.current if (rafId !== null) return
lastScrollTopRef.current = scrollTop
setLastScrollTop(scrollTop)
if (scrollTop <= 800) {
setDeepBrowsing(false)
return
}
if (diff > 20) { rafId = requestAnimationFrame(() => {
setDeepBrowsing(true) const scrollTop = (!scrollAreaRef ? window.scrollY : scrollAreaRef.current?.scrollTop) || 0
} else if (diff < -20) { const diff = scrollTop - lastScrollTopRef.current
setDeepBrowsing(false) lastScrollTopRef.current = scrollTop
} setLastScrollTop(scrollTop)
if (scrollTop <= 800) {
setDeepBrowsing(false)
rafId = null
return
}
if (diff > 20) {
setDeepBrowsing(true)
} else if (diff < -20) {
setDeepBrowsing(false)
}
rafId = null
})
} }
const target = scrollAreaRef ? scrollAreaRef.current : window const target = scrollAreaRef ? scrollAreaRef.current : window
target?.addEventListener('scroll', handleScroll) target?.addEventListener('scroll', handleScroll, { passive: true })
return () => { return () => {
target?.removeEventListener('scroll', handleScroll) target?.removeEventListener('scroll', handleScroll)
if (rafId !== null) {
cancelAnimationFrame(rafId)
}
} }
}, [active]) }, [active, scrollAreaRef])
return ( return (
<DeepBrowsingContext.Provider value={{ deepBrowsing, lastScrollTop }}> <DeepBrowsingContext.Provider value={{ deepBrowsing, lastScrollTop }}>

8
src/routes.tsx

@ -23,12 +23,20 @@ import FollowPacksPage from './pages/secondary/FollowPacksPage'
const ROUTES = [ const ROUTES = [
{ path: '/notes', element: <NoteListPage /> }, { path: '/notes', element: <NoteListPage /> },
{ path: '/notes/:id', element: <NotePage /> }, { path: '/notes/:id', element: <NotePage /> },
// Contextual note routes (e.g., /discussions/notes/:id, /search/notes/:id)
{ path: '/discussions/notes/:id', element: <NotePage /> },
{ path: '/search/notes/:id', element: <NotePage /> },
{ path: '/profile/notes/:id', element: <NotePage /> },
{ path: '/explore/notes/:id', element: <NotePage /> },
{ path: '/notifications/notes/:id', element: <NotePage /> },
{ path: '/users', element: <ProfileListPage /> }, { path: '/users', element: <ProfileListPage /> },
{ path: '/users/:id', element: <ProfilePage /> }, { path: '/users/:id', element: <ProfilePage /> },
{ path: '/users/:id/following', element: <FollowingListPage /> }, { path: '/users/:id/following', element: <FollowingListPage /> },
{ path: '/users/:id/relays', element: <OthersRelaySettingsPage /> }, { path: '/users/:id/relays', element: <OthersRelaySettingsPage /> },
{ path: '/relays/:url', element: <RelayPage /> }, { path: '/relays/:url', element: <RelayPage /> },
{ path: '/relays/:url/reviews', element: <RelayReviewsPage /> }, { path: '/relays/:url/reviews', element: <RelayReviewsPage /> },
// Contextual relay route (only for explore page where you discover relays)
{ path: '/explore/relays/:url', element: <RelayPage /> },
{ path: '/search', element: <SearchPage /> }, { path: '/search', element: <SearchPage /> },
{ path: '/settings', element: <SettingsPage /> }, { path: '/settings', element: <SettingsPage /> },
{ path: '/settings/relays', element: <RelaySettingsPage /> }, { path: '/settings/relays', element: <RelaySettingsPage /> },

13
src/services/local-storage.service.ts

@ -60,6 +60,7 @@ class LocalStorageService {
private respectQuietTags: boolean = true private respectQuietTags: boolean = true
private globalQuietMode: boolean = false private globalQuietMode: boolean = false
private showRssFeed: boolean = true private showRssFeed: boolean = true
private panelMode: 'single' | 'double' = 'single'
constructor() { constructor() {
if (!LocalStorageService.instance) { if (!LocalStorageService.instance) {
@ -296,6 +297,9 @@ class LocalStorageService {
const showRssFeedStr = window.localStorage.getItem(StorageKey.SHOW_RSS_FEED) const showRssFeedStr = window.localStorage.getItem(StorageKey.SHOW_RSS_FEED)
this.showRssFeed = showRssFeedStr === null ? true : showRssFeedStr === 'true' // Default to true this.showRssFeed = showRssFeedStr === null ? true : showRssFeedStr === 'true' // Default to true
const panelModeStr = window.localStorage.getItem(StorageKey.PANE_MODE)
this.panelMode = panelModeStr === 'double' ? 'double' : 'single' // Default to 'single'
// Clean up deprecated data // Clean up deprecated data
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP) window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP)
window.localStorage.removeItem(StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP) window.localStorage.removeItem(StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP)
@ -675,6 +679,15 @@ class LocalStorageService {
this.showRssFeed = show this.showRssFeed = show
window.localStorage.setItem(StorageKey.SHOW_RSS_FEED, show.toString()) window.localStorage.setItem(StorageKey.SHOW_RSS_FEED, show.toString())
} }
getPanelMode(): 'single' | 'double' {
return this.panelMode
}
setPanelMode(mode: 'single' | 'double') {
this.panelMode = mode
window.localStorage.setItem(StorageKey.PANE_MODE, mode)
}
} }
const instance = new LocalStorageService() const instance = new LocalStorageService()

Loading…
Cancel
Save