load-more-main-ok
This commit is contained in:
parent
f6f012449d
commit
4fe2768329
|
@ -51,7 +51,7 @@ const data: PlaceholderData = {
|
||||||
text: 'Placeholder feedDiscussions',
|
text: 'Placeholder feedDiscussions',
|
||||||
buttonLabelAuthor: 'Current discussions',
|
buttonLabelAuthor: 'Current discussions',
|
||||||
buttonLabelFeed: 'Enter',
|
buttonLabelFeed: 'Enter',
|
||||||
href: '/feed?by=last_comment'
|
href: '/feed/hot'
|
||||||
},
|
},
|
||||||
author: {
|
author: {
|
||||||
image: 'placeholder-join.webp',
|
image: 'placeholder-join.webp',
|
||||||
|
@ -71,7 +71,7 @@ const data: PlaceholderData = {
|
||||||
header: 'Join discussions',
|
header: 'Join discussions',
|
||||||
text: 'Placeholder feedDiscussions',
|
text: 'Placeholder feedDiscussions',
|
||||||
buttonLabel: 'Go to discussions',
|
buttonLabel: 'Go to discussions',
|
||||||
href: '/feed?by=last_comment',
|
href: '/feed/hot',
|
||||||
profileLinks: [
|
profileLinks: [
|
||||||
{
|
{
|
||||||
href: '/debate',
|
href: '/debate',
|
||||||
|
|
|
@ -500,14 +500,14 @@ export const Header = (props: Props) => {
|
||||||
</span>
|
</span>
|
||||||
</A>
|
</A>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
{/* <li>
|
||||||
<A href={'/feed/bookmarked'}>
|
<A href={'/feed/bookmarked'}>
|
||||||
<span class={styles.subnavigationItemName}>
|
<span class={styles.subnavigationItemName}>
|
||||||
<Icon name="bookmark" class={styles.icon} />
|
<Icon name="bookmark" class={styles.icon} />
|
||||||
{t('Bookmarks')}
|
{t('Bookmarks')}
|
||||||
</span>
|
</span>
|
||||||
</A>
|
</A>
|
||||||
</li>
|
</li> */}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { A, createAsync, useLocation, useNavigate, useSearchParams } from '@solidjs/router'
|
import { A, createAsync, useLocation, useNavigate, useSearchParams } from '@solidjs/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
|
import { For, Show, createEffect, createMemo, createSignal, on } from 'solid-js'
|
||||||
import { DropDown } from '~/components/_shared/DropDown'
|
import { DropDown } from '~/components/_shared/DropDown'
|
||||||
import { Option } from '~/components/_shared/DropDown/DropDown'
|
import { Option } from '~/components/_shared/DropDown/DropDown'
|
||||||
import { Icon } from '~/components/_shared/Icon'
|
import { Icon } from '~/components/_shared/Icon'
|
||||||
|
@ -18,7 +18,7 @@ import { useUI } from '~/context/ui'
|
||||||
import { loadUnratedShouts } from '~/graphql/api/private'
|
import { loadUnratedShouts } from '~/graphql/api/private'
|
||||||
import type { Author, Reaction, Shout } from '~/graphql/schema/core.gen'
|
import type { Author, Reaction, Shout } from '~/graphql/schema/core.gen'
|
||||||
import { byCreated } from '~/lib/sort'
|
import { byCreated } from '~/lib/sort'
|
||||||
import { FeedSearchParams } from '~/routes/feed/(feed)'
|
import { FeedSearchParams } from '~/routes/feed/[...order]'
|
||||||
import { CommentDate } from '../../Article/CommentDate'
|
import { CommentDate } from '../../Article/CommentDate'
|
||||||
import { getShareUrl } from '../../Article/SharePopup'
|
import { getShareUrl } from '../../Article/SharePopup'
|
||||||
import { AuthorBadge } from '../../Author/AuthorBadge'
|
import { AuthorBadge } from '../../Author/AuthorBadge'
|
||||||
|
@ -36,6 +36,7 @@ export type PeriodType = 'week' | 'month' | 'year'
|
||||||
|
|
||||||
export type FeedProps = {
|
export type FeedProps = {
|
||||||
shouts?: Shout[]
|
shouts?: Shout[]
|
||||||
|
mode?: '' | 'likes' | 'hot'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FeedView = (props: FeedProps) => {
|
export const FeedView = (props: FeedProps) => {
|
||||||
|
@ -53,7 +54,7 @@ export const FeedView = (props: FeedProps) => {
|
||||||
const [isLoading, setIsLoading] = createSignal(false)
|
const [isLoading, setIsLoading] = createSignal(false)
|
||||||
const [isRightColumnLoaded, setIsRightColumnLoaded] = createSignal(false)
|
const [isRightColumnLoaded, setIsRightColumnLoaded] = createSignal(false)
|
||||||
const { session } = useSession()
|
const { session } = useSession()
|
||||||
const { nonfeaturedFeed, setNonFeaturedFeed } = useFeed()
|
const { feed, setFeed } = useFeed()
|
||||||
const { loadReactionsBy } = useReactions()
|
const { loadReactionsBy } = useReactions()
|
||||||
const { topTopics } = useTopics()
|
const { topTopics } = useTopics()
|
||||||
const { topAuthors } = useAuthors()
|
const { topAuthors } = useAuthors()
|
||||||
|
@ -67,20 +68,13 @@ export const FeedView = (props: FeedProps) => {
|
||||||
setTopComments(comments.sort(byCreated).reverse())
|
setTopComments(comments.sort(byCreated).reverse())
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(
|
|
||||||
() =>
|
|
||||||
props.shouts &&
|
|
||||||
Array.isArray(props.shouts) &&
|
|
||||||
setNonFeaturedFeed((prev) => [...prev, ...(props.shouts || [])]) && console.info(nonfeaturedFeed())
|
|
||||||
)
|
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => nonfeaturedFeed(),
|
feed,
|
||||||
(sss?: Shout[]) => {
|
(sss?: Shout[]) => {
|
||||||
if (sss && Array.isArray(sss)) {
|
if (sss && Array.isArray(sss)) {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
setNonFeaturedFeed((prev) => [...prev, ...sss])
|
setFeed((prev) => [...prev, ...sss])
|
||||||
Promise.all([
|
Promise.all([
|
||||||
loadTopComments(),
|
loadTopComments(),
|
||||||
loadReactionsBy({ by: { shouts: sss.map((s: Shout) => s.slug) } })
|
loadReactionsBy({ by: { shouts: sss.map((s: Shout) => s.slug) } })
|
||||||
|
@ -113,40 +107,33 @@ export const FeedView = (props: FeedProps) => {
|
||||||
<Placeholder type={loc?.pathname} mode="feed" />
|
<Placeholder type={loc?.pathname} mode="feed" />
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={(session() || loc?.pathname === 'feed') && nonfeaturedFeed()?.length}>
|
<Show when={(session() || loc?.pathname === 'feed') && feed()?.length}>
|
||||||
<div class={styles.filtersContainer}>
|
<div class={styles.filtersContainer}>
|
||||||
<ul class={clsx('view-switcher', styles.feedFilter)}>
|
<ul class={clsx('view-switcher', styles.feedFilter)}>
|
||||||
<li
|
<li class={clsx({ 'view-switcher__item--selected': !props.mode })}>
|
||||||
class={clsx({
|
|
||||||
'view-switcher__item--selected': searchParams?.by === 'after' || !searchParams?.by
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<A href={loc.pathname}>{t('Recent')}</A>
|
<A href={loc.pathname}>{t('Recent')}</A>
|
||||||
</li>
|
</li>
|
||||||
{/*<li>*/}
|
|
||||||
{/* <a href="/feed/?by=views">{t('Most read')}</a>*/}
|
|
||||||
{/*</li>*/}
|
|
||||||
<li
|
<li
|
||||||
class={clsx({
|
class={clsx({
|
||||||
'view-switcher__item--selected': searchParams?.by === 'likes'
|
'view-switcher__item--selected': props.mode === 'likes'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class="link" onClick={() => changeSearchParams({ by: 'likes' })}>
|
<A class="link" href={'/feed/likes'}>
|
||||||
{t('Top rated')}
|
{t('Liked')}
|
||||||
</span>
|
</A>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class={clsx({
|
class={clsx({
|
||||||
'view-switcher__item--selected': searchParams?.by === 'last_comment'
|
'view-switcher__item--selected': props.mode === 'hot'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span class="link" onClick={() => changeSearchParams({ by: 'last_comment' })}>
|
<A class="link" href={'/feed/hot'}>
|
||||||
{t('Commented')}
|
{t('Commented')}
|
||||||
</span>
|
</A>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class={styles.dropdowns}>
|
<div class={styles.dropdowns}>
|
||||||
<Show when={searchParams?.by && searchParams?.by !== 'after'}>
|
<Show when={searchParams?.period}>
|
||||||
<DropDown
|
<DropDown
|
||||||
popupProps={{ horizontalAnchor: 'right' }}
|
popupProps={{ horizontalAnchor: 'right' }}
|
||||||
options={asOptions(['week', 'month', 'year'])}
|
options={asOptions(['week', 'month', 'year'])}
|
||||||
|
@ -157,7 +144,7 @@ export const FeedView = (props: FeedProps) => {
|
||||||
</Show>
|
</Show>
|
||||||
<DropDown
|
<DropDown
|
||||||
popupProps={{ horizontalAnchor: 'right' }}
|
popupProps={{ horizontalAnchor: 'right' }}
|
||||||
options={asOptions(['followed', 'unrated', 'discussed', 'bookmarked', 'coauthored'])}
|
options={asOptions(['followed', 'unrated', 'discussed', 'coauthored'])}
|
||||||
currentOption={asOption(loc.pathname.split('/').pop() || '')}
|
currentOption={asOption(loc.pathname.split('/').pop() || '')}
|
||||||
triggerCssClass={styles.periodSwitcher}
|
triggerCssClass={styles.periodSwitcher}
|
||||||
onChange={(mode: Option) => navigate(`/feed/${mode.value}`)}
|
onChange={(mode: Option) => navigate(`/feed/${mode.value}`)}
|
||||||
|
@ -166,8 +153,8 @@ export const FeedView = (props: FeedProps) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={!isLoading()} fallback={<Loading />}>
|
<Show when={!isLoading()} fallback={<Loading />}>
|
||||||
<Show when={(nonfeaturedFeed() || []).length > 0}>
|
<Show when={(feed() || []).length > 0}>
|
||||||
<For each={(nonfeaturedFeed() || []).slice(0, 4)}>
|
<For each={(feed() || []).slice(0, 4)}>
|
||||||
{(article) => (
|
{(article) => (
|
||||||
<ArticleCard
|
<ArticleCard
|
||||||
onShare={(shared) => handleShare(shared)}
|
onShare={(shared) => handleShare(shared)}
|
||||||
|
@ -199,7 +186,7 @@ export const FeedView = (props: FeedProps) => {
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<For each={(nonfeaturedFeed() || []).slice(4)}>
|
<For each={(feed() || []).slice(4)}>
|
||||||
{(article) => (
|
{(article) => (
|
||||||
<ArticleCard article={article} settings={{ isFeedMode: true }} desktopCoverSize="M" />
|
<ArticleCard article={article} settings={{ isFeedMode: true }} desktopCoverSize="M" />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -4,17 +4,17 @@ import { useLocalize } from '~/context/localize'
|
||||||
import { Author, Reaction, Shout } from '~/graphql/schema/core.gen'
|
import { Author, Reaction, Shout } from '~/graphql/schema/core.gen'
|
||||||
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
|
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
|
||||||
|
|
||||||
|
export type LoadMoreItems = Shout[] | Author[] | Reaction[]
|
||||||
|
|
||||||
type LoadMoreProps = {
|
type LoadMoreProps = {
|
||||||
loadFunction: (offset?: number) => void
|
loadFunction: (offset?: number) => Promise<LoadMoreItems>
|
||||||
pageSize: number
|
pageSize: number
|
||||||
children: JSX.Element
|
children: JSX.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
type Items = Shout[] | Author[] | Reaction[]
|
|
||||||
|
|
||||||
export const LoadMoreWrapper = (props: LoadMoreProps) => {
|
export const LoadMoreWrapper = (props: LoadMoreProps) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const [items, setItems] = createSignal<Items>([])
|
const [items, setItems] = createSignal<LoadMoreItems>([])
|
||||||
const [offset, setOffset] = createSignal(0)
|
const [offset, setOffset] = createSignal(0)
|
||||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(true)
|
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(true)
|
||||||
const [isLoading, setIsLoading] = createSignal(false)
|
const [isLoading, setIsLoading] = createSignal(false)
|
||||||
|
@ -25,7 +25,7 @@ export const LoadMoreWrapper = (props: LoadMoreProps) => {
|
||||||
const newItems = await props.loadFunction(offset())
|
const newItems = await props.loadFunction(offset())
|
||||||
if (!Array.isArray(newItems)) return
|
if (!Array.isArray(newItems)) return
|
||||||
console.debug('[_share] load more items', newItems)
|
console.debug('[_share] load more items', newItems)
|
||||||
setItems((prev) => [...prev, ...newItems])
|
setItems((prev) => [...prev, ...newItems] as LoadMoreItems)
|
||||||
setOffset((prev) => prev + props.pageSize)
|
setOffset((prev) => prev + props.pageSize)
|
||||||
setIsLoadMoreButtonVisible(newItems.length >= props.pageSize - 1)
|
setIsLoadMoreButtonVisible(newItems.length >= props.pageSize - 1)
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
|
|
|
@ -34,9 +34,9 @@ type FeedContextType = {
|
||||||
seen: Accessor<{ [slug: string]: number }>
|
seen: Accessor<{ [slug: string]: number }>
|
||||||
addSeen: (slug: string) => void
|
addSeen: (slug: string) => void
|
||||||
|
|
||||||
// featured
|
// all
|
||||||
nonfeaturedFeed: Accessor<Shout[] | undefined>
|
feed: Accessor<Shout[] | undefined>
|
||||||
setNonFeaturedFeed: Setter<Shout[]>
|
setFeed: Setter<Shout[]>
|
||||||
|
|
||||||
// featured
|
// featured
|
||||||
featuredFeed: Accessor<Shout[] | undefined>
|
featuredFeed: Accessor<Shout[] | undefined>
|
||||||
|
@ -62,7 +62,7 @@ export const useFeed = () => useContext(FeedContext)
|
||||||
export const FeedProvider = (props: { children: JSX.Element }) => {
|
export const FeedProvider = (props: { children: JSX.Element }) => {
|
||||||
const [sortedFeed, setSortedFeed] = createSignal<Shout[]>([])
|
const [sortedFeed, setSortedFeed] = createSignal<Shout[]>([])
|
||||||
const [articleEntities, setArticleEntities] = createSignal<{ [articleSlug: string]: Shout }>({})
|
const [articleEntities, setArticleEntities] = createSignal<{ [articleSlug: string]: Shout }>({})
|
||||||
const [nonfeaturedFeed, setNonFeaturedFeed] = createSignal<Shout[]>([])
|
const [feed, setFeed] = createSignal<Shout[]>([])
|
||||||
const [featuredFeed, setFeaturedFeed] = createSignal<Shout[]>([])
|
const [featuredFeed, setFeaturedFeed] = createSignal<Shout[]>([])
|
||||||
const [expoFeed, setExpoFeed] = createSignal<Shout[]>([])
|
const [expoFeed, setExpoFeed] = createSignal<Shout[]>([])
|
||||||
const [topFeed, setTopFeed] = createSignal<Shout[]>([])
|
const [topFeed, setTopFeed] = createSignal<Shout[]>([])
|
||||||
|
@ -260,8 +260,8 @@ export const FeedProvider = (props: { children: JSX.Element }) => {
|
||||||
setFeaturedFeed,
|
setFeaturedFeed,
|
||||||
expoFeed,
|
expoFeed,
|
||||||
setExpoFeed,
|
setExpoFeed,
|
||||||
nonfeaturedFeed,
|
feed,
|
||||||
setNonFeaturedFeed
|
setFeed
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|
|
@ -263,6 +263,7 @@
|
||||||
"Lists": "Списки",
|
"Lists": "Списки",
|
||||||
"Literature": "Литература",
|
"Literature": "Литература",
|
||||||
"Load more": "Показать ещё",
|
"Load more": "Показать ещё",
|
||||||
|
"loaded": "загружено",
|
||||||
"Loading": "Загрузка",
|
"Loading": "Загрузка",
|
||||||
"Login and security": "Вход и безопасность",
|
"Login and security": "Вход и безопасность",
|
||||||
"Logout": "Выход",
|
"Logout": "Выход",
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import { type RouteDefinition, type RouteSectionProps, createAsync } from '@solidjs/router'
|
import { type RouteDefinition, type RouteSectionProps, createAsync } from '@solidjs/router'
|
||||||
import { Show, createEffect } from 'solid-js'
|
import { Show, createEffect } from 'solid-js'
|
||||||
import { LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper'
|
import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper'
|
||||||
import { useFeed } from '~/context/feed'
|
import { useFeed } from '~/context/feed'
|
||||||
import { useTopics } from '~/context/topics'
|
import { useTopics } from '~/context/topics'
|
||||||
import { loadShouts, loadTopics } from '~/graphql/api/public'
|
import { loadShouts, loadTopics } from '~/graphql/api/public'
|
||||||
import { LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen'
|
import { LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen'
|
||||||
import { byStat } from '~/lib/sort'
|
|
||||||
import { SortFunction } from '~/types/common'
|
|
||||||
import { HomeView, HomeViewProps } from '../components/Views/Home'
|
import { HomeView, HomeViewProps } from '../components/Views/Home'
|
||||||
import { Loading } from '../components/_shared/Loading'
|
import { Loading } from '../components/_shared/Loading'
|
||||||
import { PageLayout } from '../components/_shared/PageLayout'
|
import { PageLayout } from '../components/_shared/PageLayout'
|
||||||
|
@ -74,7 +72,6 @@ export const route = {
|
||||||
} satisfies RouteDefinition
|
} satisfies RouteDefinition
|
||||||
|
|
||||||
export default function HomePage(props: RouteSectionProps<HomeViewProps>) {
|
export default function HomePage(props: RouteSectionProps<HomeViewProps>) {
|
||||||
const { addTopics } = useTopics()
|
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const {
|
const {
|
||||||
setFeaturedFeed,
|
setFeaturedFeed,
|
||||||
|
@ -85,46 +82,38 @@ export default function HomePage(props: RouteSectionProps<HomeViewProps>) {
|
||||||
topFeed: topRatedFeed
|
topFeed: topRatedFeed
|
||||||
} = useFeed()
|
} = useFeed()
|
||||||
|
|
||||||
const data = createAsync(async (prev?: HomeViewProps) => {
|
// preload all topics
|
||||||
const topics = props.data?.topics || (await fetchAllTopics())
|
const { addTopics, sortedTopics } = useTopics()
|
||||||
const offset = prev?.featuredShouts?.length || 0
|
|
||||||
const featuredShoutsLoader = featuredLoader(offset)
|
|
||||||
const loaded = await featuredShoutsLoader()
|
|
||||||
setFeaturedFeed((prev) => [...prev, ...loaded||[]])
|
|
||||||
const featuredShouts = [
|
|
||||||
...(prev?.featuredShouts || []),
|
|
||||||
...(loaded || props.data?.featuredShouts || [])
|
|
||||||
]
|
|
||||||
const sortFn = byStat('viewed')
|
|
||||||
const topViewedShouts = featuredShouts.sort(sortFn as SortFunction<Shout>)
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
...props.data,
|
|
||||||
topViewedShouts,
|
|
||||||
featuredShouts,
|
|
||||||
topics
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (data()?.topics) {
|
!sortedTopics() && props.data.topics && addTopics(props.data.topics)
|
||||||
console.debug('[routes.main] topics update')
|
|
||||||
addTopics(data()?.topics || [])
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// load more faetured shouts
|
||||||
const loadMoreFeatured = async (offset?: number) => {
|
const loadMoreFeatured = async (offset?: number) => {
|
||||||
const shoutsLoader = featuredLoader(offset)
|
const shoutsLoader = featuredLoader(offset)
|
||||||
const loaded = await shoutsLoader()
|
const loaded = await shoutsLoader()
|
||||||
loaded && setFeaturedFeed((prev: Shout[]) => [...prev, ...loaded])
|
loaded && setFeaturedFeed((prev: Shout[]) => [...prev, ...loaded])
|
||||||
|
return loaded as LoadMoreItems
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// preload featured shouts
|
||||||
|
const shouts = createAsync(async () => {
|
||||||
|
if (props.data.featuredShouts) {
|
||||||
|
setFeaturedFeed(props.data.featuredShouts)
|
||||||
|
console.debug('[routes.main] featured feed preloaded')
|
||||||
|
return props.data.featuredShouts
|
||||||
|
}
|
||||||
|
return await loadMoreFeatured()
|
||||||
|
})
|
||||||
|
|
||||||
const SHOUTS_PER_PAGE = 20
|
const SHOUTS_PER_PAGE = 20
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageLayout withPadding={true} title={t('Discours')} key="home">
|
<PageLayout withPadding={true} title={t('Discours')} key="home">
|
||||||
<Show when={(featuredFeed() || []).length > 0} fallback={<Loading />}>
|
<Show when={(featuredFeed() || []).length > 0} fallback={<Loading />}>
|
||||||
<LoadMoreWrapper loadFunction={loadMoreFeatured} pageSize={SHOUTS_PER_PAGE}>
|
<LoadMoreWrapper loadFunction={loadMoreFeatured} pageSize={SHOUTS_PER_PAGE}>
|
||||||
<HomeView
|
<HomeView
|
||||||
featuredShouts={featuredFeed() as Shout[]}
|
featuredShouts={featuredFeed() || shouts() as Shout[]}
|
||||||
topMonthShouts={topMonthFeed() as Shout[]}
|
topMonthShouts={topMonthFeed() as Shout[]}
|
||||||
topViewedShouts={topViewedFeed() as Shout[]}
|
topViewedShouts={topViewedFeed() as Shout[]}
|
||||||
topRatedShouts={topRatedFeed() as Shout[]}
|
topRatedShouts={topRatedFeed() as Shout[]}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import { RouteSectionProps, createAsync, useSearchParams } from '@solidjs/router'
|
import { RouteSectionProps, createAsync, useSearchParams } from '@solidjs/router'
|
||||||
import { Client } from '@urql/core'
|
import { Client } from '@urql/core'
|
||||||
import { createSignal } from 'solid-js'
|
import { createEffect } from 'solid-js'
|
||||||
import { AUTHORS_PER_PAGE } from '~/components/Views/AllAuthors/AllAuthors'
|
import { AUTHORS_PER_PAGE } from '~/components/Views/AllAuthors/AllAuthors'
|
||||||
import { Feed } from '~/components/Views/Feed'
|
import { Feed } from '~/components/Views/Feed'
|
||||||
import { LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper'
|
import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper'
|
||||||
import { PageLayout } from '~/components/_shared/PageLayout'
|
import { PageLayout } from '~/components/_shared/PageLayout'
|
||||||
import { useFeed } from '~/context/feed'
|
import { useFeed } from '~/context/feed'
|
||||||
import { useLocalize } from '~/context/localize'
|
import { useLocalize } from '~/context/localize'
|
||||||
import { ReactionsProvider } from '~/context/reactions'
|
import { ReactionsProvider } from '~/context/reactions'
|
||||||
|
import { useTopics } from '~/context/topics'
|
||||||
import { loadShouts } from '~/graphql/api/public'
|
import { loadShouts } from '~/graphql/api/public'
|
||||||
import { LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen'
|
import { LoadShoutsOptions, Shout, Topic } from '~/graphql/schema/core.gen'
|
||||||
import { SHOUTS_PER_PAGE } from '../(main)'
|
import { SHOUTS_PER_PAGE } from '../(main)'
|
||||||
|
|
||||||
export type FeedPeriod = 'week' | 'month' | 'year'
|
export type FeedPeriod = 'week' | 'month' | 'year'
|
||||||
|
@ -20,7 +21,6 @@ export type PeriodItem = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FeedSearchParams = {
|
export type FeedSearchParams = {
|
||||||
by: 'after' | 'likes' | 'last_comment'
|
|
||||||
period: FeedPeriod
|
period: FeedPeriod
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,42 +44,67 @@ const getFromDate = (period: FeedPeriod): number => {
|
||||||
return Math.floor(d.getTime() / 1000)
|
return Math.floor(d.getTime() / 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchPublishedShouts = async (offset?: number, _client?: Client) => {
|
const feedLoader = async (options: Partial<LoadShoutsOptions>, _client?: Client) => {
|
||||||
const shoutsLoader = loadShouts({ filters: { featured: undefined }, limit: SHOUTS_PER_PAGE, offset })
|
const shoutsLoader = loadShouts({ ...options, limit: SHOUTS_PER_PAGE } as LoadShoutsOptions)
|
||||||
return await shoutsLoader()
|
return await shoutsLoader()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const route = {
|
export const route = {
|
||||||
load: async ({ location: { query } }: RouteSectionProps<{ articles: Shout[] }>) => {
|
load: async ({ location: { query } }: RouteSectionProps<{ articles: Shout[] }>) => {
|
||||||
const offset: number = Number.parseInt(query.offset, 10)
|
const offset: number = Number.parseInt(query.offset, 10)
|
||||||
const result = await fetchPublishedShouts(offset)
|
const result = await feedLoader({ offset })
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (props: RouteSectionProps<Shout[]>) => {
|
export default (props: RouteSectionProps<{ shouts: Shout[]; topics: Topic[] }>) => {
|
||||||
const [searchParams] = useSearchParams<FeedSearchParams>()
|
const [searchParams] = useSearchParams<FeedSearchParams>() // ?period=month
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const {setNonFeaturedFeed} = useFeed()
|
const { setFeed } = useFeed()
|
||||||
const [offset, setOffset] = createSignal<number>(0)
|
|
||||||
const loadMore = async () => {
|
// preload all topics
|
||||||
const newOffset = offset() + SHOUTS_PER_PAGE
|
const { addTopics, sortedTopics } = useTopics()
|
||||||
setOffset(newOffset)
|
createEffect(() => {
|
||||||
|
!sortedTopics() && props.data.topics && addTopics(props.data.topics)
|
||||||
|
})
|
||||||
|
|
||||||
|
// load more feed
|
||||||
|
const loadMoreFeed = async (offset?: number) => {
|
||||||
|
// /feed/:order: - select order setting
|
||||||
|
const paramPattern = /^(hot|likes)$/
|
||||||
|
const order =
|
||||||
|
(props.params.order && paramPattern.test(props.params.order)
|
||||||
|
? props.params.order === 'hot'
|
||||||
|
? 'last_comment'
|
||||||
|
: props.params.order
|
||||||
|
: 'created_at') || 'created_at'
|
||||||
|
|
||||||
const options: LoadShoutsOptions = {
|
const options: LoadShoutsOptions = {
|
||||||
limit: SHOUTS_PER_PAGE,
|
limit: SHOUTS_PER_PAGE,
|
||||||
offset: newOffset,
|
offset,
|
||||||
order_by: searchParams?.by
|
order_by: order
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchParams?.by === 'after') {
|
// ?period=month - time period filter
|
||||||
const period = searchParams?.by || 'month'
|
if (searchParams?.period) {
|
||||||
|
const period = searchParams?.period || 'month'
|
||||||
options.filters = { after: getFromDate(period as FeedPeriod) }
|
options.filters = { after: getFromDate(period as FeedPeriod) }
|
||||||
}
|
}
|
||||||
const result = await fetchPublishedShouts(newOffset)
|
|
||||||
result && setNonFeaturedFeed(result)
|
const loaded = await feedLoader(options)
|
||||||
return
|
loaded && setFeed((prev: Shout[]) => [...prev, ...loaded])
|
||||||
|
return loaded as LoadMoreItems
|
||||||
}
|
}
|
||||||
const shouts = createAsync(async () => props.data || await loadMore())
|
|
||||||
|
// preload shouts
|
||||||
|
const shouts = createAsync(async () => {
|
||||||
|
if (props.data.shouts) {
|
||||||
|
setFeed(props.data.shouts)
|
||||||
|
console.debug('[routes.main] feed preloaded')
|
||||||
|
return props.data.shouts
|
||||||
|
}
|
||||||
|
return (await loadMoreFeed()) as Shout[]
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageLayout
|
<PageLayout
|
||||||
|
@ -88,9 +113,9 @@ export default (props: RouteSectionProps<Shout[]>) => {
|
||||||
key="feed"
|
key="feed"
|
||||||
desc="Independent media project about culture, science, art and society with horizontal editing"
|
desc="Independent media project about culture, science, art and society with horizontal editing"
|
||||||
>
|
>
|
||||||
<LoadMoreWrapper loadFunction={loadMore} pageSize={AUTHORS_PER_PAGE}>
|
<LoadMoreWrapper loadFunction={loadMoreFeed} pageSize={AUTHORS_PER_PAGE}>
|
||||||
<ReactionsProvider>
|
<ReactionsProvider>
|
||||||
<Feed shouts={shouts() || []} />
|
<Feed />
|
||||||
</ReactionsProvider>
|
</ReactionsProvider>
|
||||||
</LoadMoreWrapper>
|
</LoadMoreWrapper>
|
||||||
</PageLayout>
|
</PageLayout>
|
114
src/routes/feed/my/[...mode]/[...order].tsx
Normal file
114
src/routes/feed/my/[...mode]/[...order].tsx
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import { RouteSectionProps, useSearchParams } from '@solidjs/router'
|
||||||
|
import { createEffect } from 'solid-js'
|
||||||
|
import { AUTHORS_PER_PAGE } from '~/components/Views/AllAuthors/AllAuthors'
|
||||||
|
import { Feed } from '~/components/Views/Feed'
|
||||||
|
import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper'
|
||||||
|
import { PageLayout } from '~/components/_shared/PageLayout'
|
||||||
|
import { useFeed } from '~/context/feed'
|
||||||
|
import { useGraphQL } from '~/context/graphql'
|
||||||
|
import { useLocalize } from '~/context/localize'
|
||||||
|
import { ReactionsProvider } from '~/context/reactions'
|
||||||
|
import { useTopics } from '~/context/topics'
|
||||||
|
import {
|
||||||
|
loadCoauthoredShouts,
|
||||||
|
loadDiscussedShouts,
|
||||||
|
loadFollowedShouts,
|
||||||
|
loadUnratedShouts
|
||||||
|
} from '~/graphql/api/private'
|
||||||
|
import { LoadShoutsOptions, Shout, Topic } from '~/graphql/schema/core.gen'
|
||||||
|
|
||||||
|
const feeds = {
|
||||||
|
followed: loadFollowedShouts,
|
||||||
|
discussed: loadDiscussedShouts,
|
||||||
|
coauthored: loadCoauthoredShouts,
|
||||||
|
unrated: loadUnratedShouts
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FeedPeriod = 'week' | 'month' | 'year'
|
||||||
|
export type FeedSearchParams = { period?: FeedPeriod }
|
||||||
|
|
||||||
|
const getFromDate = (period: FeedPeriod): number => {
|
||||||
|
const now = new Date()
|
||||||
|
let d: Date = now
|
||||||
|
switch (period) {
|
||||||
|
case 'week': {
|
||||||
|
d = new Date(now.setDate(now.getDate() - 7))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'month': {
|
||||||
|
d = new Date(now.setMonth(now.getMonth() - 1))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'year': {
|
||||||
|
d = new Date(now.setFullYear(now.getFullYear() - 1))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Math.floor(d.getTime() / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// /feed/my/followed/hot
|
||||||
|
|
||||||
|
export default (props: RouteSectionProps<{ shouts: Shout[]; topics: Topic[] }>) => {
|
||||||
|
const [searchParams] = useSearchParams<FeedSearchParams>() // ?period=month
|
||||||
|
const { t } = useLocalize()
|
||||||
|
const { setFeed } = useFeed()
|
||||||
|
// TODO: use const { requireAuthentication } = useSession()
|
||||||
|
const client = useGraphQL()
|
||||||
|
|
||||||
|
// preload all topics
|
||||||
|
const { addTopics, sortedTopics } = useTopics()
|
||||||
|
createEffect(() => {
|
||||||
|
!sortedTopics() && props.data.topics && addTopics(props.data.topics)
|
||||||
|
})
|
||||||
|
|
||||||
|
// load more my feed
|
||||||
|
const loadMoreMyFeed = async (offset?: number) => {
|
||||||
|
// /feed/my/:mode:
|
||||||
|
const paramModePattern = /^(followed|discussed|liked|coauthored|unrated)$/
|
||||||
|
const mode =
|
||||||
|
props.params.mode && paramModePattern.test(props.params.mode) ? props.params.mode : 'followed'
|
||||||
|
const gqlHandler = feeds[mode as keyof typeof feeds]
|
||||||
|
|
||||||
|
// /feed/my/:mode:/:order: - select order setting
|
||||||
|
const paramOrderPattern = /^(hot|likes)$/
|
||||||
|
const order =
|
||||||
|
(paramOrderPattern.test(props.params.order)
|
||||||
|
? props.params.order === 'hot'
|
||||||
|
? 'last_comment'
|
||||||
|
: props.params.order
|
||||||
|
: 'created_at') || 'created_at'
|
||||||
|
|
||||||
|
const options: LoadShoutsOptions = {
|
||||||
|
limit: 20,
|
||||||
|
offset,
|
||||||
|
order_by: order
|
||||||
|
}
|
||||||
|
|
||||||
|
// ?period=month - time period filter
|
||||||
|
if (searchParams?.period) {
|
||||||
|
const period = searchParams?.period || 'month'
|
||||||
|
options.filters = { after: getFromDate(period as FeedPeriod) }
|
||||||
|
}
|
||||||
|
|
||||||
|
const shoutsLoader = gqlHandler(client, options)
|
||||||
|
const loaded = await shoutsLoader()
|
||||||
|
loaded && setFeed((prev: Shout[]) => [...prev, ...loaded])
|
||||||
|
return loaded as LoadMoreItems
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageLayout
|
||||||
|
withPadding={true}
|
||||||
|
title={`${t('Discours')} :: ${t('Feed')}`}
|
||||||
|
key="feed"
|
||||||
|
desc="Independent media project about culture, science, art and society with horizontal editing"
|
||||||
|
>
|
||||||
|
<LoadMoreWrapper loadFunction={loadMoreMyFeed} pageSize={AUTHORS_PER_PAGE}>
|
||||||
|
<ReactionsProvider>
|
||||||
|
<Feed />
|
||||||
|
</ReactionsProvider>
|
||||||
|
</LoadMoreWrapper>
|
||||||
|
</PageLayout>
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user