Browse Source

bug-fixes

imwald
Silberengel 1 week ago
parent
commit
b49acd2057
  1. 9
      src/components/Library/LibraryPublicationGrid.tsx
  2. 17
      src/hooks/usePanelMode.test.ts
  3. 26
      src/hooks/usePanelMode.ts
  4. 28
      src/lib/library-publication-index.test.ts
  5. 26
      src/lib/library-publication-index.ts

9
src/components/Library/LibraryPublicationGrid.tsx

@ -1,8 +1,10 @@ @@ -1,8 +1,10 @@
import PublicationCard from '@/components/Note/PublicationCard'
import { Skeleton } from '@/components/ui/skeleton'
import { libraryPublicationGridColumnClass, usePanelMode } from '@/hooks/usePanelMode'
import type { LibraryPublicationEntry } from '@/lib/library-publication-index'
import { isBooklistNip32Label } from '@/lib/nip32-label'
import { cn } from '@/lib/utils'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { BookOpen, Highlighter, MessageSquare, Tag } from 'lucide-react'
import { useTranslation } from 'react-i18next'
@ -95,10 +97,13 @@ export default function LibraryPublicationGrid({ @@ -95,10 +97,13 @@ export default function LibraryPublicationGrid({
emptyMessage?: string
}) {
const { t } = useTranslation()
const { isSmallScreen } = useScreenSize()
const panelMode = usePanelMode()
const gridCols = libraryPublicationGridColumnClass(isSmallScreen, panelMode)
if (loading) {
return (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
<div className={cn('grid gap-4', gridCols)}>
{Array.from({ length: 6 }).map((_, i) => (
<Skeleton key={i} className="h-48 w-full rounded-lg" />
))}
@ -115,7 +120,7 @@ export default function LibraryPublicationGrid({ @@ -115,7 +120,7 @@ export default function LibraryPublicationGrid({
}
return (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
<div className={cn('grid gap-4', gridCols)}>
{entries.map((entry) => (
<div
key={entry.event.id}

17
src/hooks/usePanelMode.test.ts

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
import { describe, expect, it } from 'vitest'
import { libraryPublicationGridColumnClass } from '@/hooks/usePanelMode'
describe('libraryPublicationGridColumnClass', () => {
it('uses 1 column on mobile', () => {
expect(libraryPublicationGridColumnClass(true, 'single')).toBe('grid-cols-1')
expect(libraryPublicationGridColumnClass(true, 'double')).toBe('grid-cols-1')
})
it('uses 2 columns in double-pane desktop', () => {
expect(libraryPublicationGridColumnClass(false, 'double')).toBe('grid-cols-2')
})
it('uses 3 columns in single-pane desktop', () => {
expect(libraryPublicationGridColumnClass(false, 'single')).toBe('grid-cols-3')
})
})

26
src/hooks/usePanelMode.ts

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
import storage from '@/services/local-storage.service'
import { useEffect, useState } from 'react'
export function usePanelMode(): 'single' | 'double' {
const [panelMode, setPanelMode] = useState<'single' | 'double'>(() => storage.getPanelMode())
useEffect(() => {
const onPanelMode = (ev: Event) => {
const mode = (ev as CustomEvent<{ mode: 'single' | 'double' }>).detail?.mode
if (mode === 'single' || mode === 'double') setPanelMode(mode)
}
window.addEventListener('panelModeChanged', onPanelMode)
return () => window.removeEventListener('panelModeChanged', onPanelMode)
}, [])
return panelMode
}
export function libraryPublicationGridColumnClass(
isSmallScreen: boolean,
panelMode: 'single' | 'double'
): string {
if (isSmallScreen) return 'grid-cols-1'
if (panelMode === 'double') return 'grid-cols-2'
return 'grid-cols-3'
}

28
src/lib/library-publication-index.test.ts

@ -253,6 +253,34 @@ describe('library-publication-index', () => { @@ -253,6 +253,34 @@ describe('library-publication-index', () => {
expect(picked.every((e) => e.engagementCount === 0)).toBe(true)
})
it('pickLibraryPublicationEntries merges engaged roots with recent feed', () => {
const engagedRoot = indexEvent('engaged', [`30041:${PK}:a`], '1'.repeat(64))
engagedRoot.created_at = 5
const recentRoots = Array.from({ length: 5 }, (_, i) => {
const ev = indexEvent(`recent-${i}`, [`30041:${PK}:r-${i}`], String(i + 2).padEnd(64, '0').slice(0, 64))
ev.created_at = 100 + i
return ev
})
const roots = [engagedRoot, ...recentRoots]
const indexByAddress = buildIndexByAddress(roots)
const label: Event = {
id: '4'.repeat(64),
kind: ExtendedKind.LABEL,
pubkey: 'f'.repeat(64),
created_at: 50,
content: '',
tags: [['L', 'ugc'], ['l', 'booklist', 'ugc'], ['e', engagedRoot.id]],
sig: 'e'.repeat(128)
}
const engagement = buildEngagementMapsFromEvents([label], [], [])
const picked = pickLibraryPublicationEntries(roots, indexByAddress, engagement)
expect(picked.length).toBeGreaterThan(1)
expect(picked.some((e) => e.event.id === engagedRoot.id && e.hasBooklistLabel)).toBe(true)
expect(picked.some((e) => e.event.id === recentRoots[4].id)).toBe(true)
})
it('buildRecentPublicationEntries caps at limit', () => {
const roots = Array.from({ length: 12 }, (_, i) => {
const ev = indexEvent(`book-${i}`, [`30041:${PK}:ch-${i}`], String(i).padEnd(64, '0').slice(0, 64))

26
src/lib/library-publication-index.ts

@ -700,18 +700,30 @@ export function buildRecentPublicationEntries( @@ -700,18 +700,30 @@ export function buildRecentPublicationEntries(
.map((event) => buildLibraryPublicationEntry(event, indexByAddress, engagement))
}
/** Engaged publications first; when none match, show the newest top-level indexes (still enriched). */
/** Engaged publications first, then fill with newest top-level indexes up to {@link LIBRARY_RECENT_FALLBACK_LIMIT}. */
export function pickLibraryPublicationEntries(
roots: Event[],
indexByAddress: Map<string, Event>,
engagement: PublicationEngagementMaps
): LibraryPublicationEntry[] {
const enriched = getTopLevelIndexEvents(roots).map((root) =>
buildLibraryPublicationEntry(root, indexByAddress, engagement)
)
const engaged = enriched.filter((entry) => entry.hasLabel || entry.hasComment || entry.hasHighlight)
if (engaged.length > 0) return sortLibraryPublications(engaged)
return sortLibraryPublications(buildRecentPublicationEntries(roots, indexByAddress, engagement))
const engaged = filterEngagedPublications(roots, indexByAddress, engagement)
const recent = buildRecentPublicationEntries(roots, indexByAddress, engagement)
if (engaged.length === 0) return sortLibraryPublications(recent)
const seen = new Set<string>()
const merged: LibraryPublicationEntry[] = []
for (const entry of sortLibraryPublications(engaged)) {
if (seen.has(entry.event.id)) continue
seen.add(entry.event.id)
merged.push(entry)
}
for (const entry of recent) {
if (merged.length >= LIBRARY_RECENT_FALLBACK_LIMIT) break
if (seen.has(entry.event.id)) continue
seen.add(entry.event.id)
merged.push(entry)
}
return merged
}
export function sortLibraryPublications(entries: LibraryPublicationEntry[]): LibraryPublicationEntry[] {

Loading…
Cancel
Save