authors-all-fix+slug-404

This commit is contained in:
Untone 2024-07-07 16:48:53 +03:00
parent d64f68579c
commit f4f4e80816
14 changed files with 179 additions and 134 deletions

View File

@ -1,15 +1,16 @@
import { createPopper } from '@popperjs/core'
import { clsx } from 'clsx'
// import { install } from 'ga-gtag'
import { createPopper } from '@popperjs/core'
import { Link, Meta } from '@solidjs/meta'
import { A, useSearchParams } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js'
import { isServer } from 'solid-js/web'
import { Link, Meta } from '@solidjs/meta'
import { useFeed } from '~/context/feed'
import { useLocalize } from '~/context/localize'
import { useReactions } from '~/context/reactions'
import { useSession } from '~/context/session'
import { DEFAULT_HEADER_OFFSET, useUI } from '~/context/ui'
import type { Author, Maybe, Shout, Topic } from '~/graphql/schema/core.gen'
import type { Author, Maybe, QueryLoad_Reactions_ByArgs, Shout, Topic } from '~/graphql/schema/core.gen'
import { isCyrillic } from '~/intl/translate'
import { getImageUrl, getOpenGraphImageUrl } from '~/lib/getImageUrl'
import { MediaItem } from '~/types/mediaitem'
@ -34,8 +35,6 @@ import { CommentsTree } from './CommentsTree'
import { SharePopup, getShareUrl } from './SharePopup'
import { ShoutRatingControl } from './ShoutRatingControl'
import { A, useSearchParams } from '@solidjs/router'
import { useFeed } from '~/context/feed'
import stylesHeader from '../Nav/Header/Header.module.scss'
import styles from './Article.module.scss'
@ -79,24 +78,24 @@ export const FullArticle = (props: Props) => {
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
const { addSeen } = useFeed()
const formattedDate = createMemo(() => formatDate(new Date((props.article?.published_at || 0) * 1000)))
const formattedDate = createMemo(() => formatDate(new Date((props.article.published_at || 0) * 1000)))
const canEdit = createMemo(
() =>
Boolean(author()?.id) &&
(props.article?.authors?.some((a) => Boolean(a) && a?.id === author().id) ||
props.article?.created_by?.id === author().id ||
(props.article.authors?.some((a) => Boolean(a) && a?.id === author().id) ||
props.article.created_by?.id === author().id ||
session()?.user?.roles?.includes('editor'))
)
const mainTopic = createMemo(() => {
const mainTopicSlug = (props.article?.topics?.length || 0) > 0 ? props.article.main_topic : null
const mt = props.article?.topics?.find((tpc: Maybe<Topic>) => tpc?.slug === mainTopicSlug)
const mainTopicSlug = (props.article.topics?.length || 0) > 0 ? props.article.main_topic : null
const mt = props.article.topics?.find((tpc: Maybe<Topic>) => tpc?.slug === mainTopicSlug)
if (mt) {
mt.title = lang() === 'en' ? capitalize(mt.slug.replace(/-/, ' ')) : mt.title
return mt
}
return props.article?.topics?.[0]
return props.article.topics?.[0]
})
const handleBookmarkButtonClick = (ev: MouseEvent | undefined) => {
@ -107,10 +106,10 @@ export const FullArticle = (props: Props) => {
}
const body = createMemo(() => {
if (props.article?.layout === 'literature') {
if (props.article.layout === 'literature') {
try {
if (props.article?.media) {
const media = JSON.parse(props.article?.media)
if (props.article.media) {
const media = JSON.parse(props.article.media)
if (media.length > 0) {
return media[0].body
}
@ -119,7 +118,7 @@ export const FullArticle = (props: Props) => {
console.error(error)
}
}
return props.article?.body || ''
return props.article.body || ''
})
const imageUrls = createMemo(() => {
@ -145,7 +144,7 @@ export const FullArticle = (props: Props) => {
const media = createMemo<MediaItem[]>(() => {
try {
return JSON.parse(props.article?.media || '[]')
return JSON.parse(props.article.media || '[]')
} catch {
return []
}
@ -304,7 +303,8 @@ export const FullArticle = (props: Props) => {
onMount(async () => {
// install('G-LQ4B87H8C2')
await loadReactionsBy({ by: { shout: props.article.slug } })
const opts: QueryLoad_Reactions_ByArgs = { by: { shout: props.article.slug }, limit: 999, offset: 0 }
const _rrr = await loadReactionsBy(opts)
addSeen(props.article.slug)
setIsReactionsLoaded(true)
document.title = props.article.title
@ -326,18 +326,18 @@ export const FullArticle = (props: Props) => {
})
})
const cover = props.article.cover ?? 'production/image/logo_image.png'
const cover = props.article.cover || 'production/image/logo_image.png'
const ogImage = getOpenGraphImageUrl(cover, {
title: props.article.title,
topic: mainTopic()?.title || '',
author: props.article?.authors?.[0]?.name || '',
author: props.article.authors?.[0]?.name || '',
width: 1200
})
const description = getArticleDescription(props.article.description || body() || media()[0]?.body)
const ogTitle = props.article.title
const keywords = getArticleKeywords(props.article)
const shareUrl = getShareUrl({ pathname: `/${props.article.slug}` })
const shareUrl = getShareUrl({ pathname: `/${props.article.slug || ''}` })
const getAuthorName = (a: Author) => {
return lang() === 'en' && isCyrillic(a.name || '') ? capitalize(a.slug.replace(/-/, ' ')) : a.name
}
@ -363,19 +363,19 @@ export const FullArticle = (props: Props) => {
onClick={handleArticleBodyClick}
>
{/*TODO: Check styles.shoutTopic*/}
<Show when={props.article?.layout !== 'audio'}>
<Show when={props.article.layout !== 'audio'}>
<div class={styles.shoutHeader}>
<Show when={mainTopic()}>
<CardTopic title={mainTopic()?.title || ''} slug={mainTopic()?.slug || ''} />
</Show>
<h1>{props.article?.title || ''}</h1>
<Show when={props.article?.subtitle}>
<h4>{props.article?.subtitle || ''}</h4>
<h1>{props.article.title || ''}</h1>
<Show when={props.article.subtitle}>
<h4>{props.article.subtitle || ''}</h4>
</Show>
<div class={styles.shoutAuthor}>
<For each={props.article?.authors}>
<For each={props.article.authors}>
{(a: Maybe<Author>, index: () => number) => (
<>
<Show when={index() > 0}>, </Show>
@ -386,39 +386,39 @@ export const FullArticle = (props: Props) => {
</div>
<Show
when={
props.article?.cover &&
props.article?.layout !== 'video' &&
props.article?.layout !== 'image'
props.article.cover &&
props.article.layout !== 'video' &&
props.article.layout !== 'image'
}
>
<figure class="img-align-column">
<Image
width={800}
alt={props.article?.cover_caption || ''}
src={props.article?.cover || ''}
alt={props.article.cover_caption || ''}
src={props.article.cover || ''}
/>
<figcaption innerHTML={props.article?.cover_caption || ''} />
<figcaption innerHTML={props.article.cover_caption || ''} />
</figure>
</Show>
</div>
</Show>
<Show when={props.article?.lead}>
<section class={styles.lead} innerHTML={props.article?.lead || ''} />
<Show when={props.article.lead}>
<section class={styles.lead} innerHTML={props.article.lead || ''} />
</Show>
<Show when={props.article?.layout === 'audio'}>
<Show when={props.article.layout === 'audio'}>
<AudioHeader
title={props.article?.title || ''}
cover={props.article?.cover || ''}
title={props.article.title || ''}
cover={props.article.cover || ''}
artistData={media()?.[0]}
topic={mainTopic() as Topic}
/>
<Show when={media().length > 0}>
<div class="media-items">
<AudioPlayer media={media()} articleSlug={props.article?.slug || ''} body={body()} />
<AudioPlayer media={media()} articleSlug={props.article.slug || ''} body={body()} />
</div>
</Show>
</Show>
<Show when={media() && props.article?.layout === 'video'}>
<Show when={media() && props.article.layout === 'video'}>
<div class="media-items">
<For each={media() || []}>
{(m: MediaItem) => (
@ -542,7 +542,7 @@ export const FullArticle = (props: Props) => {
<Popover content={t('Edit')}>
{(triggerRef: (el: HTMLElement) => void) => (
<div class={styles.shoutStatsItem} ref={triggerRef}>
<A href={`/edit/${props.article?.id}`} class={styles.shoutStatsItemInner}>
<A href={`/edit/${props.article.id}`} class={styles.shoutStatsItemInner}>
<Icon name="pencil-outline" class={styles.icon} />
<Icon name="pencil-outline-hover" class={clsx(styles.icon, styles.iconHover)} />
</A>
@ -577,9 +577,9 @@ export const FullArticle = (props: Props) => {
</div>
</Show>
<Show when={props.article?.topics?.length}>
<Show when={props.article.topics?.length}>
<div class={styles.topicsList}>
<For each={props.article?.topics || []}>
<For each={props.article.topics || []}>
{(topic) => (
<div class={styles.shoutTopic}>
<A href={`/topic/${topic?.slug || ''}`}>

View File

@ -1,12 +1,12 @@
import { clsx } from 'clsx'
import { For, Show, createEffect, createSignal, on } from 'solid-js'
import { useAuthors } from '~/context/authors'
import { useGraphQL } from '~/context/graphql'
import { useLocalize } from '~/context/localize'
import loadAuthorsByQuery from '~/graphql/query/core/authors-load-by'
import { loadAuthors } from '~/graphql/api/public'
import { Author } from '~/graphql/schema/core.gen'
import { AuthorBadge } from '../Author/AuthorBadge'
import { InlineLoader } from '../InlineLoader'
import { AUTHORS_PER_PAGE } from '../Views/AllAuthors/AllAuthors'
import { Button } from '../_shared/Button'
import styles from './AuthorsList.module.scss'
@ -17,7 +17,7 @@ type Props = {
allAuthorsLength?: number
}
const PAGE_SIZE = 20
// pagination handling, loadAuthors cached from api, addAuthors to context
export const AuthorsList = (props: Props) => {
const { t } = useLocalize()
@ -27,18 +27,17 @@ export const AuthorsList = (props: Props) => {
const [loading, setLoading] = createSignal(false)
const [currentPage, setCurrentPage] = createSignal({ shouts: 0, followers: 0 })
const [allLoaded, setAllLoaded] = createSignal(false)
const { query } = useGraphQL()
const fetchAuthors = async (queryType: Props['query'], page: number) => {
setLoading(true)
const offset = PAGE_SIZE * page
const resp = await query(loadAuthorsByQuery, {
const offset = AUTHORS_PER_PAGE * page
const fetcher = await loadAuthors({
by: { order: queryType },
limit: PAGE_SIZE,
limit: AUTHORS_PER_PAGE,
offset
})
const result = resp?.data?.load_authors_by
if ((result?.length || 0) > 0) {
const result = await fetcher()
if (result) {
addAuthors([...result])
if (queryType === 'shouts') {
setAuthorsByShouts((prev) => [...(prev || []), ...result])
@ -70,17 +69,7 @@ export const AuthorsList = (props: Props) => {
)
const authorsList = () => (props.query === 'shouts' ? authorsByShouts() : authorsByFollowers())
// TODO: do it with backend
// createEffect(() => {
// if (props.searchQuery) {
// // search logic
// }
// })
createEffect(() => {
setAllLoaded(props.allAuthorsLength === authorsList.length)
})
createEffect(() => setAllLoaded(props.allAuthorsLength === authorsList.length))
return (
<div class={clsx(styles.AuthorsList, props.class)}>

View File

@ -8,9 +8,8 @@ import { composeMediaItems } from '~/utils/composeMediaItems'
import { AudioPlayer } from '../../Article/AudioPlayer'
import styles from './AudioUploader.module.scss'
if (!isServer && window) window.Buffer = Buffer
console.debug('buffer patch passed')
// console.debug('buffer patch passed')
type Props = {
class?: string

View File

@ -220,7 +220,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
[styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode
})}
>
<A href={`/article${props.article?.slug || ''}`}>
<A href={`/${props.article?.slug || ''}`}>
<div class={styles.shoutCardTitle}>
<span class={styles.shoutCardLinkWrapper}>
<span class={styles.shoutCardLinkContainer} innerHTML={title} />

View File

@ -191,7 +191,7 @@ export const Header = (props: Props) => {
<div class={styles.articleHeader}>{props.title}</div>
</Show>
<div class={clsx(styles.mainNavigation, { [styles.fixed]: fixed() })}>
<ul class="view-switcher" onClick={() => !fixed() && toggleFixed()}>
<ul class="view-switcher">
<Link
onMouseOver={() => toggleSubnavigation(true, setIsZineVisible)}
onMouseOut={hideSubnavigation}

View File

@ -10,6 +10,7 @@ import type { Author } from '~/graphql/schema/core.gen'
import enKeywords from '~/intl/locales/en/keywords.json'
import ruKeywords from '~/intl/locales/ru/keywords.json'
import { authorLetterReduce, translateAuthor } from '~/intl/translate'
import { dummyFilter } from '~/lib/dummyFilter'
import { getImageUrl } from '~/lib/getImageUrl'
import { scrollHandler } from '~/utils/scroll'
import { AuthorsList } from '../../AuthorsList'
@ -27,23 +28,29 @@ export const ABC = {
en: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#'
}
// useAuthors sorted from context, set filter/sort
export const AllAuthors = (props: Props) => {
const { t, lang } = useLocalize()
const [searchQuery, setSearchQuery] = createSignal('')
const alphabet = createMemo(() => ABC[lang()] || ABC['ru'])
const [searchParams, changeSearchParams] = useSearchParams<{ by?: string }>()
const { authorsSorted, addAuthors, setAuthorsSort } = useAuthors()
const { authorsSorted, setAuthorsSort } = useAuthors()
const authors = createMemo(() => props.authors || authorsSorted())
// filter
const [searchQuery, setSearchQuery] = createSignal('')
const [filteredAuthors, setFilteredAuthors] = createSignal<Author[]>([])
createEffect(() =>
authors() && setFilteredAuthors((_prev: Author[]) => dummyFilter(authors(), searchQuery(), lang()) as Author[])
)
// sort by
onMount(() => !searchParams?.by && changeSearchParams({ by: 'name' }))
createEffect(on(() => searchParams?.by || 'name', setAuthorsSort || ((_) => null), {}))
createEffect(on(() => props.authors || [], addAuthors || ((_) => null), {}))
const filteredAuthors = createMemo(() => {
const query = searchQuery().toLowerCase()
return authorsSorted?.()?.filter((a: Author) => a?.name?.toLowerCase().includes(query)) || []
})
// store by first char
const byLetterFiltered = createMemo<{ [letter: string]: Author[] }>(() => {
console.debug('[components.AllAuthors] byLetterFiltered')
return (
filteredAuthors()?.reduce(
(acc, author: Author) => authorLetterReduce(acc, author, lang()),
@ -65,7 +72,7 @@ export const AllAuthors = (props: Props) => {
const description = createMemo(() => t('List of authors of the open editorial community'))
return (
<div class={clsx(styles.allAuthorsPage, 'wide-container')}>
<div class={clsx([styles.allAuthorsPage, 'wide-container'])}>
<Meta name="descprition" content={description() || ''} />
<Meta name="keywords" content={lang() === 'ru' ? ruKeywords[''] : enKeywords['']} />
<Meta name="og:type" content="article" />
@ -165,9 +172,9 @@ export const AllAuthors = (props: Props) => {
)}
</For>
</Show>
<Show when={searchParams?.by !== 'name' && props.isLoaded}>
<Show when={authors().length && searchParams?.by !== 'name' && props.isLoaded}>
<AuthorsList
allAuthorsLength={authorsSorted?.()?.length || 0}
allAuthorsLength={authors().length}
searchQuery={searchQuery()}
query={searchParams?.by === 'followers' ? 'followers' : 'shouts'}
/>

View File

@ -173,13 +173,13 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => {
}
const contextValue: AuthorsContextType = {
loadAllAuthors,
authorsEntities,
authorsSorted,
addAuthors,
addAuthor,
loadAuthor,
loadAuthors: loadAuthorsPaginated,
loadAuthors: loadAuthorsPaginated, // with stat
loadAllAuthors, // without stat
topAuthors,
authorsByTopic,
setAuthorsSort

View File

@ -2,10 +2,10 @@ import type { JSX } from 'solid-js'
import { createContext, onCleanup, useContext } from 'solid-js'
import { createStore, reconcile } from 'solid-js/store'
import { loadReactions } from '~/graphql/api/public'
import createReactionMutation from '~/graphql/mutation/core/reaction-create'
import destroyReactionMutation from '~/graphql/mutation/core/reaction-destroy'
import updateReactionMutation from '~/graphql/mutation/core/reaction-update'
import getReactionsByQuery from '~/graphql/query/core/reactions-load-by'
import {
MutationCreate_ReactionArgs,
MutationUpdate_ReactionArgs,
@ -37,11 +37,11 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => {
const [reactionsByShout, setReactionsByShout] = createStore<Record<number, Reaction[]>>({})
const { t } = useLocalize()
const { showSnackbar } = useSnackbar()
const { query, mutation } = useGraphQL()
const { mutation } = useGraphQL()
const loadReactionsBy = async (opts: QueryLoad_Reactions_ByArgs): Promise<Reaction[]> => {
const resp = await query(getReactionsByQuery, opts)
const result = resp?.data?.load_reactions_by || []
const fetcher = await loadReactions({ ...opts })
const result = (await fetcher()) || []
const newReactionsByShout: Record<string, Reaction[]> = {}
const newReactionEntities = result.reduce(
(acc: { [reaction_id: number]: Reaction }, reaction: Reaction) => {

View File

@ -58,6 +58,7 @@ export const loadShouts = (options: LoadShoutsOptions) => {
export const loadReactions = (options: QueryLoad_Reactions_ByArgs) => {
const page = `${options.offset || 0}-${(options?.limit || 0) + (options.offset || 0)}`
const filter = new URLSearchParams(options.by as Record<string, string>)
console.debug(options)
return cache(async () => {
const resp = await defaultClient.query(loadReactionsByQuery, { ...options }).toPromise()
const result = resp?.data?.load_reactions_by
@ -66,6 +67,7 @@ export const loadReactions = (options: QueryLoad_Reactions_ByArgs) => {
}
export const getShout = (options: QueryGet_ShoutArgs) => {
// console.debug('[lib.api] get shout cached fetcher returned', defaultClient)
return cache(
async () => {
const resp = await defaultClient.query(loadReactionsByQuery, { ...options }).toPromise()

View File

@ -1,7 +1,7 @@
import { gql } from '@urql/core'
export default gql`
query {
query GetAuthorsAllQuery {
get_authors_all {
id
slug

View File

@ -1,8 +1,8 @@
import { gql } from '@urql/core'
export default gql`
query AuthorsAllQuery($by: AuthorsBy!, $limit: Int, $offset: Int) {
get_authors(by: $by, limit: $limit, offset: $offset) {
query LoadAuthorsBy($by: AuthorsBy!, $limit: Int, $offset: Int) {
load_authors_by(by: $by, limit: $limit, offset: $offset) {
id
slug
name

View File

@ -1,11 +1,12 @@
import { HttpStatusCode } from '@solidjs/start'
import { onMount } from 'solid-js'
import { FourOuFourView } from '../components/Views/FourOuFour'
import { PageLayout } from '../components/_shared/PageLayout'
import { useLocalize } from '../context/localize'
export default () => {
const { t } = useLocalize()
onMount(() => console.info('404 page'))
return (
<PageLayout isHeaderFixed={false} hideFooter={true} title={t('Nothing is here')}>
<FourOuFourView />

View File

@ -1,8 +1,9 @@
import { RouteSectionProps, createAsync, useLocation, useParams } from '@solidjs/router'
import { ErrorBoundary, Suspense, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
import { FourOuFourView } from '~/components/Views/FourOuFour'
import { RouteDefinition, RouteSectionProps, createAsync, redirect, useLocation, useParams } from '@solidjs/router'
import { HttpStatusCode } from '@solidjs/start'
import { ErrorBoundary, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
import { Loading } from '~/components/_shared/Loading'
import { gaIdentity } from '~/config'
import { useFeed } from '~/context/feed'
import { useLocalize } from '~/context/localize'
import { getShout } from '~/graphql/api/public'
import type { Shout } from '~/graphql/schema/core.gen'
@ -11,71 +12,83 @@ import { FullArticle } from '../components/Article/FullArticle'
import { PageLayout } from '../components/_shared/PageLayout'
import { ReactionsProvider } from '../context/reactions'
const fetchShout = async (slug: string) => {
const fetchShout = async (slug: string): Promise<Shout> => {
const shoutLoader = getShout({ slug })
return await shoutLoader()
const shout = await shoutLoader()
if (!shout) {
throw new Error('Shout not found')
}
return shout
}
export const route = {
load: async ({ params }: RouteSectionProps<{ article: Shout }>) => await fetchShout(params.slug)
export const route: RouteDefinition = {
load: async ({ params }) => {
try {
return await fetchShout(params.slug)
} catch (error) {
console.error('Error loading shout:', error)
throw new Response(null, {
status: 404,
statusText: 'Not Found'
})
}
}
}
export default (props: RouteSectionProps<{ article: Shout }>) => {
const params = useParams()
const loc = useLocation()
const article = createAsync(async () => props.data.article || (await fetchShout(params.slug)))
const { articleEntities } = useFeed()
const { t } = useLocalize()
const [scrollToComments, setScrollToComments] = createSignal<boolean>(false)
const title = createMemo(
() => `${article()?.authors?.[0]?.name || t('Discours')} :: ${article()?.title || ''}`
)
const article = createAsync(async () => {
if (params.slug && articleEntities?.()) {
return articleEntities()?.[params.slug] || props.data.article || await fetchShout(params.slug)
}
throw redirect('/404', { status: 404 })
})
const title = createMemo(() => `${article()?.authors?.[0]?.name || t('Discours')} :: ${article()?.title || ''}`)
onMount(async () => {
if (gaIdentity) {
if (gaIdentity && article()?.id) {
try {
console.info('[routes.slug] mounted, connecting ga...')
await loadGAScript(gaIdentity)
initGA(gaIdentity)
console.debug('[routes.slug] Google Analytics connected successfully')
} catch (error) {
console.warn('[routes.slug] Failed to connect Google Analytics:', error)
console.warn('Failed to connect Google Analytics:', error)
}
}
})
createEffect(
on(
article,
(a?: Shout) => {
createEffect(on(article, (a?: Shout) => {
if (!a) return
console.debug('[routes.slug] article found')
window?.gtag?.('event', 'page_view', {
page_title: a.title,
page_location: window?.location.href || '',
page_path: loc.pathname
})
},
{ defer: true }
)
)
}, { defer: true }))
return (
<ErrorBoundary fallback={(_err) => <FourOuFourView />}>
<Suspense fallback={<Loading />}>
<ErrorBoundary fallback={() => <HttpStatusCode code={404} />}>
<Show when={article()?.id} fallback={<Loading />}>
<PageLayout
title={title()}
headerTitle={article()?.title || ''}
slug={article()?.slug}
articleBody={article()?.body}
cover={article()?.cover || ''}
scrollToComments={(value) => {
setScrollToComments(value)
}}
scrollToComments={(value) => setScrollToComments(value)}
>
<ReactionsProvider>
<FullArticle article={article() as Shout} scrollToComments={scrollToComments()} />
</ReactionsProvider>
</PageLayout>
</Suspense>
</Show>
</ErrorBoundary>
)
}

View File

@ -1,5 +1,5 @@
import { RouteDefinition, RouteLoadFuncArgs, type RouteSectionProps, createAsync } from '@solidjs/router'
import { Suspense, createReaction } from 'solid-js'
import { Suspense, createEffect, on } from 'solid-js'
import { AllAuthors } from '~/components/Views/AllAuthors'
import { AUTHORS_PER_PAGE } from '~/components/Views/AllAuthors/AllAuthors'
import { Loading } from '~/components/_shared/Loading'
@ -22,22 +22,56 @@ const fetchAllAuthors = async () => {
}
export const route = {
load: ({ location: { query } }: RouteLoadFuncArgs) =>
fetchAuthorsWithStat(Number.parseInt(query.offset), query.by || 'name')
load: async ({ location: { query } }: RouteLoadFuncArgs) => {
const by = query.by
const isAll = !by || by === 'name'
return {
authors: isAll && await fetchAllAuthors(),
topFollowedAuthors: await fetchAuthorsWithStat(10, 'followers'),
topShoutsAuthors: await fetchAuthorsWithStat(10, 'shouts')
} as AllAuthorsData
}
} satisfies RouteDefinition
export default function AllAuthorsPage(props: RouteSectionProps<{ authors: Author[] }>) {
type AllAuthorsData = { authors: Author[], topFollowedAuthors: Author[], topShoutsAuthors: Author[] }
// addAuthors to context
export default function AllAuthorsPage(props: RouteSectionProps<AllAuthorsData>) {
const { t } = useLocalize()
const { authorsSorted, addAuthors } = useAuthors()
const authors = createAsync<Author[]>(
async () => authorsSorted?.() || props.data.authors || (await fetchAllAuthors())
)
createReaction(() => typeof addAuthors === 'function' && addAuthors?.(authors() || []))
const { addAuthors } = useAuthors()
// async load data: from ssr or fetch
const data = createAsync<AllAuthorsData>(async () => {
if (props.data) return props.data
return {
authors: await fetchAllAuthors(),
topFollowedAuthors: await fetchAuthorsWithStat(10, 'followers'),
topShoutsAuthors: await fetchAuthorsWithStat(10, 'shouts')
} as AllAuthorsData
})
// update context when data is loaded
createEffect(on([data, () => addAuthors],
([data, aa])=> {
if(data && aa) {
aa(data.authors as Author[])
aa(data.topFollowedAuthors as Author[])
aa(data.topShoutsAuthors as Author[])
console.debug('[routes.author] added all authors:', data.authors)
}
}, { defer: true}
))
return (
<PageLayout withPadding={true} title={`${t('Discours')} :: ${t('All authors')}`}>
<ReactionsProvider>
<Suspense fallback={<Loading />}>
<AllAuthors authors={authors() || []} isLoaded={Boolean(authors())} />
<AllAuthors
isLoaded={Boolean(data()?.authors)}
authors={data()?.authors || []}
topFollowedAuthors={data()?.topFollowedAuthors}
topWritingAuthors={data()?.topShoutsAuthors}/>
</Suspense>
</ReactionsProvider>
</PageLayout>