Skip to content

Commit

Permalink
Merge pull request #156 from cdrxiv/Shane98c/pagination
Browse files Browse the repository at this point in the history
noscript functionality and seo improvements
  • Loading branch information
Shane98c authored Feb 26, 2025
2 parents cb97c26 + 8264cf9 commit d574673
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 179 deletions.
42 changes: 2 additions & 40 deletions app/landing-page.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,15 @@
'use client'

import React, { useState } from 'react'
import { useSearchParams } from 'next/navigation'
import { Box, Divider, Flex } from 'theme-ui'
import { Column, Row, Link } from '../components'
import Topics from './topics'
import ViewSelector from './view-selector'

interface LandingPageProps {
children?: React.ReactNode
}

type ViewType = 'grid' | 'list'

const LandingPage: React.FC<LandingPageProps> = ({ children }) => {
const searchParams = useSearchParams()
const [currentView, setCurrentView] = useState<ViewType>(
() => (searchParams.get('view') as ViewType) || 'grid',
)

const handleViewChange = (view: ViewType) => {
setCurrentView(view)
const params = new URLSearchParams(searchParams.toString())
params.set('view', view)
window.history.replaceState(null, '', `?${params.toString()}`)
}

return (
<>
<Row columns={[6, 8, 12, 12]} sx={{ mb: [4, 4, 8, 8] }}>
Expand Down Expand Up @@ -72,30 +57,7 @@ const LandingPage: React.FC<LandingPageProps> = ({ children }) => {
<Box as='h2' sx={{ variant: 'text.monoCaps' }}>
Recent preprints
</Box>
<Flex role='listbox' aria-label='View options' sx={{ gap: 3 }}>
<Link
role='option'
aria-selected={currentView === 'grid'}
onClick={() => {
handleViewChange('grid')
}}
selected={currentView === 'grid'}
hoverEffect={true}
>
Grid
</Link>
<Link
role='option'
aria-selected={currentView === 'list'}
onClick={() => {
handleViewChange('list')
}}
selected={currentView === 'list'}
hoverEffect={true}
>
List
</Link>
</Flex>
<ViewSelector />
</Flex>
</Column>
</Row>
Expand Down
15 changes: 0 additions & 15 deletions app/loading-wrapper.tsx

This file was deleted.

51 changes: 34 additions & 17 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,49 @@
import { Suspense } from 'react'
import LandingPage from './landing-page'
import PreprintsView from './preprints-view'
import LoadingWrapper from './loading-wrapper'
import { fetchWithAlerting } from '../actions/server-utils'

interface HomeProps {
searchParams: { [key: string]: string | string[] | undefined }
searchParams: { [key: string]: string | undefined }
}

const Preprints = async ({ subject }: { subject: string | undefined }) => {
let url = `${process.env.NEXT_PUBLIC_JANEWAY_URL}/api/published_preprints/?limit=48`
const preprintsPerPage = 48

const Home = async ({ searchParams }: HomeProps) => {
const subject = searchParams.subject
const page = searchParams.page ? parseInt(searchParams.page) : 1
const offset = (page - 1) * preprintsPerPage
let url = `${process.env.NEXT_PUBLIC_JANEWAY_URL}/api/published_preprints/?limit=${preprintsPerPage}&offset=${offset}`
if (subject) {
url += `&subject=${subject}`
}
const res = await fetchWithAlerting(url, { next: { revalidate: 180 } })
const preprints = await res.json()
const results = preprints.results || []
return (
<PreprintsView preprints={results} nextPage={preprints.next as string} />
)
}

const Home = async ({ searchParams }: HomeProps) => {
const subject = searchParams.subject as string | undefined
let results = []
let nextPage = null
let totalCount = 0
let error = false

try {
const res = await fetchWithAlerting(url, { next: { revalidate: 180 } })
const preprints = await res.json()
results = preprints.results || []
nextPage = preprints.next
totalCount = preprints.count
} catch (err) {
error = true
}

return (
<LandingPage>
<Suspense fallback={<LoadingWrapper />}>
<Preprints subject={subject} />
</Suspense>
{error ? (
<div style={{ textAlign: 'center' }}>Unable to load preprints.</div>
) : (
<PreprintsView
preprints={results}
nextPage={nextPage}
totalCount={totalCount}
preprintsPerPage={preprintsPerPage}
/>
)}
</LandingPage>
)
}
Expand Down
90 changes: 90 additions & 0 deletions app/pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useSearchParams } from 'next/navigation'
import { Box, Flex } from 'theme-ui'
import { Link } from '../components'

type PaginationProps = {
totalCount: number | undefined
itemsPerPage: number | undefined
currentPage: number
hasNextPage: boolean
}

const generatePaginationLinks = (
totalCount: number | undefined,
itemsPerPage: number | undefined,
currentPage: number,
) => {
if (!totalCount || !itemsPerPage) return []
const totalPages = Math.ceil(totalCount / itemsPerPage)
let pages = []

if (totalPages < 8) {
pages = Array(totalPages)
.fill(null)
.map((d, i) => i + 1)
} else if (currentPage <= 4) {
pages = [1, 2, 3, 4, 5, 6, '...', totalPages]
} else if (totalPages - currentPage <= 4) {
pages = [
1,
'...',
...Array(6)
.fill(null)
.map((d, i) => totalPages - 5 + i),
]
} else {
pages = [
1,
'...',
...Array(5)
.fill(null)
.map((d, i) => currentPage - 2 + i),
'...',
totalPages,
]
}
return pages
}

const Pagination = ({
totalCount,
itemsPerPage,
currentPage,
hasNextPage,
}: PaginationProps) => {
const paginationLinks = generatePaginationLinks(
totalCount,
itemsPerPage,
currentPage,
)
const searchParams = useSearchParams()

const createPageUrl = (page: number) =>
`?${new URLSearchParams({ ...Object.fromEntries(searchParams), page: page.toString() })}`

if (paginationLinks.length === 0 && !hasNextPage && currentPage <= 1) {
return null
}

return (
<Flex sx={{ justifyContent: 'center', gap: 2, mt: 5 }}>
{currentPage > 1 && (
<Link href={createPageUrl(currentPage - 1)}>Previous</Link>
)}
{paginationLinks.map((page, i) =>
page === '...' || page === currentPage ? (
<Box as='span' sx={{ mt: '1px' }} key={i}>
{page}
</Box>
) : (
<Link key={i} href={createPageUrl(page as number)}>
{page}
</Link>
),
)}
{hasNextPage && <Link href={createPageUrl(currentPage + 1)}>Next</Link>}
</Flex>
)
}

export default Pagination
46 changes: 33 additions & 13 deletions app/preprints-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,28 @@ import { useSearchParams } from 'next/navigation'

import { fetchPublishedPreprints } from '../actions'
import type { PublishedPreprint } from '../types/preprint'
import { Loading } from '../components'
import { Loading, Link } from '../components'
import Pagination from './pagination'
import List from './list'
import Grid from './grid'

type ViewType = 'grid' | 'list'
type Props = {
preprints: PublishedPreprint[]
nextPage?: string
totalCount?: number
preprintsPerPage?: number
}

const PreprintsView = (props: Props) => {
const sentinelRef = useRef<HTMLDivElement>()
const searchParams = useSearchParams()
const currentPage = searchParams.get('page')
const currentPageNum = currentPage ? parseInt(currentPage) : 1
const [nextPage, setNextPage] = useState(props.nextPage)
const [preprints, setPreprints] = useState(props.preprints)
const [isLoading, setIsLoading] = useState<boolean>(false)
const searchParams = useSearchParams()

const [currentView, setCurrentView] = useState<ViewType>(
() => (searchParams.get('view') as ViewType) || 'grid',
)

useEffect(() => {
const view = searchParams.get('view') as ViewType
if (view === 'grid' || view === 'list') {
setCurrentView(view)
}
}, [searchParams])
const currentView = (searchParams.get('view') as ViewType) || 'grid'

useEffect(() => {
setPreprints(props.preprints)
Expand Down Expand Up @@ -77,6 +72,20 @@ const PreprintsView = (props: Props) => {

return (
<>
{currentPage && currentPageNum > 1 && (
<Box
sx={{
margin: 'auto',
mb: 5,
width: 'fit-content',
}}
>
<Link href={`/?page=1&view=${currentView}`}>
View latest preprints
</Link>
</Box>
)}

{currentView === 'list' ? (
<List preprints={preprints} />
) : (
Expand All @@ -92,6 +101,17 @@ const PreprintsView = (props: Props) => {
{nextPage && (
<Box ref={sentinelRef} sx={{ height: '1px' }} /> // Invisible sentinel
)}

{(nextPage || currentPageNum > 1) && (
<noscript>
<Pagination
totalCount={props.totalCount}
itemsPerPage={props.preprintsPerPage}
currentPage={currentPageNum}
hasNextPage={!!nextPage}
/>
</noscript>
)}
</>
)
}
Expand Down
34 changes: 21 additions & 13 deletions app/search/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { Suspense } from 'react'

import ResultsWrapper from './results-wrapper'
import PreprintsView from '../preprints-view'
import LoadingWrapper from '../loading-wrapper'
import { fetchWithAlerting } from '../../actions/server-utils'

interface SearchProps {
Expand All @@ -13,24 +10,35 @@ export const metadata = {
title: 'Search – CDRXIV',
}

const preprintsPerPage = 48

const Search = async ({ searchParams }: SearchProps) => {
const { query: search, view, ...rest } = searchParams // map query -> search and omit view from params passed to Janeway
const params = new URLSearchParams({ search: search ?? '', ...rest })
const url = `${process.env.NEXT_PUBLIC_JANEWAY_URL}/api/published_preprints/?${params.toString()}&limit=48`
const page = searchParams.page ? parseInt(searchParams.page) : 1
const offset = (page - 1) * preprintsPerPage

const params = new URLSearchParams({
search: search ?? '',
...rest,
limit: preprintsPerPage.toString(),
offset: offset.toString(),
})

const url = `${process.env.NEXT_PUBLIC_JANEWAY_URL}/api/published_preprints/?${params.toString()}`

const res = await fetchWithAlerting(url, { next: { revalidate: 180 } })
const preprints = await res.json()
const results = preprints.results || []

return (
<Suspense key={search} fallback={<LoadingWrapper />}>
<ResultsWrapper count={preprints.count} search={search ?? ''}>
<PreprintsView
preprints={results}
nextPage={preprints.next as string}
/>
</ResultsWrapper>
</Suspense>
<ResultsWrapper count={preprints.count} search={search ?? ''}>
<PreprintsView
preprints={results}
nextPage={preprints.next}
totalCount={preprints.count}
preprintsPerPage={preprintsPerPage}
/>
</ResultsWrapper>
)
}

Expand Down
Loading

0 comments on commit d574673

Please sign in to comment.