authors-all-fix+slug-404
This commit is contained in:
parent
d64f68579c
commit
f4f4e80816
|
@ -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 || ''}`}>
|
||||
|
|
|
@ -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)}>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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'}
|
||||
/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { gql } from '@urql/core'
|
||||
|
||||
export default gql`
|
||||
query {
|
||||
query GetAuthorsAllQuery {
|
||||
get_authors_all {
|
||||
id
|
||||
slug
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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) => {
|
||||
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 }
|
||||
)
|
||||
)
|
||||
createEffect(on(article, (a?: Shout) => {
|
||||
if (!a) return
|
||||
window?.gtag?.('event', 'page_view', {
|
||||
page_title: a.title,
|
||||
page_location: window?.location.href || '',
|
||||
page_path: loc.pathname
|
||||
})
|
||||
}, { 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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue
Block a user