webapp/src/components/Views/Feed/Feed.tsx

279 lines
11 KiB
TypeScript
Raw Normal View History

2024-07-03 17:38:43 +00:00
import { A, createAsync, useLocation, useNavigate, useSearchParams } from '@solidjs/router'
import { clsx } from 'clsx'
import { For, Show, createMemo, createSignal, onMount } from 'solid-js'
import { DropDown } from '~/components/_shared/DropDown'
2024-07-03 17:38:43 +00:00
import { Option } from '~/components/_shared/DropDown/DropDown'
import { Icon } from '~/components/_shared/Icon'
import { InviteMembers } from '~/components/_shared/InviteMembers'
import { Loading } from '~/components/_shared/Loading'
import { ShareModal } from '~/components/_shared/ShareModal'
import { useAuthors } from '~/context/authors'
2024-06-24 17:50:27 +00:00
import { useGraphQL } from '~/context/graphql'
import { useLocalize } from '~/context/localize'
import { useReactions } from '~/context/reactions'
import { useSession } from '~/context/session'
import { useTopics } from '~/context/topics'
2024-06-24 17:50:27 +00:00
import { useUI } from '~/context/ui'
import { loadUnratedShouts } from '~/graphql/api/private'
import type { Author, Reaction, Shout } from '~/graphql/schema/core.gen'
2024-07-05 14:08:12 +00:00
import { byCreated } from '~/lib/sortby'
2024-07-05 19:40:54 +00:00
import { FeedSearchParams } from '~/routes/feed/[feed]'
import { CommentDate } from '../../Article/CommentDate'
2024-01-08 13:02:52 +00:00
import { getShareUrl } from '../../Article/SharePopup'
import { AuthorBadge } from '../../Author/AuthorBadge'
2023-12-31 05:01:34 +00:00
import { AuthorLink } from '../../Author/AuthorLink'
import { ArticleCard } from '../../Feed/ArticleCard'
2024-06-24 17:50:27 +00:00
import stylesBeside from '../../Feed/Beside.module.scss'
import stylesTopic from '../../Feed/CardTopic.module.scss'
2024-05-10 16:52:13 +00:00
import { Placeholder } from '../../Feed/Placeholder'
import { Sidebar } from '../../Feed/Sidebar'
2024-02-03 08:16:47 +00:00
import { Modal } from '../../Nav/Modal'
2024-02-04 11:25:21 +00:00
import styles from './Feed.module.scss'
2022-09-09 11:53:35 +00:00
export const FEED_PAGE_SIZE = 20
2024-07-03 17:38:43 +00:00
export type PeriodType = 'week' | 'month' | 'year'
export type FeedProps = {
shouts: Shout[]
}
2024-07-03 17:38:43 +00:00
export const FeedView = (props: FeedProps) => {
2024-07-09 09:13:13 +00:00
const { t } = useLocalize()
2024-06-24 17:50:27 +00:00
const loc = useLocation()
2024-07-03 17:38:43 +00:00
const client = useGraphQL()
const unrated = createAsync(async () => {
if (client) {
const shoutsLoader = loadUnratedShouts(client, { limit: 5 })
return await shoutsLoader()
}
})
const navigate = useNavigate()
2024-06-24 17:50:27 +00:00
const { showModal } = useUI()
const [isLoading, setIsLoading] = createSignal(false)
const [isRightColumnLoaded, setIsRightColumnLoaded] = createSignal(false)
2024-06-24 17:50:27 +00:00
const { session } = useSession()
2024-02-04 17:40:15 +00:00
const { loadReactionsBy } = useReactions()
2024-05-06 23:44:25 +00:00
const { topTopics } = useTopics()
2024-06-24 17:50:27 +00:00
const { topAuthors } = useAuthors()
2023-02-17 09:21:02 +00:00
const [topComments, setTopComments] = createSignal<Reaction[]>([])
2024-07-03 17:38:43 +00:00
const [searchParams, changeSearchParams] = useSearchParams<FeedSearchParams>()
const asOption = (o: string) => ({ value: o, title: t(o) })
const asOptions = (opts: string[]) => opts.map(asOption)
const currentPeriod = createMemo(() => asOption(searchParams?.period || ''))
const loadTopComments = async () => {
2024-02-29 17:51:07 +00:00
const comments = await loadReactionsBy({ by: { comment: true }, limit: 50 })
2024-03-01 13:04:28 +00:00
setTopComments(comments.sort(byCreated).reverse())
}
onMount(() => {
setIsLoading(true)
2024-07-03 17:38:43 +00:00
Promise.all([
loadTopComments(),
loadReactionsBy({ by: { shouts: props.shouts.map((s) => s.slug) } })
]).finally(() => {
setIsRightColumnLoaded(true)
setIsLoading(false)
2023-02-28 17:13:14 +00:00
})
2024-07-03 17:38:43 +00:00
})
2024-01-08 13:02:52 +00:00
const [shareData, setShareData] = createSignal<Shout | undefined>()
2024-03-01 13:04:28 +00:00
const handleShare = (shared: Shout | undefined) => {
2024-01-08 13:02:52 +00:00
showModal('share')
setShareData(shared)
}
2022-09-09 11:53:35 +00:00
return (
2024-07-09 09:13:13 +00:00
<div class="feed">
<div class="row">
<div class={clsx('col-md-5 col-xl-4', styles.feedNavigation)}>
<Sidebar />
</div>
2023-03-10 17:42:48 +00:00
<div class="col-md-12 offset-xl-1">
2024-06-24 17:50:27 +00:00
<Show when={!session() && loc?.pathname !== 'feed'}>
<Placeholder type={loc?.pathname} mode="feed" />
2024-05-18 22:03:06 +00:00
</Show>
2024-07-03 17:38:43 +00:00
<Show when={(session() || loc?.pathname === 'feed') && props.shouts.length}>
2024-05-10 16:52:13 +00:00
<div class={styles.filtersContainer}>
<ul class={clsx('view-switcher', styles.feedFilter)}>
<li
class={clsx({
2024-07-03 17:38:43 +00:00
'view-switcher__item--selected': searchParams?.by === 'after' || !searchParams?.by
2024-05-10 16:52:13 +00:00
})}
>
2024-06-24 17:50:27 +00:00
<A href={loc.pathname}>{t('Recent')}</A>
2024-05-10 16:52:13 +00:00
</li>
{/*<li>*/}
{/* <a href="/feed/?by=views">{t('Most read')}</a>*/}
{/*</li>*/}
<li
class={clsx({
2024-06-26 08:22:05 +00:00
'view-switcher__item--selected': searchParams?.by === 'likes'
2024-05-10 16:52:13 +00:00
})}
>
<span class="link" onClick={() => changeSearchParams({ by: 'likes' })}>
{t('Top rated')}
</span>
</li>
<li
class={clsx({
2024-06-26 08:22:05 +00:00
'view-switcher__item--selected': searchParams?.by === 'last_comment'
2024-05-10 16:52:13 +00:00
})}
>
<span class="link" onClick={() => changeSearchParams({ by: 'last_comment' })}>
2024-07-03 17:38:43 +00:00
{t('Commented')}
2024-05-10 16:52:13 +00:00
</span>
</li>
</ul>
<div class={styles.dropdowns}>
2024-07-03 17:38:43 +00:00
<Show when={searchParams?.by && searchParams?.by !== 'after'}>
2024-05-10 16:52:13 +00:00
<DropDown
popupProps={{ horizontalAnchor: 'right' }}
2024-07-03 17:38:43 +00:00
options={asOptions(['week', 'month', 'year'])}
2024-05-10 16:52:13 +00:00
currentOption={currentPeriod()}
triggerCssClass={styles.periodSwitcher}
2024-07-03 17:38:43 +00:00
onChange={(period: Option) => changeSearchParams({ period: period.value })}
2024-05-10 16:52:13 +00:00
/>
</Show>
<DropDown
2024-01-06 04:06:58 +00:00
popupProps={{ horizontalAnchor: 'right' }}
2024-07-03 17:38:43 +00:00
options={asOptions(['followed', 'unrated', 'discussed', 'bookmarked', 'coauthored'])}
currentOption={asOption(loc.pathname.split('/').pop() || '')}
triggerCssClass={styles.periodSwitcher}
2024-07-03 17:38:43 +00:00
onChange={(mode: Option) => navigate(`/feed/${mode.value}`)}
/>
2024-05-10 16:52:13 +00:00
</div>
</div>
2023-08-12 14:17:00 +00:00
2024-05-10 16:52:13 +00:00
<Show when={!isLoading()} fallback={<Loading />}>
2024-07-03 17:38:43 +00:00
<Show when={props.shouts.length > 0}>
<For each={props.shouts.slice(0, 4)}>
2024-05-10 16:52:13 +00:00
{(article) => (
<ArticleCard
onShare={(shared) => handleShare(shared)}
onInvite={() => showModal('inviteMembers')}
article={article}
settings={{ isFeedMode: true }}
desktopCoverSize="M"
/>
)}
</For>
<div class={styles.asideSection}>
<div class={stylesBeside.besideColumnTitle}>
<h4>{t('Popular authors')}</h4>
2024-07-03 21:25:03 +00:00
<a href="/author">
2024-05-10 16:52:13 +00:00
{t('All authors')}
<Icon name="arrow-right" class={stylesBeside.icon} />
</a>
</div>
<ul class={stylesBeside.besideColumn}>
<For each={topAuthors().slice(0, 5)}>
{(author) => (
<li>
<AuthorBadge author={author} />
</li>
)}
</For>
</ul>
</div>
2024-07-03 17:38:43 +00:00
<For each={props.shouts.slice(4)}>
2024-05-10 16:52:13 +00:00
{(article) => (
<ArticleCard article={article} settings={{ isFeedMode: true }} desktopCoverSize="M" />
)}
</For>
</Show>
</Show>
</Show>
</div>
2023-03-10 17:42:48 +00:00
<aside class={clsx('col-md-7 col-xl-6 offset-xl-1', styles.feedAside)}>
<Show when={isRightColumnLoaded()}>
<Show when={topComments().length > 0}>
<section class={styles.asideSection}>
<h4>{t('Comments')}</h4>
<For each={topComments()}>
{(comment) => {
return (
<div class={styles.comment}>
<div class={clsx('text-truncate', styles.commentBody)}>
2024-06-24 17:50:27 +00:00
<A
href={`article/${comment.shout.slug}?commentId=${comment.id}`}
innerHTML={comment.body || ''}
/>
</div>
<div class={styles.commentDetails}>
2023-12-20 16:54:20 +00:00
<AuthorLink author={comment.created_by as Author} size={'XS'} />
<CommentDate comment={comment} isShort={true} isLastInRow={true} />
</div>
<div class={clsx('text-truncate', styles.commentArticleTitle)}>
<a href={`/${comment.shout.slug}`}>{comment.shout.title}</a>
</div>
</div>
)
}}
</For>
</section>
</Show>
<Show when={topTopics().length > 0}>
<section class={styles.asideSection}>
<h4>{t('Hot topics')}</h4>
<For each={topTopics().slice(0, 7)}>
{(topic) => (
<span class={clsx(stylesTopic.shoutTopic, styles.topic)}>
<a href={`/topic/${topic.slug}`}>{topic.title}</a>{' '}
</span>
)}
</For>
</section>
</Show>
<section class={clsx(styles.asideSection, styles.pinnedLinks)}>
<h4>{t('Knowledge base')}</h4>
<ul class="nodash">
<li>
2024-06-24 17:50:27 +00:00
<A href={'/guide'}>Как устроен Дискурс</A>
</li>
<li>
2024-06-24 17:50:27 +00:00
<A href="/how-to-write-a-good-article">Как создать хороший текст</A>
</li>
<li>
2024-06-24 17:50:27 +00:00
<A href="#">Правила конструктивных дискуссий</A>
</li>
<li>
2024-07-05 19:40:54 +00:00
<A href={'/principles'}>Принципы сообщества</A>
</li>
</ul>
</section>
2024-07-03 17:38:43 +00:00
<Show when={unrated()}>
<section class={clsx(styles.asideSection)}>
<h4>{t('Be the first to rate')}</h4>
2024-07-03 17:38:43 +00:00
<For each={unrated()}>
{(article) => (
<ArticleCard article={article} settings={{ noimage: true, nodate: true }} />
)}
</For>
</section>
</Show>
</Show>
</aside>
</div>
2024-01-08 13:02:52 +00:00
<Show when={shareData()}>
<ShareModal
2024-06-24 17:50:27 +00:00
title={shareData()?.title || ''}
description={shareData()?.description || ''}
imageUrl={shareData()?.cover || ''}
shareUrl={getShareUrl({ pathname: `/${shareData()?.slug || ''}` })}
2024-01-08 13:02:52 +00:00
/>
</Show>
2024-02-03 05:19:15 +00:00
<Modal variant="medium" name="inviteCoauthors">
<InviteMembers variant={'coauthors'} title={t('Invite experts')} />
</Modal>
</div>
2022-09-09 11:53:35 +00:00
)
}