home-tops

This commit is contained in:
Untone 2024-06-28 10:47:38 +03:00
parent ae207a02f2
commit 305bd23f9f
9 changed files with 59 additions and 37 deletions

View File

@ -4,6 +4,7 @@ import sassDts from 'vite-plugin-sass-dts'
const isVercel = Boolean(process?.env.VERCEL) const isVercel = Boolean(process?.env.VERCEL)
const isBun = Boolean(process.env.BUN) const isBun = Boolean(process.env.BUN)
export default defineConfig({ export default defineConfig({
ssr: true, ssr: true,
server: { server: {

View File

@ -6,11 +6,13 @@
}, },
"vcs": { "vcs": {
"defaultBranch": "dev", "defaultBranch": "dev",
"useIgnoreFile": true "useIgnoreFile": true,
"enabled": true,
"clientKind": "git"
}, },
"organizeImports": { "organizeImports": {
"enabled": true, "enabled": true,
"ignore": ["./api", "./gen"] "ignore": ["./gen"]
}, },
"formatter": { "formatter": {
"indentStyle": "space", "indentStyle": "space",

View File

@ -62,8 +62,8 @@ const getTitleAndSubtitle = (
title: string title: string
subtitle: string subtitle: string
} => { } => {
let title = article.title let title = article?.title || ''
let subtitle: string = article.subtitle || '' let subtitle: string = article?.subtitle || ''
if (!subtitle) { if (!subtitle) {
let titleParts = article.title?.split('. ') || [] let titleParts = article.title?.split('. ') || []
@ -220,7 +220,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
[styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode [styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode
})} })}
> >
<A href={`/article${props.article.slug}`}> <A href={`/article${props.article?.slug || ''}`}>
<div class={styles.shoutCardTitle}> <div class={styles.shoutCardTitle}>
<span class={styles.shoutCardLinkWrapper}> <span class={styles.shoutCardLinkWrapper}>
<span class={styles.shoutCardLinkContainer} innerHTML={title} /> <span class={styles.shoutCardLinkContainer} innerHTML={title} />
@ -229,7 +229,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
<Show when={!props.settings?.nosubtitle && subtitle}> <Show when={!props.settings?.nosubtitle && subtitle}>
<div class={styles.shoutCardSubtitle}> <div class={styles.shoutCardSubtitle}>
<span class={styles.shoutCardLinkContainer} innerHTML={subtitle} /> <span class={styles.shoutCardLinkContainer} innerHTML={subtitle || ''} />
</div> </div>
</Show> </Show>
</A> </A>

View File

@ -3,7 +3,7 @@ import { For, Show, createEffect, createMemo, createSignal, on } from 'solid-js'
import { useAuthors } from '~/context/authors' import { useAuthors } from '~/context/authors'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { useTopics } from '../../context/topics' import { useTopics } from '../../context/topics'
import { Shout, Topic } from '../../graphql/schema/core.gen' import { Author, Shout, Topic } from '../../graphql/schema/core.gen'
import { capitalize } from '../../utils/capitalize' import { capitalize } from '../../utils/capitalize'
import { splitToPages } from '../../utils/splitToPages' import { splitToPages } from '../../utils/splitToPages'
import Banner from '../Discours/Banner' import Banner from '../Discours/Banner'
@ -38,7 +38,7 @@ export interface HomeViewProps {
export const HomeView = (props: HomeViewProps) => { export const HomeView = (props: HomeViewProps) => {
const { t } = useLocalize() const { t } = useLocalize()
const { topAuthors } = useAuthors() const { topAuthors, addAuthors } = useAuthors()
const { topTopics, randomTopic } = useTopics() const { topTopics, randomTopic } = useTopics()
const [randomTopicArticles, setRandomTopicArticles] = createSignal<Shout[]>([]) const [randomTopicArticles, setRandomTopicArticles] = createSignal<Shout[]>([])
createEffect( createEffect(
@ -53,6 +53,9 @@ export const HomeView = (props: HomeViewProps) => {
}) })
const shouts = await shoutsByTopicLoader() const shouts = await shoutsByTopicLoader()
setRandomTopicArticles(shouts || []) setRandomTopicArticles(shouts || [])
shouts?.forEach((s: Shout) => addAuthors((s?.authors || []) as Author[]))
props.featuredShouts?.forEach((s: Shout) => addAuthors((s?.authors || []) as Author[]))
props.topRatedShouts?.forEach((s: Shout) => addAuthors((s?.authors || []) as Author[]))
} }
}, },
{ defer: true } { defer: true }
@ -84,7 +87,7 @@ export const HomeView = (props: HomeViewProps) => {
<Beside <Beside
beside={props.featuredShouts[9]} beside={props.featuredShouts[9]}
title={t('Top authors')} title={t('Top authors')}
values={topAuthors()} values={topAuthors?.() || []}
wrapper={'author'} wrapper={'author'}
nodate={true} nodate={true}
/> />
@ -96,7 +99,7 @@ export const HomeView = (props: HomeViewProps) => {
<Row1 article={props.featuredShouts[16]} nodate={true} /> <Row1 article={props.featuredShouts[16]} nodate={true} />
<Row3 articles={props.featuredShouts.slice(17, 20)} nodate={true} /> <Row3 articles={props.featuredShouts.slice(17, 20)} nodate={true} />
<Row3 <Row3
articles={props.topCommentedShouts.slice(0, 3)} articles={props.topCommentedShouts?.slice(0, 3) || []}
header={<h2>{t('Top commented')}</h2>} header={<h2>{t('Top commented')}</h2>}
nodate={true} nodate={true}
/> />

View File

@ -23,7 +23,8 @@ export const ArticleCardSwiper = (props: Props) => {
let mainSwipeRef: SwiperRef | null let mainSwipeRef: SwiperRef | null
onMount(async () => { onMount(async () => {
if (props.slides.length > 1) { if (props.slides?.length > 1) {
console.debug(props.slides)
const { register } = await import('swiper/element/bundle') const { register } = await import('swiper/element/bundle')
register() register()
SwiperCore.use([Pagination, Navigation, Manipulation]) SwiperCore.use([Pagination, Navigation, Manipulation])
@ -34,16 +35,16 @@ export const ArticleCardSwiper = (props: Props) => {
<ShowOnlyOnClient> <ShowOnlyOnClient>
<div <div
class={clsx({ class={clsx({
[styles.Swiper]: props.slides.length > 1, [styles.Swiper]: props.slides?.length > 1,
[styles.articleMode]: true, [styles.articleMode]: true,
[styles.ArticleCardSwiper]: props.slides.length > 1 [styles.ArticleCardSwiper]: props.slides?.length > 1
})} })}
> >
<Show when={props.title}> <Show when={props.title}>
<h2 class={styles.sliderTitle}>{props.title}</h2> <h2 class={styles.sliderTitle}>{props.title}</h2>
</Show> </Show>
<div class={styles.container}> <div class={styles.container}>
<Show when={props.slides.length > 0}> <Show when={props.slides?.length > 0}>
<Show when={props.slides.length !== 1} fallback={<Row1 article={props.slides[0]} />}> <Show when={props.slides.length !== 1} fallback={<Row1 article={props.slides[0]} />}>
<Show when={props.slides.length !== 2} fallback={<Row2 articles={props.slides} />}> <Show when={props.slides.length !== 2} fallback={<Row2 articles={props.slides} />}>
<div class={styles.holder}> <div class={styles.holder}>

View File

@ -1,7 +1,9 @@
import { type RouteDefinition, type RouteSectionProps, createAsync } from '@solidjs/router' import { type RouteDefinition, type RouteSectionProps, createAsync } from '@solidjs/router'
import { Show, Suspense, createEffect, createSignal, on, onMount } from 'solid-js' import { Show, Suspense, createEffect, createSignal, on, onMount } from 'solid-js'
import { LoadShoutsOptions } from '~/graphql/schema/core.gen'
import { loadShouts } from '~/lib/api' import { loadShouts } from '~/lib/api'
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll' import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
import { byStat } from '~/utils/sortby'
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'
@ -11,32 +13,33 @@ import { ReactionsProvider } from '../context/reactions'
export const SHOUTS_PER_PAGE = 20 export const SHOUTS_PER_PAGE = 20
const fetchHomeTopData = async () => { const fetchHomeTopData = async () => {
const limit = 20
const topCommentedLoader = loadShouts({ const topCommentedLoader = loadShouts({
filters: { featured: true }, filters: { featured: true },
limit order_by: 'comments_stat',
limit: 10
}) })
const topMonthLoader = loadShouts({ const daysago = Date.now() - 30 * 24 * 60 * 60 * 1000
filters: { featured: true }, const after = Math.floor(daysago / 1000)
limit const options: LoadShoutsOptions = {
}) filters: {
featured: true,
const topViewedLoader = loadShouts({ after
filters: { featured: true }, },
limit order_by: 'likes_stat',
}) limit: 10
}
const topMonthLoader = loadShouts({ ...options })
const topRatedLoader = loadShouts({ const topRatedLoader = loadShouts({
filters: { featured: true }, filters: { featured: true },
limit order_by: 'likes_stat',
limit: 10
}) })
return { return {
topCommentedShouts: await topCommentedLoader(),
topMonthShouts: await topMonthLoader(),
topRatedShouts: await topRatedLoader(), topRatedShouts: await topRatedLoader(),
topViewedShouts: await topViewedLoader() topMonthShouts: await topMonthLoader(),
topCommentedShouts: await topCommentedLoader()
} as Partial<HomeViewProps> } as Partial<HomeViewProps>
} }
@ -69,12 +72,19 @@ export default function HomePage(props: RouteSectionProps<HomeViewProps>) {
// async ssr-friendly router-level cached data source // async ssr-friendly router-level cached data source
const data = createAsync(async (prev?: HomeViewProps) => { const data = createAsync(async (prev?: HomeViewProps) => {
const featuredShoutsLoader = featuredLoader(featuredOffset()) const featuredShoutsLoader = featuredLoader(featuredOffset())
const featuredShouts = await featuredShoutsLoader() const featuredShouts = [
return { ...(prev?.featuredShouts || []),
...((await featuredShoutsLoader()) || props.data?.featuredShouts || [])
]
const sortFn = byStat('viewed')
const topViewedShouts = featuredShouts?.sort(sortFn) || []
const result = {
...prev, ...prev,
...props.data, ...props.data,
featuredShouts: featuredShouts || prev?.featuredShouts || props.data?.featuredShouts topViewedShouts,
featuredShouts
} }
return result
}) })
const [canLoadMoreFeatured, setCanLoadMoreFeatured] = createSignal(false) const [canLoadMoreFeatured, setCanLoadMoreFeatured] = createSignal(false)

View File

@ -44,10 +44,11 @@ export const ArticlePage = (props: RouteSectionProps<{ article: Shout }>) => {
// wrapped by the returned tracking function is notified of a change` // wrapped by the returned tracking function is notified of a change`
createReaction(() => { createReaction(() => {
if (article()) { if (article()) {
console.debug('[routes.slug] article signal changed once')
window.gtag?.('event', 'page_view', { window.gtag?.('event', 'page_view', {
page_title: article()?.title, page_title: article()?.title,
page_location: window.location.href, page_location: window.location.href,
page_path: window.location.pathname, page_path: window.location.pathname
}) })
} }
}) })

View File

@ -19,10 +19,12 @@ export const loadGAScript = (id: string) => {
export const initGA = (id: string) => { export const initGA = (id: string) => {
const w = window as Window const w = window as Window
if (w) { if (w) {
// @ts-ignore
// biome-ignore lint/suspicious/noExplicitAny: ga-script // biome-ignore lint/suspicious/noExplicitAny: ga-script
w.dataLayer = (w.dataLayer as any) || [] w.dataLayer = (w.dataLayer as any) || []
// biome-ignore lint/suspicious/noExplicitAny: ga-script // biome-ignore lint/suspicious/noExplicitAny: ga-script
function gtag(...args: any[]) { function gtag(...args: any[]) {
// @ts-ignore
w.dataLayer.push(args) w.dataLayer.push(args)
} }
gtag('js', new Date()) gtag('js', new Date())

View File

@ -1,4 +1,4 @@
import type { Author, Reaction, Shout, Topic, TopicStat } from '../graphql/schema/core.gen' import type { Author, Maybe, Reaction, Shout, Topic, TopicStat } from '../graphql/schema/core.gen'
// biome-ignore lint/suspicious/noExplicitAny: sort by first char // biome-ignore lint/suspicious/noExplicitAny: sort by first char
export const byFirstChar = (a: { name?: any; title?: any }, b: { name?: any; title?: any }) => export const byFirstChar = (a: { name?: any; title?: any }, b: { name?: any; title?: any }) =>
@ -26,8 +26,10 @@ export const byLength = (
return 0 return 0
} }
export type SomeStat = { [x: string]: Maybe<number> } | undefined | null
export const byStat = (metric: string) => { export const byStat = (metric: string) => {
return (a: { stat: { [x: string]: number } }, b: { stat: { [x: string]: number } }) => { return (a: { stat?: SomeStat }, b: { stat?: SomeStat }) => {
const aStat = a.stat?.[metric] ?? 0 const aStat = a.stat?.[metric] ?? 0
const bStat = b.stat?.[metric] ?? 0 const bStat = b.stat?.[metric] ?? 0
return aStat - bStat return aStat - bStat