load more (Feed, Author, Topic, Home update)
This commit is contained in:
parent
d491ae741e
commit
25fd5bf42f
|
@ -2,7 +2,7 @@ import type { Author } from '../../graphql/types.gen'
|
|||
import { AuthorCard } from './Card'
|
||||
import './Full.scss'
|
||||
|
||||
export default (props: { author: Author }) => {
|
||||
export const AuthorFull = (props: { author: Author }) => {
|
||||
return (
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
|
|
@ -21,7 +21,7 @@ interface BesideProps {
|
|||
iconButton?: boolean
|
||||
}
|
||||
|
||||
export default (props: BesideProps) => {
|
||||
export const Beside = (props: BesideProps) => {
|
||||
return (
|
||||
<Show when={!!props.beside?.slug && props.values?.length > 0}>
|
||||
<div class="floor floor--9">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { For, Suspense } from 'solid-js/web'
|
||||
import OneWide from './Row1'
|
||||
import Row2 from './Row2'
|
||||
import Row3 from './Row3'
|
||||
import { Row1 } from './Row1'
|
||||
import { Row2 } from './Row2'
|
||||
import { Row3 } from './Row3'
|
||||
import { shuffle } from '../../utils'
|
||||
import { createMemo, createSignal } from 'solid-js'
|
||||
import type { JSX } from 'solid-js'
|
||||
|
@ -10,7 +10,7 @@ import './List.scss'
|
|||
import { t } from '../../utils/intl'
|
||||
|
||||
export const Block6 = (props: { articles: Shout[] }) => {
|
||||
const dice = createMemo(() => shuffle([OneWide, Row2, Row3]))
|
||||
const dice = createMemo(() => shuffle([Row1, Row2, Row3]))
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Show } from 'solid-js'
|
|||
import type { Shout } from '../../graphql/types.gen'
|
||||
import { ArticleCard } from './Card'
|
||||
|
||||
export default (props: { article: Shout }) => (
|
||||
export const Row1 = (props: { article: Shout }) => (
|
||||
<Show when={!!props.article}>
|
||||
<div class="floor floor--one-article">
|
||||
<div class="wide-container row">
|
||||
|
|
|
@ -2,13 +2,14 @@ import { createComputed, createSignal, Show } from 'solid-js'
|
|||
import { For } from 'solid-js/web'
|
||||
import type { Shout } from '../../graphql/types.gen'
|
||||
import { ArticleCard } from './Card'
|
||||
|
||||
const x = [
|
||||
['6', '6'],
|
||||
['4', '8'],
|
||||
['8', '4']
|
||||
]
|
||||
|
||||
export default (props: { articles: Shout[] }) => {
|
||||
export const Row2 = (props: { articles: Shout[] }) => {
|
||||
const [y, setY] = createSignal(0)
|
||||
|
||||
createComputed(() => setY(Math.floor(Math.random() * x.length)))
|
||||
|
|
|
@ -2,7 +2,7 @@ import { For } from 'solid-js/web'
|
|||
import type { Shout } from '../../graphql/types.gen'
|
||||
import { ArticleCard } from './Card'
|
||||
|
||||
export default (props: { articles: Shout[]; header?: any }) => {
|
||||
export const Row3 = (props: { articles: Shout[]; header?: any }) => {
|
||||
return (
|
||||
<div class="floor">
|
||||
<div class="wide-container row">
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { MainLayout } from '../Layouts/MainLayout'
|
||||
import { AuthorView } from '../Views/Author'
|
||||
import { AuthorView, PRERENDERED_ARTICLES_COUNT } from '../Views/Author'
|
||||
import type { PageProps } from '../types'
|
||||
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
import { loadArticlesForAuthors, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { loadAuthorArticles, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { loadAuthor } from '../../stores/zine/authors'
|
||||
import { Loading } from '../Loading'
|
||||
|
@ -27,7 +27,7 @@ export const AuthorPage = (props: PageProps) => {
|
|||
return
|
||||
}
|
||||
|
||||
await loadArticlesForAuthors({ authorSlugs: [slug()] })
|
||||
await loadAuthorArticles({ authorSlug: slug(), limit: PRERENDERED_ARTICLES_COUNT })
|
||||
await loadAuthor({ slug: slug() })
|
||||
|
||||
setIsLoaded(true)
|
||||
|
|
|
@ -1,30 +1,14 @@
|
|||
import { MainLayout } from '../Layouts/MainLayout'
|
||||
import { FeedView } from '../Views/Feed'
|
||||
import type { PageProps } from '../types'
|
||||
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
import { loadRecentArticles, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { Loading } from '../Loading'
|
||||
|
||||
export const FeedPage = (props: PageProps) => {
|
||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.feedArticles))
|
||||
|
||||
onMount(async () => {
|
||||
if (isLoaded()) {
|
||||
return
|
||||
}
|
||||
|
||||
await loadRecentArticles({ limit: 50, offset: 0 })
|
||||
|
||||
setIsLoaded(true)
|
||||
})
|
||||
import { onCleanup } from 'solid-js'
|
||||
import { resetSortedArticles } from '../../stores/zine/articles'
|
||||
|
||||
export const FeedPage = () => {
|
||||
onCleanup(() => resetSortedArticles())
|
||||
|
||||
return (
|
||||
<MainLayout>
|
||||
<Show when={isLoaded()} fallback={<Loading />}>
|
||||
<FeedView articles={props.feedArticles} />
|
||||
</Show>
|
||||
<FeedView />
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { HomeView } from '../Views/Home'
|
||||
import { HomeView, PRERENDERED_ARTICLES_COUNT } from '../Views/Home'
|
||||
import { MainLayout } from '../Layouts/MainLayout'
|
||||
import type { PageProps } from '../types'
|
||||
import { createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
|
@ -14,7 +14,7 @@ export const HomePage = (props: PageProps) => {
|
|||
return
|
||||
}
|
||||
|
||||
await loadPublishedArticles({ limit: 5, offset: 0 })
|
||||
await loadPublishedArticles({ limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
|
||||
await loadRandomTopics()
|
||||
|
||||
setIsLoaded(true)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { MainLayout } from '../Layouts/MainLayout'
|
||||
import { TopicView } from '../Views/Topic'
|
||||
import { PRERENDERED_ARTICLES_COUNT, TopicView } from '../Views/Topic'
|
||||
import type { PageProps } from '../types'
|
||||
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||
import { loadArticlesForTopics, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { loadTopicArticles, resetSortedArticles } from '../../stores/zine/articles'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { loadTopic } from '../../stores/zine/topics'
|
||||
import { Loading } from '../Loading'
|
||||
|
@ -27,7 +27,7 @@ export const TopicPage = (props: PageProps) => {
|
|||
return
|
||||
}
|
||||
|
||||
await loadArticlesForTopics({ topicSlugs: [slug()] })
|
||||
await loadTopicArticles({ topicSlug: slug(), limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
|
||||
await loadTopic({ slug: slug() })
|
||||
|
||||
setIsLoaded(true)
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
import { Show, createMemo } from 'solid-js'
|
||||
import { Show, createMemo, createSignal, For, onMount } from 'solid-js'
|
||||
import type { Author, Shout } from '../../graphql/types.gen'
|
||||
import Row2 from '../Feed/Row2'
|
||||
import Row3 from '../Feed/Row3'
|
||||
// import Beside from '../Feed/Beside'
|
||||
import AuthorFull from '../Author/Full'
|
||||
import { Row2 } from '../Feed/Row2'
|
||||
import { Row3 } from '../Feed/Row3'
|
||||
import { AuthorFull } from '../Author/Full'
|
||||
import { t } from '../../utils/intl'
|
||||
import { useAuthorsStore } from '../../stores/zine/authors'
|
||||
import { useArticlesStore } from '../../stores/zine/articles'
|
||||
import { loadAuthorArticles, useArticlesStore } from '../../stores/zine/articles'
|
||||
|
||||
import '../../styles/Topic.scss'
|
||||
import { useTopicsStore } from '../../stores/zine/topics'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import Beside from '../Feed/Beside'
|
||||
import { Beside } from '../Feed/Beside'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
||||
import { splitToPages } from '../../utils/splitToPages'
|
||||
|
||||
// TODO: load reactions on client
|
||||
type AuthorProps = {
|
||||
|
@ -26,16 +27,37 @@ type AuthorPageSearchParams = {
|
|||
by: '' | 'viewed' | 'rating' | 'commented' | 'recent'
|
||||
}
|
||||
|
||||
export const PRERENDERED_ARTICLES_COUNT = 12
|
||||
const LOAD_MORE_PAGE_SIZE = 9 // Row3 + Row3 + Row3
|
||||
|
||||
export const AuthorView = (props: AuthorProps) => {
|
||||
const { sortedArticles } = useArticlesStore({
|
||||
sortedArticles: props.authorArticles
|
||||
})
|
||||
const { authorEntities } = useAuthorsStore({ authors: [props.author] })
|
||||
const { topicsByAuthor } = useTopicsStore()
|
||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
|
||||
const author = createMemo(() => authorEntities()[props.authorSlug])
|
||||
const { searchParams, changeSearchParam } = useRouter<AuthorPageSearchParams>()
|
||||
|
||||
const loadMore = async () => {
|
||||
saveScrollPosition()
|
||||
const { hasMore } = await loadAuthorArticles({
|
||||
authorSlug: author().slug,
|
||||
limit: LOAD_MORE_PAGE_SIZE,
|
||||
offset: sortedArticles().length
|
||||
})
|
||||
setIsLoadMoreButtonVisible(hasMore)
|
||||
restoreScrollPosition()
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
if (sortedArticles().length === PRERENDERED_ARTICLES_COUNT) {
|
||||
loadMore()
|
||||
}
|
||||
})
|
||||
|
||||
const title = createMemo(() => {
|
||||
const m = searchParams().by
|
||||
if (m === 'viewed') return t('Top viewed')
|
||||
|
@ -44,6 +66,10 @@ export const AuthorView = (props: AuthorProps) => {
|
|||
return t('Top recent')
|
||||
})
|
||||
|
||||
const pages = createMemo<Shout[][]>(() =>
|
||||
splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
|
||||
)
|
||||
|
||||
return (
|
||||
<div class="container author-page">
|
||||
<Show when={author()} fallback={<div class="center">{t('Loading')}</div>}>
|
||||
|
@ -83,8 +109,8 @@ export const AuthorView = (props: AuthorProps) => {
|
|||
</div>
|
||||
|
||||
<h3 class="col-12">{title()}</h3>
|
||||
|
||||
<div class="row">
|
||||
<Show when={sortedArticles().length > 0}>
|
||||
<Beside
|
||||
title={t('Topics which supported by author')}
|
||||
values={topicsByAuthor()[author().slug].slice(0, 5)}
|
||||
|
@ -96,18 +122,26 @@ export const AuthorView = (props: AuthorProps) => {
|
|||
iconButton={true}
|
||||
/>
|
||||
<Row3 articles={sortedArticles().slice(1, 4)} />
|
||||
|
||||
<Show when={sortedArticles().length > 4}>
|
||||
<Row2 articles={sortedArticles().slice(4, 6)} />
|
||||
</Show>
|
||||
|
||||
<Show when={sortedArticles().length > 6}>
|
||||
<Row3 articles={sortedArticles().slice(6, 9)} />
|
||||
</Show>
|
||||
|
||||
<Show when={sortedArticles().length > 9}>
|
||||
<Row3 articles={sortedArticles().slice(9, 12)} />
|
||||
</Show>
|
||||
|
||||
<For each={pages()}>
|
||||
{(page) => (
|
||||
<>
|
||||
<Row3 articles={page.slice(0, 3)} />
|
||||
<Row3 articles={page.slice(3, 6)} />
|
||||
<Row3 articles={page.slice(6, 9)} />
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
|
||||
<Show when={isLoadMoreButtonVisible()}>
|
||||
<p class="load-more-container">
|
||||
<button class="button" onClick={loadMore}>
|
||||
{t('Load more')}
|
||||
</button>
|
||||
</p>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { createMemo, For, Show } from 'solid-js'
|
||||
import type { Shout, Reaction } from '../../graphql/types.gen'
|
||||
import { createMemo, createSignal, For, onMount, Show } from 'solid-js'
|
||||
import '../../styles/Feed.scss'
|
||||
import stylesBeside from '../../components/Feed/Beside.module.scss'
|
||||
import { Icon } from '../Nav/Icon'
|
||||
|
@ -17,11 +16,6 @@ import { useAuthorsStore } from '../../stores/zine/authors'
|
|||
import { useTopicsStore } from '../../stores/zine/topics'
|
||||
import { useTopAuthorsStore } from '../../stores/zine/topAuthors'
|
||||
|
||||
interface FeedProps {
|
||||
articles: Shout[]
|
||||
reactions?: Reaction[]
|
||||
}
|
||||
|
||||
// const AUTHORSHIP_REACTIONS = [
|
||||
// ReactionKind.Accept,
|
||||
// ReactionKind.Reject,
|
||||
|
@ -29,9 +23,11 @@ interface FeedProps {
|
|||
// ReactionKind.Ask
|
||||
// ]
|
||||
|
||||
export const FeedView = (props: FeedProps) => {
|
||||
export const FEED_PAGE_SIZE = 20
|
||||
|
||||
export const FeedView = () => {
|
||||
// state
|
||||
const { sortedArticles } = useArticlesStore({ sortedArticles: props.articles })
|
||||
const { sortedArticles } = useArticlesStore()
|
||||
const reactions = useReactionsStore()
|
||||
const { sortedAuthors } = useAuthorsStore()
|
||||
const { topTopics } = useTopicsStore()
|
||||
|
@ -40,6 +36,8 @@ export const FeedView = (props: FeedProps) => {
|
|||
|
||||
const topReactions = createMemo(() => sortBy(reactions(), byCreated))
|
||||
|
||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
|
||||
// const expectingFocus = createMemo<Shout[]>(() => {
|
||||
// // 1 co-author notifications needs
|
||||
// // TODO: list of articles where you are co-author
|
||||
|
@ -53,13 +51,15 @@ export const FeedView = (props: FeedProps) => {
|
|||
// return []
|
||||
// })
|
||||
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
const loadMore = () => {
|
||||
// const limit = props.limit || 50
|
||||
// const offset = props.offset || 0
|
||||
// FIXME
|
||||
loadRecentArticles({ limit: 50, offset: 0 })
|
||||
const loadMore = async () => {
|
||||
const { hasMore } = await loadRecentArticles({ limit: FEED_PAGE_SIZE, offset: sortedArticles().length })
|
||||
setIsLoadMoreButtonVisible(hasMore)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
loadMore()
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="container feed">
|
||||
|
@ -113,10 +113,6 @@ export const FeedView = (props: FeedProps) => {
|
|||
{(article) => <ArticleCard article={article} settings={{ isFeedMode: true }} />}
|
||||
</For>
|
||||
</Show>
|
||||
|
||||
<p class="load-more-container">
|
||||
<button class="button">{t('Load more')}</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<aside class="col-md-3">
|
||||
|
@ -136,12 +132,13 @@ export const FeedView = (props: FeedProps) => {
|
|||
</Show>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<Show when={isLoadMoreButtonVisible()}>
|
||||
<p class="load-more-container">
|
||||
<button class="button" onClick={loadMore}>
|
||||
{t('Load more')}
|
||||
</button>
|
||||
</p>
|
||||
</Show>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { createMemo, For, onMount, Show } from 'solid-js'
|
||||
import { createMemo, createSignal, For, onMount, Show } from 'solid-js'
|
||||
import Banner from '../Discours/Banner'
|
||||
import { NavTopics } from '../Nav/Topics'
|
||||
import { Row5 } from '../Feed/Row5'
|
||||
import Row3 from '../Feed/Row3'
|
||||
import Row2 from '../Feed/Row2'
|
||||
import Row1 from '../Feed/Row1'
|
||||
import { Row3 } from '../Feed/Row3'
|
||||
import { Row2 } from '../Feed/Row2'
|
||||
import { Row1 } from '../Feed/Row1'
|
||||
import Hero from '../Discours/Hero'
|
||||
import Beside from '../Feed/Beside'
|
||||
import { Beside } from '../Feed/Beside'
|
||||
import RowShort from '../Feed/RowShort'
|
||||
import Slider from '../Feed/Slider'
|
||||
import Group from '../Feed/Group'
|
||||
|
@ -24,6 +24,7 @@ import {
|
|||
import { useTopAuthorsStore } from '../../stores/zine/topAuthors'
|
||||
import { locale } from '../../stores/ui'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
||||
import { splitToPages } from '../../utils/splitToPages'
|
||||
|
||||
const log = getLogger('home view')
|
||||
|
||||
|
@ -31,7 +32,8 @@ type HomeProps = {
|
|||
randomTopics: Topic[]
|
||||
recentPublishedArticles: Shout[]
|
||||
}
|
||||
const PRERENDERED_ARTICLES_COUNT = 5
|
||||
|
||||
export const PRERENDERED_ARTICLES_COUNT = 5
|
||||
const CLIENT_LOAD_ARTICLES_COUNT = 29
|
||||
const LOAD_MORE_PAGE_SIZE = 16 // Row1 + Row3 + Row2 + Beside (3 + 1) + Row1 + Row 2 + Row3
|
||||
|
||||
|
@ -49,14 +51,20 @@ export const HomeView = (props: HomeProps) => {
|
|||
const { randomTopics, topTopics } = useTopicsStore({
|
||||
randomTopics: props.randomTopics
|
||||
})
|
||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
|
||||
const { topAuthors } = useTopAuthorsStore()
|
||||
|
||||
onMount(() => {
|
||||
onMount(async () => {
|
||||
loadTopArticles()
|
||||
loadTopMonthArticles()
|
||||
if (sortedArticles().length < PRERENDERED_ARTICLES_COUNT + CLIENT_LOAD_ARTICLES_COUNT) {
|
||||
loadPublishedArticles({ limit: CLIENT_LOAD_ARTICLES_COUNT, offset: sortedArticles().length })
|
||||
const { hasMore } = await loadPublishedArticles({
|
||||
limit: CLIENT_LOAD_ARTICLES_COUNT,
|
||||
offset: sortedArticles().length
|
||||
})
|
||||
|
||||
setIsLoadMoreButtonVisible(hasMore)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -85,22 +93,23 @@ export const HomeView = (props: HomeProps) => {
|
|||
|
||||
const loadMore = async () => {
|
||||
saveScrollPosition()
|
||||
await loadPublishedArticles({ limit: LOAD_MORE_PAGE_SIZE, offset: sortedArticles().length })
|
||||
|
||||
const { hasMore } = await loadPublishedArticles({
|
||||
limit: LOAD_MORE_PAGE_SIZE,
|
||||
offset: sortedArticles().length
|
||||
})
|
||||
setIsLoadMoreButtonVisible(hasMore)
|
||||
|
||||
restoreScrollPosition()
|
||||
}
|
||||
|
||||
const pages = createMemo<Shout[][]>(() => {
|
||||
return sortedArticles()
|
||||
.slice(PRERENDERED_ARTICLES_COUNT + CLIENT_LOAD_ARTICLES_COUNT)
|
||||
.reduce((acc, article, index) => {
|
||||
if (index % LOAD_MORE_PAGE_SIZE === 0) {
|
||||
acc.push([])
|
||||
}
|
||||
|
||||
acc[acc.length - 1].push(article)
|
||||
return acc
|
||||
}, [] as Shout[][])
|
||||
})
|
||||
const pages = createMemo<Shout[][]>(() =>
|
||||
splitToPages(
|
||||
sortedArticles(),
|
||||
PRERENDERED_ARTICLES_COUNT + CLIENT_LOAD_ARTICLES_COUNT,
|
||||
LOAD_MORE_PAGE_SIZE
|
||||
)
|
||||
)
|
||||
|
||||
return (
|
||||
<Show when={locale() && sortedArticles().length > 0}>
|
||||
|
@ -173,11 +182,13 @@ export const HomeView = (props: HomeProps) => {
|
|||
)}
|
||||
</For>
|
||||
|
||||
<Show when={isLoadMoreButtonVisible()}>
|
||||
<p class="load-more-container">
|
||||
<button class="button" onClick={loadMore}>
|
||||
{t('Load more')}
|
||||
</button>
|
||||
</p>
|
||||
</Show>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import { For, Show, createMemo } from 'solid-js'
|
||||
import { For, Show, createMemo, onMount, createSignal } from 'solid-js'
|
||||
import type { Shout, Topic } from '../../graphql/types.gen'
|
||||
import Row3 from '../Feed/Row3'
|
||||
import Row2 from '../Feed/Row2'
|
||||
import Beside from '../Feed/Beside'
|
||||
import { Row3 } from '../Feed/Row3'
|
||||
import { Row2 } from '../Feed/Row2'
|
||||
import { Beside } from '../Feed/Beside'
|
||||
import { ArticleCard } from '../Feed/Card'
|
||||
import '../../styles/Topic.scss'
|
||||
import { FullTopic } from '../Topic/Full'
|
||||
import { t } from '../../utils/intl'
|
||||
import { useRouter } from '../../stores/router'
|
||||
import { useTopicsStore } from '../../stores/zine/topics'
|
||||
import { useArticlesStore } from '../../stores/zine/articles'
|
||||
import { loadPublishedArticles, useArticlesStore } from '../../stores/zine/articles'
|
||||
import { useAuthorsStore } from '../../stores/zine/authors'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
||||
import { splitToPages } from '../../utils/splitToPages'
|
||||
|
||||
type TopicsPageSearchParams = {
|
||||
by: 'comments' | '' | 'recent' | 'viewed' | 'rating' | 'commented'
|
||||
|
@ -22,9 +24,14 @@ interface TopicProps {
|
|||
topicSlug: string
|
||||
}
|
||||
|
||||
export const PRERENDERED_ARTICLES_COUNT = 21
|
||||
const LOAD_MORE_PAGE_SIZE = 9 // Row3 + Row3 + Row3
|
||||
|
||||
export const TopicView = (props: TopicProps) => {
|
||||
const { searchParams, changeSearchParam } = useRouter<TopicsPageSearchParams>()
|
||||
|
||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||
|
||||
const { sortedArticles } = useArticlesStore({ sortedArticles: props.topicArticles })
|
||||
const { topicEntities } = useTopicsStore({ topics: [props.topic] })
|
||||
|
||||
|
@ -32,6 +39,24 @@ export const TopicView = (props: TopicProps) => {
|
|||
|
||||
const topic = createMemo(() => topicEntities()[props.topicSlug])
|
||||
|
||||
const loadMore = async () => {
|
||||
saveScrollPosition()
|
||||
|
||||
const { hasMore } = await loadPublishedArticles({
|
||||
limit: LOAD_MORE_PAGE_SIZE,
|
||||
offset: sortedArticles().length
|
||||
})
|
||||
setIsLoadMoreButtonVisible(hasMore)
|
||||
|
||||
restoreScrollPosition()
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
if (sortedArticles().length === PRERENDERED_ARTICLES_COUNT) {
|
||||
loadMore()
|
||||
}
|
||||
})
|
||||
|
||||
const title = createMemo(() => {
|
||||
const m = searchParams().by
|
||||
if (m === 'viewed') return t('Top viewed')
|
||||
|
@ -40,6 +65,10 @@ export const TopicView = (props: TopicProps) => {
|
|||
return t('Top recent')
|
||||
})
|
||||
|
||||
const pages = createMemo<Shout[][]>(() =>
|
||||
splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
|
||||
)
|
||||
|
||||
return (
|
||||
<div class="topic-page container">
|
||||
<Show when={topic()}>
|
||||
|
@ -110,6 +139,24 @@ export const TopicView = (props: TopicProps) => {
|
|||
<Row3 articles={sortedArticles().slice(15, 18)} />
|
||||
<Row3 articles={sortedArticles().slice(18, 21)} />
|
||||
</Show>
|
||||
|
||||
<For each={pages()}>
|
||||
{(page) => (
|
||||
<>
|
||||
<Row3 articles={page.slice(0, 3)} />
|
||||
<Row3 articles={page.slice(3, 6)} />
|
||||
<Row3 articles={page.slice(6, 9)} />
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
|
||||
<Show when={isLoadMoreButtonVisible()}>
|
||||
<p class="load-more-container">
|
||||
<button class="button" onClick={loadMore}>
|
||||
{t('Load more')}
|
||||
</button>
|
||||
</p>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
|
|
@ -8,7 +8,6 @@ export type PageProps = {
|
|||
authorArticles?: Shout[]
|
||||
topicArticles?: Shout[]
|
||||
homeArticles?: Shout[]
|
||||
feedArticles?: Shout[]
|
||||
author?: Author
|
||||
allAuthors?: Author[]
|
||||
topic?: Topic
|
||||
|
|
|
@ -3,9 +3,10 @@ import { Root } from '../../../components/Root'
|
|||
import Zine from '../../../layouts/zine.astro'
|
||||
import { apiClient } from '../../../utils/apiClient'
|
||||
import { initRouter } from '../../../stores/router'
|
||||
import { PRERENDERED_ARTICLES_COUNT } from '../../../components/Views/Author'
|
||||
|
||||
const slug = Astro.params.slug.toString()
|
||||
const articles = await apiClient.getArticlesForAuthors({ authorSlugs: [slug], limit: 50 })
|
||||
const articles = await apiClient.getArticlesForAuthors({ authorSlugs: [slug], limit: PRERENDERED_ARTICLES_COUNT })
|
||||
const author = articles[0].authors.find((a) => a.slug === slug)
|
||||
|
||||
const { pathname, search } = Astro.url
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
---
|
||||
import { Root } from '../../components/Root'
|
||||
import Zine from '../../layouts/zine.astro'
|
||||
import { apiClient } from '../../utils/apiClient'
|
||||
|
||||
import { initRouter } from '../../stores/router'
|
||||
|
||||
const { pathname, search } = Astro.url
|
||||
initRouter(pathname, search)
|
||||
|
||||
const articles = await apiClient.getRecentArticles({ limit: 50 })
|
||||
---
|
||||
|
||||
<Zine>
|
||||
<Root feedArticles={articles} client:load />
|
||||
<Root client:load />
|
||||
</Zine>
|
||||
|
|
|
@ -3,14 +3,14 @@ import Zine from '../layouts/zine.astro'
|
|||
import { Root } from '../components/Root'
|
||||
import { apiClient } from '../utils/apiClient'
|
||||
import { initRouter } from '../stores/router'
|
||||
import { PRERENDERED_ARTICLES_COUNT } from '../components/Views/Home'
|
||||
|
||||
const randomTopics = await apiClient.getRandomTopics({ amount: 12 })
|
||||
const articles = await apiClient.getRecentPublishedArticles({ limit: 5 })
|
||||
const articles = await apiClient.getRecentPublishedArticles({ limit: PRERENDERED_ARTICLES_COUNT })
|
||||
|
||||
const { pathname, search } = Astro.url
|
||||
initRouter(pathname, search)
|
||||
|
||||
|
||||
Astro.response.headers.set('Cache-Control', 's-maxage=1, stale-while-revalidate')
|
||||
---
|
||||
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
import { Root } from '../../components/Root'
|
||||
import Zine from '../../layouts/zine.astro'
|
||||
import { apiClient } from '../../utils/apiClient'
|
||||
import { PRERENDERED_ARTICLES_COUNT } from '../../components/Views/Topic'
|
||||
|
||||
const slug = Astro.params.slug?.toString() || ''
|
||||
const articles = await apiClient.getArticlesForTopics({ topicSlugs: [slug], limit: 50 })
|
||||
const articles = await apiClient.getArticlesForTopics({ topicSlugs: [slug], limit: PRERENDERED_ARTICLES_COUNT })
|
||||
const topic = articles[0].topics.find(({ slug: topicSlug }) => topicSlug === slug)
|
||||
|
||||
import { initRouter } from '../../stores/router'
|
||||
|
|
|
@ -127,40 +127,109 @@ const addSortedArticles = (articles: Shout[]) => {
|
|||
setSortedArticles((prevSortedArticles) => [...prevSortedArticles, ...articles])
|
||||
}
|
||||
|
||||
export const loadFeed = async ({
|
||||
limit,
|
||||
offset
|
||||
}: {
|
||||
limit: number
|
||||
offset?: number
|
||||
}): Promise<{ hasMore: boolean }> => {
|
||||
// TODO: load actual feed
|
||||
return await loadRecentArticles({ limit, offset })
|
||||
}
|
||||
|
||||
export const loadRecentArticles = async ({
|
||||
limit,
|
||||
offset
|
||||
}: {
|
||||
limit?: number
|
||||
limit: number
|
||||
offset?: number
|
||||
}): Promise<void> => {
|
||||
const newArticles = await apiClient.getRecentArticles({ limit, offset })
|
||||
}): Promise<{ hasMore: boolean }> => {
|
||||
const newArticles = await apiClient.getRecentArticles({ limit: limit + 1, offset })
|
||||
const hasMore = newArticles.length === limit + 1
|
||||
|
||||
if (hasMore) {
|
||||
newArticles.splice(-1)
|
||||
}
|
||||
|
||||
addArticles(newArticles)
|
||||
addSortedArticles(newArticles)
|
||||
|
||||
return { hasMore }
|
||||
}
|
||||
|
||||
export const loadPublishedArticles = async ({
|
||||
limit,
|
||||
offset
|
||||
offset = 0
|
||||
}: {
|
||||
limit?: number
|
||||
limit: number
|
||||
offset?: number
|
||||
}): Promise<void> => {
|
||||
const newArticles = await apiClient.getPublishedArticles({ limit, offset })
|
||||
}): Promise<{ hasMore: boolean }> => {
|
||||
const newArticles = await apiClient.getPublishedArticles({ limit: limit + 1, offset })
|
||||
const hasMore = newArticles.length === limit + 1
|
||||
|
||||
if (hasMore) {
|
||||
newArticles.splice(-1)
|
||||
}
|
||||
|
||||
addArticles(newArticles)
|
||||
addSortedArticles(newArticles)
|
||||
|
||||
return { hasMore }
|
||||
}
|
||||
|
||||
export const loadArticlesForAuthors = async ({ authorSlugs }: { authorSlugs: string[] }): Promise<void> => {
|
||||
const articles = await apiClient.getArticlesForAuthors({ authorSlugs, limit: 50 })
|
||||
addArticles(articles)
|
||||
setSortedArticles(articles)
|
||||
export const loadAuthorArticles = async ({
|
||||
authorSlug,
|
||||
limit,
|
||||
offset = 0
|
||||
}: {
|
||||
authorSlug: string
|
||||
limit: number
|
||||
offset?: number
|
||||
}): Promise<{ hasMore: boolean }> => {
|
||||
const newArticles = await apiClient.getArticlesForAuthors({
|
||||
authorSlugs: [authorSlug],
|
||||
limit: limit + 1,
|
||||
offset
|
||||
})
|
||||
|
||||
const hasMore = newArticles.length === limit + 1
|
||||
|
||||
if (hasMore) {
|
||||
newArticles.splice(-1)
|
||||
}
|
||||
|
||||
addArticles(newArticles)
|
||||
addSortedArticles(newArticles)
|
||||
|
||||
return { hasMore }
|
||||
}
|
||||
|
||||
export const loadArticlesForTopics = async ({ topicSlugs }: { topicSlugs: string[] }): Promise<void> => {
|
||||
const articles = await apiClient.getArticlesForTopics({ topicSlugs, limit: 50 })
|
||||
addArticles(articles)
|
||||
setSortedArticles(articles)
|
||||
export const loadTopicArticles = async ({
|
||||
topicSlug,
|
||||
limit,
|
||||
offset
|
||||
}: {
|
||||
topicSlug: string
|
||||
limit: number
|
||||
offset: number
|
||||
}): Promise<{ hasMore: boolean }> => {
|
||||
const newArticles = await apiClient.getArticlesForTopics({
|
||||
topicSlugs: [topicSlug],
|
||||
limit: limit + 1,
|
||||
offset
|
||||
})
|
||||
|
||||
const hasMore = newArticles.length === limit + 1
|
||||
|
||||
if (hasMore) {
|
||||
newArticles.splice(-1)
|
||||
}
|
||||
|
||||
addArticles(newArticles)
|
||||
addSortedArticles(newArticles)
|
||||
|
||||
return { hasMore }
|
||||
}
|
||||
|
||||
export const resetSortedArticles = () => {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { apiClient } from '../../utils/apiClient'
|
||||
import type { Author } from '../../graphql/types.gen'
|
||||
import { byCreated, byStat, byTopicStatDesc } from '../../utils/sortby'
|
||||
|
||||
import { getLogger } from '../../utils/logger'
|
||||
import { createSignal } from 'solid-js'
|
||||
|
|
|
@ -184,7 +184,7 @@ export const apiClient = {
|
|||
},
|
||||
getArticlesForTopics: async ({
|
||||
topicSlugs,
|
||||
limit = FEED_SIZE,
|
||||
limit,
|
||||
offset = 0
|
||||
}: {
|
||||
topicSlugs: string[]
|
||||
|
@ -207,7 +207,7 @@ export const apiClient = {
|
|||
},
|
||||
getArticlesForAuthors: async ({
|
||||
authorSlugs,
|
||||
limit = FEED_SIZE,
|
||||
limit,
|
||||
offset = 0
|
||||
}: {
|
||||
authorSlugs: string[]
|
||||
|
|
10
src/utils/splitToPages.ts
Normal file
10
src/utils/splitToPages.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export function splitToPages<T>(arr: T[], startIndex: number, pageSize: number): T[][] {
|
||||
return arr.slice(startIndex).reduce((acc, article, index) => {
|
||||
if (index % pageSize === 0) {
|
||||
acc.push([])
|
||||
}
|
||||
|
||||
acc[acc.length - 1].push(article)
|
||||
return acc
|
||||
}, [] as T[][])
|
||||
}
|
Loading…
Reference in New Issue
Block a user