Browse Source

bug-fixes

imwald
Silberengel 4 weeks ago
parent
commit
1c7c3b701c
  1. 17
      src/components/FavoriteRelaysFeedPicker/index.tsx
  2. 34
      src/components/NoteOptions/useMenuActions.tsx
  3. 25
      src/constants.ts
  4. 28
      src/hooks/useContainerWidth.ts

17
src/components/FavoriteRelaysFeedPicker/index.tsx

@ -14,14 +14,17 @@ import { toRelaySettings } from '@/lib/link' @@ -14,14 +14,17 @@ import { toRelaySettings } from '@/lib/link'
import { normalizeUrl, simplifyUrl } from '@/lib/url'
import { buildWispTrendingNotesRelayUrl } from '@/lib/wisp-trending-relay'
import { cn } from '@/lib/utils'
import { useContainerWidth } from '@/hooks/useContainerWidth'
import { useSecondaryPage } from '@/PageManager'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { useFeed } from '@/providers/FeedProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { SquarePen } from 'lucide-react'
import { useMemo } from 'react'
import { useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
/** Chips → dropdown below this container width (px). Matches Tailwind `sm` breakpoint. */
const NARROW_THRESHOLD = 640
const ALL_FAVORITES_VALUE = '__all_favorites__'
function relaySetToSelectValue(id: string) {
@ -36,7 +39,11 @@ function selectValueToRelaySetId(v: string) { @@ -36,7 +39,11 @@ function selectValueToRelaySetId(v: string) {
/** Top-of-feed control: all favorites, Wisp trending (nostrarchives), relay sets, then single relays. */
export default function FavoriteRelaysFeedPicker() {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize()
const containerRef = useRef<HTMLDivElement>(null)
const containerWidth = useContainerWidth(containerRef)
// True when the component's own container is narrow — covers both mobile viewports
// and the left pane in double-pane desktop mode.
const isNarrow = containerWidth !== undefined ? containerWidth < NARROW_THRESHOLD : false
const { push } = useSecondaryPage()
const { favoriteRelays, blockedRelays, relaySets } = useFavoriteRelays()
const { feedInfo, switchFeed } = useFeed()
@ -166,9 +173,10 @@ export default function FavoriteRelaysFeedPicker() { @@ -166,9 +173,10 @@ export default function FavoriteRelaysFeedPicker() {
</Button>
)
if (isSmallScreen) {
if (isNarrow) {
return (
<div
ref={containerRef}
className="flex w-full min-w-0 items-center gap-1.5 border-b border-border/80 bg-background px-2 py-1.5"
aria-label={t('Favorite Relays')}
>
@ -234,6 +242,7 @@ export default function FavoriteRelaysFeedPicker() { @@ -234,6 +242,7 @@ export default function FavoriteRelaysFeedPicker() {
return (
<div
ref={containerRef}
className="flex w-full min-w-0 items-center gap-1.5 border-b border-border/80 bg-background px-2 py-1.5"
role="toolbar"
aria-label={t('Favorite Relays')}

34
src/components/NoteOptions/useMenuActions.tsx

@ -50,7 +50,7 @@ import { @@ -50,7 +50,7 @@ import {
} from 'lucide-react'
import { Event, kinds } from 'nostr-tools'
import { nip19 } from 'nostr-tools'
import { useMemo, useState, useEffect, useContext } from 'react'
import { useMemo, useState, useEffect, useRef, useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import RelayIcon from '../RelayIcon'
@ -139,7 +139,16 @@ export function useMenuActions({ @@ -139,7 +139,16 @@ export function useMenuActions({
// Check if event is pinned
const [isPinned, setIsPinned] = useState(false)
// Keep refs so the effect can read the latest relay lists without making them
// part of the dependency array. Including live array references as deps causes
// an infinite loop: relay fetch → NoteList re-render → new array refs →
// effect re-fires for every visible note → relay fetch → …
const currentBrowsingRelayUrlsRef = useRef(currentBrowsingRelayUrls)
currentBrowsingRelayUrlsRef.current = currentBrowsingRelayUrls
const favoriteRelaysRef = useRef(favoriteRelays)
favoriteRelaysRef.current = favoriteRelays
useEffect(() => {
const checkIfPinned = async () => {
if (!pubkey) {
@ -147,21 +156,15 @@ export function useMenuActions({ @@ -147,21 +156,15 @@ export function useMenuActions({
return
}
try {
// Build comprehensive relay list for pin status check
const allRelays = [
...(currentBrowsingRelayUrls || []),
...(favoriteRelays || []),
...FAST_READ_RELAY_URLS,
...(currentBrowsingRelayUrlsRef.current || []),
...(favoriteRelaysRef.current || []),
...FAST_READ_RELAY_URLS,
...FAST_WRITE_RELAY_URLS
]
const normalizedRelays = allRelays
.map(url => normalizeUrl(url))
.filter((url): url is string => !!url)
const comprehensiveRelays = Array.from(new Set(normalizedRelays))
const comprehensiveRelays = Array.from(
new Set(allRelays.map(url => normalizeUrl(url)).filter((url): url is string => !!url))
)
const pinListEvent = await fetchNewestPinListForPubkey(pubkey, comprehensiveRelays)
if (pinListEvent) {
setIsPinned(isEventInPinList(pinListEvent, event))
@ -174,7 +177,10 @@ export function useMenuActions({ @@ -174,7 +177,10 @@ export function useMenuActions({
}
}
checkIfPinned()
}, [pubkey, event.id, currentBrowsingRelayUrls, favoriteRelays])
// Only re-run when the user or the specific event changes, not on relay list
// reference churn (relay arrays are read via refs above).
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pubkey, event.id])
const handlePinNote = async () => {
if (!pubkey) return

25
src/constants.ts

@ -31,22 +31,35 @@ export const READ_ALOUD_TTS_URL = @@ -31,22 +31,35 @@ export const READ_ALOUD_TTS_URL =
export const HIVETALK_BASE_URL =
(import.meta.env.VITE_HIVETALK_BASE_URL as string | undefined) ?? 'https://vanilla.hivetalk.org'
/**
* Stable reference to this module's URL at load time.
* `import.meta.url` alone is left untouched by Vite (only `new URL(path, import.meta.url)`
* with a literal/template path gets transformed into a static asset map).
* In the Electron build (dist/assets/*.js) this is something like:
* file:///path/to/dist/assets/index-abc.js
*/
const _moduleHref: string = import.meta.url
/**
* URL for a file from `public/` (banner, favicon, payto logos, etc.).
* Uses Vite `base`: `/` on the web, `./` when built for Electron (`loadFile` + `file:`).
*
* Electron packaged builds use `file:` + client-side history paths like `/notes/…`, which replace
* the document URL with `file:///notes/…`. Relative `BASE_URL` links would then resolve next to that
* bogus path and 404. Resolve from this module's emitted chunk (`dist/assets/*.js`) instead.
* One `..` reaches `dist/` (sibling of `assets/`); `../..` would miss `public/` copies and 404.
* bogus path and 404.
*
* For `file:` we derive the `dist/` root from the chunk's own URL. The chunk lives at
* `dist/assets/*.js`, so `/assets/` marks the boundary: everything before it is the dist root.
* Vite would transform `new URL(\`../${dynamic}\`, import.meta.url)` into a static glob map that
* does NOT include `public/` copies, so we must NOT use that pattern here.
*/
export function publicAssetUrl(assetPath: string): string {
const trimmed = assetPath.replace(/^\//, '')
if (typeof window !== 'undefined' && window.location.protocol === 'file:') {
try {
return new URL(`../${trimmed}`, import.meta.url).href
} catch {
// fall through to BASE_URL
const assetsIdx = _moduleHref.lastIndexOf('/assets/')
if (assetsIdx !== -1) {
// e.g. "file:///path/to/dist/" + "banner.png"
return _moduleHref.slice(0, assetsIdx + 1) + trimmed
}
}
return `${import.meta.env.BASE_URL}${trimmed}`

28
src/hooks/useContainerWidth.ts

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
import { RefObject, useEffect, useState } from 'react'
/**
* Tracks the rendered width of `ref`'s element via ResizeObserver.
* Returns `undefined` until the first measurement fires.
* Use this when you need a component to respond to its *own* container width
* rather than the viewport width (e.g. inside a split-pane layout where
* `isSmallScreen` is still `false` but the column is narrow).
*/
export function useContainerWidth(ref: RefObject<Element | null>): number | undefined {
const [width, setWidth] = useState<number | undefined>(undefined)
useEffect(() => {
const el = ref.current
if (!el) return
const observer = new ResizeObserver((entries) => {
const entry = entries[0]
if (entry) setWidth(entry.contentRect.width)
})
observer.observe(el)
// Initialise synchronously so there's no render flash
setWidth(el.getBoundingClientRect().width)
return () => observer.disconnect()
}, [ref])
return width
}
Loading…
Cancel
Save