topic navigation fix (#346)
* topic navigation fix * topic loading state
This commit is contained in:
parent
d463c83fe3
commit
87b6e44eb2
|
@ -2,7 +2,7 @@ import type { Shout, Topic } from '../../graphql/types.gen'
|
||||||
|
|
||||||
import { Meta } from '@solidjs/meta'
|
import { Meta } from '@solidjs/meta'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { For, Show, createMemo, onMount, createSignal, createEffect } from 'solid-js'
|
import { For, Show, createMemo, onMount, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
|
@ -14,7 +14,6 @@ import { getImageUrl } from '../../utils/getImageUrl'
|
||||||
import { getDescription } from '../../utils/meta'
|
import { getDescription } from '../../utils/meta'
|
||||||
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
||||||
import { splitToPages } from '../../utils/splitToPages'
|
import { splitToPages } from '../../utils/splitToPages'
|
||||||
import { Loading } from '../_shared/Loading'
|
|
||||||
import { ArticleCardSwiper } from '../_shared/SolidSwiper/ArticleCardSwiper'
|
import { ArticleCardSwiper } from '../_shared/SolidSwiper/ArticleCardSwiper'
|
||||||
import { Beside } from '../Feed/Beside'
|
import { Beside } from '../Feed/Beside'
|
||||||
import { Row1 } from '../Feed/Row1'
|
import { Row1 } from '../Feed/Row1'
|
||||||
|
@ -32,8 +31,6 @@ interface Props {
|
||||||
topic: Topic
|
topic: Topic
|
||||||
shouts: Shout[]
|
shouts: Shout[]
|
||||||
topicSlug: string
|
topicSlug: string
|
||||||
isLoaded: boolean
|
|
||||||
title: (val: string) => string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PRERENDERED_ARTICLES_COUNT = 28
|
export const PRERENDERED_ARTICLES_COUNT = 28
|
||||||
|
@ -88,7 +85,6 @@ export const TopicView = (props: Props) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
const pageTitle = `#${capitalize(topic().title, true)}`
|
const pageTitle = `#${capitalize(topic().title, true)}`
|
||||||
createEffect(() => props.title(pageTitle))
|
|
||||||
|
|
||||||
const ogImage = topic().pic
|
const ogImage = topic().pic
|
||||||
? getImageUrl(topic().pic, { width: 1200 })
|
? getImageUrl(topic().pic, { width: 1200 })
|
||||||
|
@ -101,7 +97,7 @@ export const TopicView = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div class={styles.topicPage}>
|
<div class={styles.topicPage}>
|
||||||
<Meta name="descprition" content={description} />
|
<Meta name="descprition" content={description} />
|
||||||
<Meta name="keywords" content={t('topicKeywords', { topic: topic().title })} />
|
<Meta name="keywords" content={t('topicKeywords', { topic: pageTitle })} />
|
||||||
<Meta name="og:type" content="article" />
|
<Meta name="og:type" content="article" />
|
||||||
<Meta name="og:title" content={ogTitle} />
|
<Meta name="og:title" content={ogTitle} />
|
||||||
<Meta name="og:image" content={ogImage} />
|
<Meta name="og:image" content={ogImage} />
|
||||||
|
@ -110,102 +106,98 @@ export const TopicView = (props: Props) => {
|
||||||
<Meta name="twitter:card" content="summary_large_image" />
|
<Meta name="twitter:card" content="summary_large_image" />
|
||||||
<Meta name="twitter:title" content={ogTitle} />
|
<Meta name="twitter:title" content={ogTitle} />
|
||||||
<Meta name="twitter:description" content={description} />
|
<Meta name="twitter:description" content={description} />
|
||||||
<Show when={props.isLoaded} fallback={<Loading />}>
|
<FullTopic topic={topic()} />
|
||||||
<Show when={topic()}>
|
<div class="wide-container">
|
||||||
<FullTopic topic={topic()} />
|
<div class={clsx(styles.groupControls, 'row group__controls')}>
|
||||||
<div class="wide-container">
|
<div class="col-md-16">
|
||||||
<div class={clsx(styles.groupControls, 'row group__controls')}>
|
<ul class="view-switcher">
|
||||||
<div class="col-md-16">
|
<li
|
||||||
<ul class="view-switcher">
|
classList={{
|
||||||
<li
|
'view-switcher__item--selected': searchParams().by === 'recent' || !searchParams().by,
|
||||||
classList={{
|
}}
|
||||||
'view-switcher__item--selected': searchParams().by === 'recent' || !searchParams().by,
|
>
|
||||||
}}
|
<button
|
||||||
>
|
type="button"
|
||||||
<button
|
onClick={() =>
|
||||||
type="button"
|
changeSearchParams({
|
||||||
onClick={() =>
|
by: 'recent',
|
||||||
changeSearchParams({
|
})
|
||||||
by: 'recent',
|
}
|
||||||
})
|
>
|
||||||
}
|
{t('Recent')}
|
||||||
>
|
</button>
|
||||||
{t('Recent')}
|
</li>
|
||||||
</button>
|
{/*TODO: server sort*/}
|
||||||
</li>
|
{/*<li classList={{ 'view-switcher__item--selected': getSearchParams().by === 'rating' }}>*/}
|
||||||
{/*TODO: server sort*/}
|
{/* <button type="button" onClick={() => changeSearchParams('by', 'rating')}>*/}
|
||||||
{/*<li classList={{ 'view-switcher__item--selected': getSearchParams().by === 'rating' }}>*/}
|
{/* {t('Popular')}*/}
|
||||||
{/* <button type="button" onClick={() => changeSearchParams('by', 'rating')}>*/}
|
{/* </button>*/}
|
||||||
{/* {t('Popular')}*/}
|
{/*</li>*/}
|
||||||
{/* </button>*/}
|
{/*<li classList={{ 'view-switcher__item--selected': getSearchParams().by === 'viewed' }}>*/}
|
||||||
{/*</li>*/}
|
{/* <button type="button" onClick={() => changeSearchParams('by', 'viewed')}>*/}
|
||||||
{/*<li classList={{ 'view-switcher__item--selected': getSearchParams().by === 'viewed' }}>*/}
|
{/* {t('Views')}*/}
|
||||||
{/* <button type="button" onClick={() => changeSearchParams('by', 'viewed')}>*/}
|
{/* </button>*/}
|
||||||
{/* {t('Views')}*/}
|
{/*</li>*/}
|
||||||
{/* </button>*/}
|
{/*<li classList={{ 'view-switcher__item--selected': getSearchParams().by === 'commented' }}>*/}
|
||||||
{/*</li>*/}
|
{/* <button type="button" onClick={() => changeSearchParams('by', 'commented')}>*/}
|
||||||
{/*<li classList={{ 'view-switcher__item--selected': getSearchParams().by === 'commented' }}>*/}
|
{/* {t('Discussing')}*/}
|
||||||
{/* <button type="button" onClick={() => changeSearchParams('by', 'commented')}>*/}
|
{/* </button>*/}
|
||||||
{/* {t('Discussing')}*/}
|
{/*</li>*/}
|
||||||
{/* </button>*/}
|
</ul>
|
||||||
{/*</li>*/}
|
</div>
|
||||||
</ul>
|
<div class="col-md-8">
|
||||||
</div>
|
<div class="mode-switcher">
|
||||||
<div class="col-md-8">
|
{`${t('Show')} `}
|
||||||
<div class="mode-switcher">
|
<span class="mode-switcher__control">{t('All posts')}</span>
|
||||||
{`${t('Show')} `}
|
|
||||||
<span class="mode-switcher__control">{t('All posts')}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Row1 article={sortedArticles()[0]} />
|
<Row1 article={sortedArticles()[0]} />
|
||||||
<Row2 articles={sortedArticles().slice(1, 3)} isEqual={true} />
|
<Row2 articles={sortedArticles().slice(1, 3)} isEqual={true} />
|
||||||
|
|
||||||
<Beside
|
<Beside
|
||||||
title={t('Topic is supported by')}
|
title={t('Topic is supported by')}
|
||||||
values={authorsByTopic()[topic().slug].slice(0, 6)}
|
values={authorsByTopic()[topic().slug].slice(0, 6)}
|
||||||
beside={sortedArticles()[4]}
|
beside={sortedArticles()[4]}
|
||||||
wrapper={'author'}
|
wrapper={'author'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ArticleCardSwiper title={title()} slides={sortedArticles().slice(5, 11)} />
|
<ArticleCardSwiper title={title()} slides={sortedArticles().slice(5, 11)} />
|
||||||
|
|
||||||
<Beside
|
<Beside
|
||||||
beside={sortedArticles()[12]}
|
beside={sortedArticles()[12]}
|
||||||
title={t('Top viewed')}
|
title={t('Top viewed')}
|
||||||
values={sortedArticles().slice(0, 5)}
|
values={sortedArticles().slice(0, 5)}
|
||||||
wrapper={'top-article'}
|
wrapper={'top-article'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Row2 articles={sortedArticles().slice(13, 15)} isEqual={true} />
|
<Row2 articles={sortedArticles().slice(13, 15)} isEqual={true} />
|
||||||
<Row1 article={sortedArticles()[15]} />
|
<Row1 article={sortedArticles()[15]} />
|
||||||
|
|
||||||
<Show when={sortedArticles().length > 15}>
|
<Show when={sortedArticles().length > 15}>
|
||||||
<ArticleCardSwiper slides={sortedArticles().slice(16, 22)} />
|
<ArticleCardSwiper slides={sortedArticles().slice(16, 22)} />
|
||||||
<Row3 articles={sortedArticles().slice(23, 26)} />
|
<Row3 articles={sortedArticles().slice(23, 26)} />
|
||||||
<Row2 articles={sortedArticles().slice(26, 28)} />
|
<Row2 articles={sortedArticles().slice(26, 28)} />
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<For each={pages()}>
|
<For each={pages()}>
|
||||||
{(page) => (
|
{(page) => (
|
||||||
<>
|
<>
|
||||||
<Row3 articles={page.slice(0, 3)} />
|
<Row3 articles={page.slice(0, 3)} />
|
||||||
<Row3 articles={page.slice(3, 6)} />
|
<Row3 articles={page.slice(3, 6)} />
|
||||||
<Row3 articles={page.slice(6, 9)} />
|
<Row3 articles={page.slice(6, 9)} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
|
||||||
<Show when={isLoadMoreButtonVisible()}>
|
<Show when={isLoadMoreButtonVisible()}>
|
||||||
<p class="load-more-container">
|
<p class="load-more-container">
|
||||||
<button class="button" onClick={loadMore}>
|
<button class="button" onClick={loadMore}>
|
||||||
{t('Load more')}
|
{t('Load more')}
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</Show>
|
|
||||||
</Show>
|
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper'
|
||||||
import { Shout } from '../../../graphql/types.gen'
|
import { Shout } from '../../../graphql/types.gen'
|
||||||
import { ArticleCard } from '../../Feed/ArticleCard'
|
import { ArticleCard } from '../../Feed/ArticleCard'
|
||||||
import { Icon } from '../Icon'
|
import { Icon } from '../Icon'
|
||||||
|
import { ShowOnlyOnClient } from '../ShowOnlyOnClient'
|
||||||
|
|
||||||
import { SwiperRef } from './swiper'
|
import { SwiperRef } from './swiper'
|
||||||
|
|
||||||
|
@ -25,65 +26,67 @@ export const ArticleCardSwiper = (props: Props) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={clsx(styles.Swiper, styles.articleMode, styles.ArticleCardSwiper)}>
|
<ShowOnlyOnClient>
|
||||||
<Show when={props.title}>
|
<div class={clsx(styles.Swiper, styles.articleMode, styles.ArticleCardSwiper)}>
|
||||||
<h2 class={styles.sliderTitle}>{props.title}</h2>
|
<Show when={props.title}>
|
||||||
</Show>
|
<h2 class={styles.sliderTitle}>{props.title}</h2>
|
||||||
<div class={styles.container}>
|
|
||||||
<Show when={props.slides.length > 0}>
|
|
||||||
<div class={styles.holder}>
|
|
||||||
<swiper-container
|
|
||||||
ref={(el) => (mainSwipeRef.current = el)}
|
|
||||||
centered-slides={true}
|
|
||||||
observer={true}
|
|
||||||
space-between={10}
|
|
||||||
breakpoints={{
|
|
||||||
576: { spaceBetween: 20, slidesPerView: 1.5 },
|
|
||||||
992: { spaceBetween: 52, slidesPerView: 1.5 },
|
|
||||||
}}
|
|
||||||
round-lengths={true}
|
|
||||||
loop={true}
|
|
||||||
speed={800}
|
|
||||||
autoplay={{
|
|
||||||
disableOnInteraction: false,
|
|
||||||
delay: 6000,
|
|
||||||
pauseOnMouseEnter: true,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<For each={props.slides}>
|
|
||||||
{(slide, index) => (
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore
|
|
||||||
<swiper-slide virtual-index={index()}>
|
|
||||||
<ArticleCard
|
|
||||||
article={slide}
|
|
||||||
settings={{
|
|
||||||
additionalClass: 'swiper-slide',
|
|
||||||
isFloorImportant: true,
|
|
||||||
isWithCover: true,
|
|
||||||
nodate: true,
|
|
||||||
}}
|
|
||||||
desktopCoverSize="L"
|
|
||||||
/>
|
|
||||||
</swiper-slide>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
</swiper-container>
|
|
||||||
<div
|
|
||||||
class={clsx(styles.navigation, styles.prev)}
|
|
||||||
onClick={() => mainSwipeRef.current.swiper.slidePrev()}
|
|
||||||
>
|
|
||||||
<Icon name="swiper-l-arr" class={styles.icon} />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class={clsx(styles.navigation, styles.next)}
|
|
||||||
onClick={() => mainSwipeRef.current.swiper.slideNext()}
|
|
||||||
>
|
|
||||||
<Icon name="swiper-r-arr" class={styles.icon} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
</Show>
|
||||||
|
<div class={styles.container}>
|
||||||
|
<Show when={props.slides.length > 0}>
|
||||||
|
<div class={styles.holder}>
|
||||||
|
<swiper-container
|
||||||
|
ref={(el) => (mainSwipeRef.current = el)}
|
||||||
|
centered-slides={true}
|
||||||
|
observer={true}
|
||||||
|
space-between={10}
|
||||||
|
breakpoints={{
|
||||||
|
576: { spaceBetween: 20, slidesPerView: 1.5 },
|
||||||
|
992: { spaceBetween: 52, slidesPerView: 1.5 },
|
||||||
|
}}
|
||||||
|
round-lengths={true}
|
||||||
|
loop={true}
|
||||||
|
speed={800}
|
||||||
|
autoplay={{
|
||||||
|
disableOnInteraction: false,
|
||||||
|
delay: 6000,
|
||||||
|
pauseOnMouseEnter: true,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<For each={props.slides}>
|
||||||
|
{(slide, index) => (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore
|
||||||
|
<swiper-slide virtual-index={index()}>
|
||||||
|
<ArticleCard
|
||||||
|
article={slide}
|
||||||
|
settings={{
|
||||||
|
additionalClass: 'swiper-slide',
|
||||||
|
isFloorImportant: true,
|
||||||
|
isWithCover: true,
|
||||||
|
nodate: true,
|
||||||
|
}}
|
||||||
|
desktopCoverSize="L"
|
||||||
|
/>
|
||||||
|
</swiper-slide>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</swiper-container>
|
||||||
|
<div
|
||||||
|
class={clsx(styles.navigation, styles.prev)}
|
||||||
|
onClick={() => mainSwipeRef.current.swiper.slidePrev()}
|
||||||
|
>
|
||||||
|
<Icon name="swiper-l-arr" class={styles.icon} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class={clsx(styles.navigation, styles.next)}
|
||||||
|
onClick={() => mainSwipeRef.current.swiper.slideNext()}
|
||||||
|
>
|
||||||
|
<Icon name="swiper-r-arr" class={styles.icon} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ShowOnlyOnClient>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { PageContext } from '../renderer/types'
|
||||||
|
|
||||||
import { render } from 'vike/abort'
|
import { render } from 'vike/abort'
|
||||||
|
|
||||||
|
import { PRERENDERED_ARTICLES_COUNT } from '../components/Views/Topic'
|
||||||
import { apiClient } from '../utils/apiClient'
|
import { apiClient } from '../utils/apiClient'
|
||||||
|
|
||||||
export const onBeforeRender = async (pageContext: PageContext) => {
|
export const onBeforeRender = async (pageContext: PageContext) => {
|
||||||
|
@ -14,7 +15,12 @@ export const onBeforeRender = async (pageContext: PageContext) => {
|
||||||
throw render(404)
|
throw render(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageProps: PageProps = { topic, seo: { title: topic.title } }
|
const topicShouts = await apiClient.getShouts({
|
||||||
|
filters: { topic: topic.slug },
|
||||||
|
limit: PRERENDERED_ARTICLES_COUNT,
|
||||||
|
})
|
||||||
|
|
||||||
|
const pageProps: PageProps = { topic, topicShouts, seo: { title: topic.title } }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pageContext: {
|
pageContext: {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import type { PageProps } from './types'
|
import type { PageProps } from './types'
|
||||||
|
|
||||||
import { createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js'
|
import { createEffect, createMemo, createSignal, on, onCleanup, onMount, Show } from 'solid-js'
|
||||||
|
|
||||||
|
import { Loading } from '../components/_shared/Loading'
|
||||||
import { PageLayout } from '../components/_shared/PageLayout'
|
import { PageLayout } from '../components/_shared/PageLayout'
|
||||||
import { PRERENDERED_ARTICLES_COUNT, TopicView } from '../components/Views/Topic'
|
import { PRERENDERED_ARTICLES_COUNT, TopicView } from '../components/Views/Topic'
|
||||||
import { ReactionsProvider } from '../context/reactions'
|
import { ReactionsProvider } from '../context/reactions'
|
||||||
|
@ -16,7 +17,7 @@ export const TopicPage = (props: PageProps) => {
|
||||||
const [isLoaded, setIsLoaded] = createSignal(
|
const [isLoaded, setIsLoaded] = createSignal(
|
||||||
Boolean(props.topicShouts) && Boolean(props.topic) && props.topic.slug === slug(),
|
Boolean(props.topicShouts) && Boolean(props.topic) && props.topic.slug === slug(),
|
||||||
)
|
)
|
||||||
const [pageTitle, setPageTitle] = createSignal<string>()
|
|
||||||
const preload = () =>
|
const preload = () =>
|
||||||
Promise.all([
|
Promise.all([
|
||||||
loadShouts({ filters: { topic: slug() }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 }),
|
loadShouts({ filters: { topic: slug() }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 }),
|
||||||
|
@ -51,15 +52,15 @@ export const TopicPage = (props: PageProps) => {
|
||||||
const usePrerenderedData = props.topic?.slug === slug()
|
const usePrerenderedData = props.topic?.slug === slug()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageLayout title={pageTitle()}>
|
<PageLayout title={props.seo.title}>
|
||||||
<ReactionsProvider>
|
<ReactionsProvider>
|
||||||
<TopicView
|
<Show when={isLoaded()} fallback={<Loading />}>
|
||||||
title={(title) => setPageTitle(title)}
|
<TopicView
|
||||||
isLoaded={isLoaded()}
|
topic={usePrerenderedData ? props.topic : null}
|
||||||
topic={usePrerenderedData ? props.topic : null}
|
shouts={usePrerenderedData ? props.topicShouts : null}
|
||||||
shouts={usePrerenderedData ? props.topicShouts : null}
|
topicSlug={slug()}
|
||||||
topicSlug={slug()}
|
/>
|
||||||
/>
|
</Show>
|
||||||
</ReactionsProvider>
|
</ReactionsProvider>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user