author-shouts-loadmore
This commit is contained in:
parent
33a81d8ee7
commit
e176544e36
|
@ -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('. ') || []
|
||||||
|
@ -85,8 +85,8 @@ const getTitleAndSubtitle = (
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMainTopicTitle = (article: Shout, lng: string) => {
|
const getMainTopicTitle = (article: Shout, lng: string) => {
|
||||||
const mainTopicSlug = article?.main_topic || ''
|
const mainTopicSlug = article.main_topic || ''
|
||||||
const mainTopic = (article?.topics || []).find((tpc: Maybe<Topic>) => tpc?.slug === mainTopicSlug)
|
const mainTopic = (article.topics || []).find((tpc: Maybe<Topic>) => tpc?.slug === mainTopicSlug)
|
||||||
const mainTopicTitle =
|
const mainTopicTitle =
|
||||||
mainTopicSlug && lng === 'en' ? mainTopicSlug.replace(/-/, ' ') : mainTopic?.title || ''
|
mainTopicSlug && lng === 'en' ? mainTopicSlug.replace(/-/, ' ') : mainTopic?.title || ''
|
||||||
|
|
||||||
|
@ -109,29 +109,30 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
|
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
|
||||||
const [isCoverImageLoadError, setIsCoverImageLoadError] = createSignal(false)
|
const [isCoverImageLoadError, setIsCoverImageLoadError] = createSignal(false)
|
||||||
const [isCoverImageLoading, setIsCoverImageLoading] = createSignal(true)
|
const [isCoverImageLoading, setIsCoverImageLoading] = createSignal(true)
|
||||||
const description = descFromBody(props.article?.body)
|
const description = descFromBody(props.article.body)
|
||||||
const aspectRatio: Accessor<string> = () => LAYOUT_ASPECT[props.article?.layout as string]
|
const aspectRatio: Accessor<string> = () => LAYOUT_ASPECT[props.article.layout as string]
|
||||||
const [mainTopicTitle, mainTopicSlug] = getMainTopicTitle(props.article, lang())
|
const [mainTopicTitle, mainTopicSlug] = getMainTopicTitle(props.article, lang())
|
||||||
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
||||||
|
|
||||||
const formattedDate = createMemo<string>(() =>
|
const formattedDate = createMemo<string>(() =>
|
||||||
props.article?.published_at ? formatDate(new Date(props.article.published_at * 1000)) : ''
|
props.article.published_at ? formatDate(new Date(props.article.published_at * 1000)) : ''
|
||||||
)
|
)
|
||||||
|
|
||||||
const canEdit = createMemo(
|
const canEdit = createMemo(
|
||||||
() =>
|
() =>
|
||||||
Boolean(author()?.id) &&
|
Boolean(author()?.id) &&
|
||||||
(props.article?.authors?.some((a) => Boolean(a) && a?.id === author().id) ||
|
(props.article.authors?.some((a) => Boolean(a) && a?.id === author().id) ||
|
||||||
props.article?.created_by?.id === author().id ||
|
props.article.created_by?.id === author().id ||
|
||||||
session()?.user?.roles?.includes('editor'))
|
session()?.user?.roles?.includes('editor'))
|
||||||
)
|
)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const scrollToComments = (event: MouseEvent & { currentTarget: HTMLAnchorElement; target: Element }) => {
|
const scrollToComments = (event: MouseEvent & { currentTarget: HTMLAnchorElement; target: Element }) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
navigate(`/${props.article.slug}`)
|
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
scrollTo: 'comments'
|
scrollTo: 'comments'
|
||||||
})
|
})
|
||||||
|
navigate(`/${props.article.slug}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onInvite = () => {
|
const onInvite = () => {
|
||||||
|
@ -196,10 +197,9 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div class={styles.shoutCardType}>
|
<div class={styles.shoutCardType}>
|
||||||
<a href={`/expo/${props.article.layout}`}>
|
<A href={`/expo/${props.article.layout}`}>
|
||||||
<Icon name={props.article.layout} class={styles.icon} />
|
<Icon name={props.article.layout} class={styles.icon} />
|
||||||
{/*<Icon name={`${layout}-hover`} class={clsx(styles.icon, styles.iconHover)} />*/}
|
</A>
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
@ -220,7 +220,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
[styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode
|
[styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<A href={`/${props.article?.slug || ''}`}>
|
<A href={`/${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} />
|
||||||
|
@ -280,10 +280,9 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div class={styles.shoutCardType}>
|
<div class={styles.shoutCardType}>
|
||||||
<a href={`/expo/${props.article.layout}`}>
|
<A href={`/expo/${props.article.layout}`}>
|
||||||
<Icon name={props.article.layout} class={styles.icon} />
|
<Icon name={props.article.layout} class={styles.icon} />
|
||||||
{/*<Icon name={`${layout}-hover`} class={clsx(styles.icon, styles.iconHover)} />*/}
|
</A>
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<div class={styles.shoutCardCover}>
|
<div class={styles.shoutCardCover}>
|
||||||
|
@ -332,7 +331,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
<Popover content={t('Edit')} disabled={isActionPopupActive()}>
|
<Popover content={t('Edit')} disabled={isActionPopupActive()}>
|
||||||
{(triggerRef: (el: HTMLElement) => void) => (
|
{(triggerRef: (el: HTMLElement) => void) => (
|
||||||
<div class={styles.shoutCardDetailsItem} ref={triggerRef}>
|
<div class={styles.shoutCardDetailsItem} ref={triggerRef}>
|
||||||
<A href={`/edit/${props.article?.id}`}>
|
<A href={`/edit/${props.article.id}`}>
|
||||||
<Icon name="pencil-outline" class={clsx(styles.icon, styles.feedControlIcon)} />
|
<Icon name="pencil-outline" class={clsx(styles.icon, styles.feedControlIcon)} />
|
||||||
<Icon
|
<Icon
|
||||||
name="pencil-outline-hover"
|
name="pencil-outline-hover"
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
import { A, useLocation, useParams } from '@solidjs/router'
|
import { A, useLocation, useParams } from '@solidjs/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { For, Match, Show, Switch, createEffect, createMemo, createSignal } from 'solid-js'
|
import { For, Match, Show, Switch, createEffect, createMemo, createSignal, on } from 'solid-js'
|
||||||
|
import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper'
|
||||||
import { Loading } from '~/components/_shared/Loading'
|
import { Loading } from '~/components/_shared/Loading'
|
||||||
import { coreApiUrl } from '~/config'
|
import { coreApiUrl } from '~/config'
|
||||||
import { useAuthors } from '~/context/authors'
|
import { useAuthors } from '~/context/authors'
|
||||||
|
import { SHOUTS_PER_PAGE, useFeed } from '~/context/feed'
|
||||||
import { useFollowing } from '~/context/following'
|
import { useFollowing } from '~/context/following'
|
||||||
import { useLocalize } from '~/context/localize'
|
import { useLocalize } from '~/context/localize'
|
||||||
import { useSession } from '~/context/session'
|
import { useSession } from '~/context/session'
|
||||||
|
import { loadShouts } from '~/graphql/api/public'
|
||||||
import { graphqlClientCreate } from '~/graphql/client'
|
import { graphqlClientCreate } from '~/graphql/client'
|
||||||
import getAuthorFollowersQuery from '~/graphql/query/core/author-followers'
|
import getAuthorFollowersQuery from '~/graphql/query/core/author-followers'
|
||||||
import getAuthorFollowsQuery from '~/graphql/query/core/author-follows'
|
import getAuthorFollowsQuery from '~/graphql/query/core/author-follows'
|
||||||
import type { Author, Reaction, Shout, Topic } from '~/graphql/schema/core.gen'
|
import type { Author, Reaction, Shout, Topic } from '~/graphql/schema/core.gen'
|
||||||
|
import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll'
|
||||||
import { byCreated } from '~/utils/sort'
|
import { byCreated } from '~/utils/sort'
|
||||||
import stylesArticle from '../../Article/Article.module.scss'
|
import stylesArticle from '../../Article/Article.module.scss'
|
||||||
import { Comment } from '../../Article/Comment'
|
import { Comment } from '../../Article/Comment'
|
||||||
|
@ -131,6 +135,32 @@ export const AuthorView = (props: AuthorViewProps) => {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const { feedByAuthor, addFeed } = useFeed()
|
||||||
|
const [sortedFeed, setSortedFeed] = createSignal<Shout[]>([])
|
||||||
|
const [loadMoreHidden, setLoadMoreHidden] = createSignal(false)
|
||||||
|
const loadMore = async () => {
|
||||||
|
saveScrollPosition()
|
||||||
|
const amountBefore = feedByAuthor()?.[props.authorSlug]?.length || 0
|
||||||
|
const topicShoutsFetcher = loadShouts({
|
||||||
|
filters: { author: props.authorSlug },
|
||||||
|
limit: SHOUTS_PER_PAGE,
|
||||||
|
offset: amountBefore
|
||||||
|
})
|
||||||
|
const result = await topicShoutsFetcher()
|
||||||
|
result && addFeed(result)
|
||||||
|
const amountAfter = feedByAuthor()?.[props.authorSlug].length
|
||||||
|
setLoadMoreHidden(amountAfter === amountBefore)
|
||||||
|
restoreScrollPosition()
|
||||||
|
return result as LoadMoreItems
|
||||||
|
}
|
||||||
|
|
||||||
|
// fx to update author's feed
|
||||||
|
createEffect(on(feedByAuthor, (byAuthor) => {
|
||||||
|
const feed = byAuthor[props.authorSlug] as Shout[]
|
||||||
|
if (!feed) return
|
||||||
|
setSortedFeed(feed)
|
||||||
|
},{}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={styles.authorPage}>
|
<div class={styles.authorPage}>
|
||||||
<div class="wide-container">
|
<div class="wide-container">
|
||||||
|
@ -218,10 +248,14 @@ export const AuthorView = (props: AuthorViewProps) => {
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={Array.isArray(props.shouts) && props.shouts.length > 0}>
|
<LoadMoreWrapper loadFunction={loadMore} pageSize={SHOUTS_PER_PAGE} hidden={loadMoreHidden()}>
|
||||||
<For each={props.shouts.filter((_, i) => i % 3 === 0)}>
|
<For
|
||||||
|
each={sortedFeed()
|
||||||
|
.filter((_, i) => i % 3 === 0)}
|
||||||
|
>
|
||||||
{(_shout, index) => {
|
{(_shout, index) => {
|
||||||
const articles = props.shouts.slice(index() * 3, index() * 3 + 3)
|
const articles = sortedFeed()
|
||||||
|
.slice(index() * 3, index() * 3 + 3)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Switch>
|
<Switch>
|
||||||
|
@ -239,7 +273,7 @@ export const AuthorView = (props: AuthorViewProps) => {
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
</For>
|
</For>
|
||||||
</Show>
|
</LoadMoreWrapper>
|
||||||
</Match>
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,11 +2,10 @@ import { RouteSectionProps, createAsync } from '@solidjs/router'
|
||||||
import { ErrorBoundary, Suspense, createEffect, createSignal, on } from 'solid-js'
|
import { ErrorBoundary, Suspense, createEffect, createSignal, on } from 'solid-js'
|
||||||
import { AuthorView } from '~/components/Views/Author'
|
import { AuthorView } from '~/components/Views/Author'
|
||||||
import { FourOuFourView } from '~/components/Views/FourOuFour'
|
import { FourOuFourView } from '~/components/Views/FourOuFour'
|
||||||
import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper'
|
|
||||||
import { Loading } from '~/components/_shared/Loading'
|
import { Loading } from '~/components/_shared/Loading'
|
||||||
import { PageLayout } from '~/components/_shared/PageLayout'
|
import { PageLayout } from '~/components/_shared/PageLayout'
|
||||||
import { useAuthors } from '~/context/authors'
|
import { useAuthors } from '~/context/authors'
|
||||||
import { SHOUTS_PER_PAGE, useFeed } from '~/context/feed'
|
import { SHOUTS_PER_PAGE } from '~/context/feed'
|
||||||
import { useLocalize } from '~/context/localize'
|
import { useLocalize } from '~/context/localize'
|
||||||
import { ReactionsProvider } from '~/context/reactions'
|
import { ReactionsProvider } from '~/context/reactions'
|
||||||
import { loadAuthors, loadShouts, loadTopics } from '~/graphql/api/public'
|
import { loadAuthors, loadShouts, loadTopics } from '~/graphql/api/public'
|
||||||
|
@ -106,21 +105,7 @@ export default function AuthorPage(props: RouteSectionProps<AuthorPageProps>) {
|
||||||
)
|
)
|
||||||
|
|
||||||
// author's shouts
|
// author's shouts
|
||||||
const { addFeed, feedByAuthor } = useFeed()
|
const authorShouts = createAsync(async () => props.data.articles as Shout[] || await fetchAuthorShouts(props.params.slug, 0))
|
||||||
const [loadMoreHidden, setLoadMoreHidden] = createSignal(true)
|
|
||||||
const authorShouts = createAsync(async () => {
|
|
||||||
const sss: Shout[] = (props.data.articles as Shout[]) || feedByAuthor()[props.params.slug] || []
|
|
||||||
const result = sss || (await fetchAuthorShouts(props.params.slug, 0))
|
|
||||||
if (!result) setLoadMoreHidden(true)
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
|
|
||||||
// load more shouts
|
|
||||||
const loadAuthorShoutsMore = async (offset: number) => {
|
|
||||||
const loadedShouts = await fetchAuthorShouts(props.params.slug, offset)
|
|
||||||
loadedShouts && addFeed(loadedShouts)
|
|
||||||
return (loadedShouts || []) as LoadMoreItems
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary fallback={(_err) => <FourOuFourView />}>
|
<ErrorBoundary fallback={(_err) => <FourOuFourView />}>
|
||||||
|
@ -133,17 +118,11 @@ export default function AuthorPage(props: RouteSectionProps<AuthorPageProps>) {
|
||||||
cover={cover()}
|
cover={cover()}
|
||||||
>
|
>
|
||||||
<ReactionsProvider>
|
<ReactionsProvider>
|
||||||
<LoadMoreWrapper
|
|
||||||
loadFunction={loadAuthorShoutsMore}
|
|
||||||
pageSize={SHOUTS_PER_PAGE}
|
|
||||||
hidden={loadMoreHidden()}
|
|
||||||
>
|
|
||||||
<AuthorView
|
<AuthorView
|
||||||
author={author() as Author}
|
author={author() as Author}
|
||||||
authorSlug={props.params.slug}
|
authorSlug={props.params.slug}
|
||||||
shouts={authorShouts() || []}
|
shouts={authorShouts() || []}
|
||||||
/>
|
/>
|
||||||
</LoadMoreWrapper>
|
|
||||||
</ReactionsProvider>
|
</ReactionsProvider>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user