Browse Source

bug-fixes

imwald
Silberengel 3 weeks ago
parent
commit
5ff909949b
  1. 25
      src/PageManager.tsx
  2. 8
      src/components/ImageGallery/index.tsx
  3. 77
      src/components/ImageWithLightbox/index.tsx
  4. 8
      src/components/Note/AsciidocArticle/AsciidocArticle.tsx
  5. 23
      src/components/NoteDrawer/index.tsx
  6. 6
      src/index.css
  7. 41
      src/services/client.service.ts
  8. 24
      src/services/modal-manager.service.ts

25
src/PageManager.tsx

@ -1,3 +1,7 @@ @@ -1,3 +1,7 @@
// This file exports both React components and hooks alongside inline JSX in callbacks,
// making it incompatible with Vite's Fast Refresh auto-detection. Opting into explicit
// full-reload mode to suppress the "incompatible export" HMR warning.
// @refresh reset
import { RefreshButton } from '@/components/RefreshButton'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
@ -1435,37 +1439,16 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { @@ -1435,37 +1439,16 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
const onPopState = (e: PopStateEvent) => {
if (ignorePopStateRef.current) {
logger.info('[LightboxTrace][PageManager] popstate ignored', {
reason: 'ignorePopStateRef',
pathname: window.location.pathname,
state: e.state
})
ignorePopStateRef.current = false
return
}
logger.info('[LightboxTrace][PageManager] popstate received', {
pathname: window.location.pathname,
state: e.state,
secondaryStackLength: secondaryStackRef.current.length,
drawerOpen,
drawerNoteId,
panelMode,
isSmallScreen
})
// If the side panel has frames, this popstate is almost certainly stack navigation — do not let
// modalManager steal it (history.forward + return), which leaves the URL changed and the panel stale.
if (secondaryStackRef.current.length === 0) {
const closeModal = modalManager.pop()
logger.info('[LightboxTrace][PageManager] modalManager.pop result', {
closeModal,
pathname: window.location.pathname,
state: e.state
})
if (closeModal) {
ignorePopStateRef.current = true
logger.info('[LightboxTrace][PageManager] modal popped, forcing history.forward')
window.history.forward()
return
}

8
src/components/ImageGallery/index.tsx

@ -108,7 +108,13 @@ export default function ImageGallery({ @@ -108,7 +108,13 @@ export default function ImageGallery({
<div className={cn(displayImages.length === 1 ? 'w-fit max-w-[400px]' : 'w-full', className)}>
{imageContent}
{createPortal(
<div onClick={(e) => e.stopPropagation()}>
<div
data-lightbox-overlay
onClick={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onTouchStart={(e) => e.stopPropagation()}
>
<Lightbox
index={index}
slides={(() => {

77
src/components/ImageWithLightbox/index.tsx

@ -1,10 +1,9 @@ @@ -1,10 +1,9 @@
import { randomString } from '@/lib/random'
import { cn } from '@/lib/utils'
import logger from '@/lib/logger'
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
import modalManager from '@/services/modal-manager.service'
import { TImetaInfo } from '@/types'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { createPortal } from 'react-dom'
import { preferBlossomPrimalDisplayUrl } from '@/lib/url'
import { useTranslation } from 'react-i18next'
@ -38,66 +37,19 @@ export default function ImageWithLightbox({ @@ -38,66 +37,19 @@ export default function ImageWithLightbox({
}
}, [autoLoadMedia])
const logLightboxEvent = useCallback((stage: string, details?: Record<string, unknown>) => {
logger.info('[LightboxTrace]', {
stage,
id,
imageUrl: image.url,
index,
pathname: window.location.pathname,
search: window.location.search,
hash: window.location.hash,
...details
})
}, [id, image.url, index])
useEffect(() => {
if (index >= 0) {
logLightboxEvent('modal-register')
modalManager.register(id, () => {
logLightboxEvent('modal-callback-close')
setIndex(-1)
})
} else {
logLightboxEvent('modal-unregister')
modalManager.unregister(id)
}
}, [id, index, logLightboxEvent])
useEffect(() => {
if (index < 0) return
const onCaptureKeydown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
logLightboxEvent('escape-keydown-capture', {
defaultPrevented: event.defaultPrevented,
eventPhase: event.eventPhase
})
}
}
const onPopState = (event: PopStateEvent) => {
logLightboxEvent('window-popstate-while-open', {
hasState: !!event.state,
state: event.state
})
}
window.addEventListener('keydown', onCaptureKeydown, true)
window.addEventListener('popstate', onPopState)
return () => {
window.removeEventListener('keydown', onCaptureKeydown, true)
window.removeEventListener('popstate', onPopState)
}
}, [index, logLightboxEvent])
}, [id, index])
const handlePhotoClick = (event: React.MouseEvent) => {
logLightboxEvent('thumbnail-click', {
defaultPreventedBefore: event.defaultPrevented
})
event.stopPropagation()
event.preventDefault()
logLightboxEvent('set-open-index')
setIndex(0)
}
@ -132,22 +84,10 @@ export default function ImageWithLightbox({ @@ -132,22 +84,10 @@ export default function ImageWithLightbox({
{createPortal(
<div
data-lightbox-overlay
onClick={(e) => {
logLightboxEvent('overlay-click', { target: (e.target as HTMLElement)?.tagName })
e.stopPropagation()
}}
onPointerDown={(e) => {
logLightboxEvent('overlay-pointerdown', { target: (e.target as HTMLElement)?.tagName })
e.stopPropagation()
}}
onMouseDown={(e) => {
logLightboxEvent('overlay-mousedown', { target: (e.target as HTMLElement)?.tagName })
e.stopPropagation()
}}
onTouchStart={(e) => {
logLightboxEvent('overlay-touchstart', { target: (e.target as HTMLElement)?.tagName })
e.stopPropagation()
}}
onClick={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onTouchStart={(e) => e.stopPropagation()}
>
<Lightbox
index={index}
@ -160,10 +100,7 @@ export default function ImageWithLightbox({ @@ -160,10 +100,7 @@ export default function ImageWithLightbox({
]}
plugins={[Zoom, Captions]}
open={index >= 0}
close={() => {
logLightboxEvent('lightbox-close-callback')
setIndex(-1)
}}
close={() => setIndex(-1)}
controller={{
closeOnBackdropClick: false,
closeOnPullUp: true,

8
src/components/Note/AsciidocArticle/AsciidocArticle.tsx

@ -2122,7 +2122,13 @@ export default function AsciidocArticle({ @@ -2122,7 +2122,13 @@ export default function AsciidocArticle({
{/* Image gallery lightbox */}
{allImages.length > 0 && createPortal(
<div onClick={(e) => e.stopPropagation()}>
<div
data-lightbox-overlay
onClick={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onTouchStart={(e) => e.stopPropagation()}
>
<Lightbox
index={lightboxIndex}
slides={allImages.map(({ url, alt }) => ({

23
src/components/NoteDrawer/index.tsx

@ -3,7 +3,6 @@ import { Sheet, SheetContent } from '@/components/ui/sheet' @@ -3,7 +3,6 @@ import { Sheet, SheetContent } from '@/components/ui/sheet'
import NotePage from '@/pages/secondary/NotePage'
import { useSecondaryPage } from '@/PageManager'
import type { Event } from 'nostr-tools'
import logger from '@/lib/logger'
interface NoteDrawerProps {
open: boolean
@ -47,20 +46,26 @@ export default function NoteDrawer({ open, onOpenChange, noteId, initialEvent }: @@ -47,20 +46,26 @@ export default function NoteDrawer({ open, onOpenChange, noteId, initialEvent }:
return (
<Sheet
open={open}
onOpenChange={(nextOpen) => {
logger.info('[LightboxTrace][NoteDrawer] onOpenChange', {
currentOpen: open,
nextOpen,
noteId: displayNoteId
})
onOpenChange(nextOpen)
}}
onOpenChange={onOpenChange}
registerWithModalManager={false}
>
<SheetContent
side="right"
className="w-full sm:max-w-[1042px] overflow-y-auto p-0"
hideClose
onPointerDownOutside={(e) => {
// Prevent the drawer from closing when the user interacts with a lightbox portal.
// The lightbox renders into document.body (outside the Sheet DOM) so Radix UI
// would otherwise treat every click inside the lightbox as "outside" the drawer.
if (document.body.classList.contains('yarl__no_scroll')) {
e.preventDefault()
}
}}
onInteractOutside={(e) => {
if (document.body.classList.contains('yarl__no_scroll')) {
e.preventDefault()
}
}}
>
<div className="min-h-full">
<NotePage

6
src/index.css

@ -605,6 +605,12 @@ @@ -605,6 +605,12 @@
max-height: 100% !important;
max-width: 100% !important;
}
/* Radix UI modal dialogs (e.g. NoteDrawer Sheet) set pointer-events:none on document.body
to block background interaction. Override this for the lightbox portal so it remains
fully interactive when opened from inside a Radix modal. */
.yarl__portal {
pointer-events: auto;
}
@media (max-width: 768px) {
.yarl__slide_captions_container {
padding: 12px;

41
src/services/client.service.ts

@ -285,14 +285,6 @@ class ClientService extends EventTarget { @@ -285,14 +285,6 @@ class ClientService extends EventTarget {
params?: { connectionTimeout?: number; abort?: AbortSignal }
) => {
const n = normalizeUrl(url) || url
// ── DIAGNOSTIC: catch any local-network WS attempt so we can trace its origin ──
if (isLocalNetworkUrl(n)) {
logger.warn('[DIAG] pool.ensureRelay called with LOCAL-NETWORK WS URL', {
url,
normalizedUrl: n,
stack: new Error('stack').stack?.split('\n').slice(1, 8).join(' | ')
})
}
const base = params?.connectionTimeout ?? RELAY_POOL_CONNECTION_TIMEOUT_MS
const connectionTimeout = READ_ONLY_RELAY_CONNECT_BOOST_URLS.has(n)
? Math.max(base, RELAY_READ_ONLY_POOL_CONNECT_TIMEOUT_MS)
@ -1033,14 +1025,6 @@ class ClientService extends EventTarget { @@ -1033,14 +1025,6 @@ class ClientService extends EventTarget {
private recordSessionRelayFailure(url: string) {
const n = normalizeAnyRelayUrl(url) || url
if (!n) return
// ── DIAGNOSTIC: trace who is recording failures for local-network relays ──
if (isLocalNetworkUrl(n)) {
logger.warn('[DIAG] recordSessionRelayFailure for LOCAL-NETWORK relay', {
url,
normalizedUrl: n,
stack: new Error('stack').stack?.split('\n').slice(1, 8).join(' | ')
})
}
const prev = this.publishStrikeCount.get(n) ?? 0
if (prev >= ClientService.SESSION_RELAY_FAILURE_STRIKE_THRESHOLD) {
return
@ -1811,9 +1795,9 @@ class ClientService extends EventTarget { @@ -1811,9 +1795,9 @@ class ClientService extends EventTarget {
eventIdSet.add(evt.id)
events.push(evt)
})
events = events
.sort((a, b) => b.created_at - a.created_at)
.slice(0, mergedTimelineLimit)
events = needSort
? events.sort((a, b) => b.created_at - a.created_at).slice(0, mergedTimelineLimit)
: events.slice(0, mergedTimelineLimit)
eventIdSet = new Set(events.map((evt) => evt.id))
scheduleOuterFlush(!!_eosed)
@ -1943,15 +1927,6 @@ class ClientService extends EventTarget { @@ -1943,15 +1927,6 @@ class ClientService extends EventTarget {
relayReqLog?: { groupId?: string; onBatchEnd?: (rows: RelayOpTerminalRow[]) => void }
) {
const originalDedupedRelays = Array.from(new Set(urls))
// ── DIAGNOSTIC: trace local-network URLs entering subscribe() ──
const localInSubscribe = originalDedupedRelays.filter((u) => isLocalNetworkUrl(normalizeAnyRelayUrl(u) || u))
if (localInSubscribe.length > 0) {
logger.warn('[DIAG] subscribe() received LOCAL-NETWORK relay URLs', {
localUrls: localInSubscribe,
allUrls: originalDedupedRelays,
stack: new Error('stack').stack?.split('\n').slice(1, 8).join(' | ')
})
}
let relays = originalDedupedRelays.filter((url) => !isHttpRelayUrl(url))
const filters = sanitizeSubscribeFiltersBeforeReq(filter)
if (filters.length === 0) {
@ -2309,16 +2284,6 @@ class ClientService extends EventTarget { @@ -2309,16 +2284,6 @@ class ClientService extends EventTarget {
} = {}
) {
let relays = Array.from(new Set(urls))
// ── DIAGNOSTIC: trace local-network URLs entering _subscribeTimeline ──
const localInTimeline = relays.filter((u) => isLocalNetworkUrl(normalizeAnyRelayUrl(u) || u))
if (localInTimeline.length > 0) {
logger.warn('[DIAG] _subscribeTimeline received LOCAL-NETWORK relay URLs', {
localUrls: localInTimeline,
allUrls: relays,
httpOnes: relays.filter((u) => isHttpRelayUrl(u)),
stack: new Error('stack').stack?.split('\n').slice(1, 10).join(' | ')
})
}
if (relayFiltersUseCapitalLetterTagKeys(filter as Filter)) {
relays = relayUrlsStripExtendedTagReqBlocked(relays)
if (relays.length === 0) {

24
src/services/modal-manager.service.ts

@ -1,5 +1,3 @@ @@ -1,5 +1,3 @@
import logger from '@/lib/logger'
class ModalManagerService {
static instance: ModalManagerService
@ -15,19 +13,10 @@ class ModalManagerService { @@ -15,19 +13,10 @@ class ModalManagerService {
register(id: string, cb: () => void) {
const modal = this.modals.find((m) => m.id === id)
if (modal) {
// already registered, update callback
modal.cb = cb
logger.info('[LightboxTrace][ModalManager] updated modal callback', {
id,
modalCount: this.modals.length
})
return
}
this.modals.push({ id, cb })
logger.info('[LightboxTrace][ModalManager] register', {
id,
modalCount: this.modals.length
})
}
unregister(id: string) {
@ -36,24 +25,13 @@ class ModalManagerService { @@ -36,24 +25,13 @@ class ModalManagerService {
modal.cb()
this.modals = this.modals.filter((m) => m.id !== id)
logger.info('[LightboxTrace][ModalManager] unregister', {
id,
modalCount: this.modals.length
})
}
pop() {
const modal = this.modals.pop()
if (!modal) {
logger.info('[LightboxTrace][ModalManager] pop noop', { modalCount: this.modals.length })
return false
}
if (!modal) return false
modal.cb()
logger.info('[LightboxTrace][ModalManager] pop close', {
id: modal.id,
modalCount: this.modals.length
})
return true
}
}

Loading…
Cancel
Save