2023-07-07 16:53:01 +00:00
|
|
|
|
import { createEffect, createSignal, For, on, onMount, Show } from 'solid-js'
|
2022-11-14 17:41:05 +00:00
|
|
|
|
import { Icon } from '../_shared/Icon'
|
2023-05-01 18:32:32 +00:00
|
|
|
|
import { ArticleCard } from '../Feed/ArticleCard'
|
|
|
|
|
import { AuthorCard } from '../Author/AuthorCard'
|
2023-04-29 13:43:50 +00:00
|
|
|
|
import { Sidebar } from '../Feed/Sidebar'
|
2023-07-07 16:53:01 +00:00
|
|
|
|
import { loadShouts, loadMyFeed, useArticlesStore, resetSortedArticles } from '../../stores/zine/articles'
|
2022-09-09 11:53:35 +00:00
|
|
|
|
import { useAuthorsStore } from '../../stores/zine/authors'
|
2022-09-09 12:18:09 +00:00
|
|
|
|
import { useTopicsStore } from '../../stores/zine/topics'
|
2022-09-22 09:37:49 +00:00
|
|
|
|
import { useTopAuthorsStore } from '../../stores/zine/topAuthors'
|
2023-01-25 22:13:01 +00:00
|
|
|
|
import { clsx } from 'clsx'
|
2023-02-17 09:21:02 +00:00
|
|
|
|
import { useReactions } from '../../context/reactions'
|
2023-07-07 16:53:01 +00:00
|
|
|
|
import type { Author, LoadShoutsOptions, Reaction } from '../../graphql/types.gen'
|
2023-02-17 09:21:02 +00:00
|
|
|
|
import { getPagePath } from '@nanostores/router'
|
2023-07-07 16:53:01 +00:00
|
|
|
|
import { router, useRouter } from '../../stores/router'
|
2023-02-17 09:21:02 +00:00
|
|
|
|
import { useLocalize } from '../../context/localize'
|
2023-03-04 17:26:28 +00:00
|
|
|
|
import styles from './Feed.module.scss'
|
|
|
|
|
import stylesTopic from '../Feed/CardTopic.module.scss'
|
|
|
|
|
import stylesBeside from '../../components/Feed/Beside.module.scss'
|
2023-06-12 17:58:02 +00:00
|
|
|
|
import { CommentDate } from '../Article/CommentDate'
|
2023-07-07 16:53:01 +00:00
|
|
|
|
import { Loading } from '../_shared/Loading'
|
2022-09-09 11:53:35 +00:00
|
|
|
|
|
2022-10-28 21:21:47 +00:00
|
|
|
|
export const FEED_PAGE_SIZE = 20
|
|
|
|
|
|
2023-07-07 16:53:01 +00:00
|
|
|
|
type FeedSearchParams = {
|
|
|
|
|
by: 'publish_date' | 'rating' | 'last_comment'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getOrderBy = (by: FeedSearchParams['by']) => {
|
|
|
|
|
if (by === 'rating') {
|
|
|
|
|
return 'rating_stat'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (by === 'last_comment') {
|
|
|
|
|
return 'last_comment'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ''
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-28 21:21:47 +00:00
|
|
|
|
export const FeedView = () => {
|
2023-02-17 09:21:02 +00:00
|
|
|
|
const { t } = useLocalize()
|
2023-07-07 16:53:01 +00:00
|
|
|
|
const { page, searchParams } = useRouter<FeedSearchParams>()
|
|
|
|
|
const [isLoading, setIsLoading] = createSignal(false)
|
2023-02-17 09:21:02 +00:00
|
|
|
|
|
2022-09-09 11:53:35 +00:00
|
|
|
|
// state
|
2022-11-18 02:23:04 +00:00
|
|
|
|
const { sortedArticles } = useArticlesStore()
|
2022-09-28 20:16:44 +00:00
|
|
|
|
const { sortedAuthors } = useAuthorsStore()
|
|
|
|
|
const { topTopics } = useTopicsStore()
|
|
|
|
|
const { topAuthors } = useTopAuthorsStore()
|
2022-10-28 21:21:47 +00:00
|
|
|
|
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
2023-02-17 09:21:02 +00:00
|
|
|
|
const [topComments, setTopComments] = createSignal<Reaction[]>([])
|
2022-10-28 21:21:47 +00:00
|
|
|
|
|
2023-02-17 09:21:02 +00:00
|
|
|
|
const {
|
|
|
|
|
actions: { loadReactionsBy }
|
|
|
|
|
} = useReactions()
|
|
|
|
|
|
2023-07-07 16:53:01 +00:00
|
|
|
|
createEffect(
|
|
|
|
|
on(
|
|
|
|
|
() => page().route + searchParams().by,
|
|
|
|
|
() => {
|
|
|
|
|
resetSortedArticles()
|
|
|
|
|
loadMore()
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
)
|
2022-11-29 11:01:23 +00:00
|
|
|
|
|
2023-07-07 16:53:01 +00:00
|
|
|
|
const loadFeedShouts = () => {
|
|
|
|
|
const options: LoadShoutsOptions = {
|
2022-11-15 14:24:50 +00:00
|
|
|
|
limit: FEED_PAGE_SIZE,
|
|
|
|
|
offset: sortedArticles().length
|
2023-07-07 16:53:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const orderBy = getOrderBy(searchParams().by)
|
|
|
|
|
|
|
|
|
|
if (orderBy) {
|
|
|
|
|
options.order_by = orderBy
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (page().route === 'feedMy') {
|
|
|
|
|
return loadMyFeed(options)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// default feed
|
|
|
|
|
return loadShouts({
|
|
|
|
|
...options,
|
|
|
|
|
filters: { visibility: 'community' }
|
2022-11-15 14:24:50 +00:00
|
|
|
|
})
|
2023-07-07 16:53:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const loadMore = async () => {
|
|
|
|
|
setIsLoading(true)
|
|
|
|
|
const { hasMore, newShouts } = await loadFeedShouts()
|
|
|
|
|
setIsLoading(false)
|
2023-02-28 17:13:14 +00:00
|
|
|
|
|
|
|
|
|
loadReactionsBy({
|
|
|
|
|
by: {
|
|
|
|
|
shouts: newShouts.map((s) => s.slug)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2022-10-28 21:21:47 +00:00
|
|
|
|
setIsLoadMoreButtonVisible(hasMore)
|
2022-09-13 09:59:04 +00:00
|
|
|
|
}
|
2022-10-28 21:21:47 +00:00
|
|
|
|
|
2022-11-16 06:33:58 +00:00
|
|
|
|
onMount(async () => {
|
2023-02-17 09:21:02 +00:00
|
|
|
|
// load 5 recent comments overall
|
|
|
|
|
const comments = await loadReactionsBy({ by: { comment: true }, limit: 5 })
|
|
|
|
|
setTopComments(comments)
|
2022-10-28 21:21:47 +00:00
|
|
|
|
})
|
|
|
|
|
|
2022-09-09 11:53:35 +00:00
|
|
|
|
return (
|
2023-04-29 13:43:50 +00:00
|
|
|
|
<div>
|
2023-01-25 22:13:01 +00:00
|
|
|
|
<div class="wide-container feed">
|
2023-03-10 17:42:48 +00:00
|
|
|
|
<div class="row">
|
2023-03-23 22:05:23 +00:00
|
|
|
|
<div class={clsx('col-md-5 col-xl-4', styles.feedNavigation)}>
|
2023-04-29 13:43:50 +00:00
|
|
|
|
<Sidebar authors={sortedAuthors()} />
|
2022-09-09 11:53:35 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
2023-03-10 17:42:48 +00:00
|
|
|
|
<div class="col-md-12 offset-xl-1">
|
2023-05-22 22:01:04 +00:00
|
|
|
|
<ul class={clsx(styles.feedFilter, 'view-switcher')}>
|
2023-07-07 16:53:01 +00:00
|
|
|
|
<li
|
|
|
|
|
class={clsx({
|
|
|
|
|
'view-switcher__item--selected':
|
|
|
|
|
searchParams().by === 'publish_date' || !searchParams().by
|
|
|
|
|
})}
|
|
|
|
|
>
|
2023-07-18 15:42:34 +00:00
|
|
|
|
<a href={getPagePath(router, page().route)}>{t('Recent')}</a>
|
2023-03-10 17:42:48 +00:00
|
|
|
|
</li>
|
2023-07-07 16:53:01 +00:00
|
|
|
|
{/*<li>*/}
|
|
|
|
|
{/* <a href="/feed/?by=views">{t('Most read')}</a>*/}
|
|
|
|
|
{/*</li>*/}
|
|
|
|
|
<li
|
|
|
|
|
class={clsx({
|
|
|
|
|
'view-switcher__item--selected': searchParams().by === 'rating'
|
|
|
|
|
})}
|
|
|
|
|
>
|
|
|
|
|
<a href={`${getPagePath(router, page().route)}?by=rating`}>{t('Top rated')}</a>
|
2023-03-10 17:42:48 +00:00
|
|
|
|
</li>
|
2023-07-07 16:53:01 +00:00
|
|
|
|
<li
|
|
|
|
|
class={clsx({
|
|
|
|
|
'view-switcher__item--selected': searchParams().by === 'last_comment'
|
|
|
|
|
})}
|
|
|
|
|
>
|
|
|
|
|
<a href={`${getPagePath(router, page().route)}?by=last_comment`}>{t('Most commented')}</a>
|
2023-03-10 17:42:48 +00:00
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
|
2023-07-07 16:53:01 +00:00
|
|
|
|
<Show when={!isLoading()} fallback={<Loading />}>
|
|
|
|
|
<Show when={sortedArticles().length > 0}>
|
|
|
|
|
<For each={sortedArticles().slice(0, 4)}>
|
|
|
|
|
{(article) => <ArticleCard article={article} settings={{ isFeedMode: true }} />}
|
2023-03-10 17:42:48 +00:00
|
|
|
|
</For>
|
2022-09-09 11:53:35 +00:00
|
|
|
|
|
2023-08-12 14:17:00 +00:00
|
|
|
|
<div class={styles.asideSection}>
|
|
|
|
|
<div class={stylesBeside.besideColumnTitle}>
|
|
|
|
|
<h4>{t('Popular authors')}</h4>
|
|
|
|
|
<a href="/authors">
|
|
|
|
|
{t('All authors')}
|
|
|
|
|
<Icon name="arrow-right" class={stylesBeside.icon} />
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<ul class={stylesBeside.besideColumn}>
|
|
|
|
|
<For each={topAuthors().slice(0, 5)}>
|
|
|
|
|
{(author) => (
|
|
|
|
|
<li>
|
|
|
|
|
<AuthorCard
|
|
|
|
|
author={author}
|
|
|
|
|
hideWriteButton={true}
|
|
|
|
|
hasLink={true}
|
|
|
|
|
truncateBio={true}
|
|
|
|
|
isTextButton={true}
|
|
|
|
|
/>
|
|
|
|
|
</li>
|
|
|
|
|
)}
|
|
|
|
|
</For>
|
|
|
|
|
</ul>
|
2023-07-07 16:53:01 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<For each={sortedArticles().slice(4)}>
|
|
|
|
|
{(article) => <ArticleCard article={article} settings={{ isFeedMode: true }} />}
|
|
|
|
|
</For>
|
|
|
|
|
</Show>
|
2023-03-10 17:42:48 +00:00
|
|
|
|
|
2023-07-07 16:53:01 +00:00
|
|
|
|
<Show when={isLoadMoreButtonVisible()}>
|
|
|
|
|
<p class="load-more-container">
|
|
|
|
|
<button class="button" onClick={loadMore}>
|
|
|
|
|
{t('Load more')}
|
|
|
|
|
</button>
|
|
|
|
|
</p>
|
|
|
|
|
</Show>
|
2023-03-10 17:42:48 +00:00
|
|
|
|
</Show>
|
|
|
|
|
</div>
|
2022-09-09 11:53:35 +00:00
|
|
|
|
|
2023-03-10 17:42:48 +00:00
|
|
|
|
<aside class={clsx('col-md-7 col-xl-6 offset-xl-1', styles.feedAside)}>
|
|
|
|
|
<section class={styles.asideSection}>
|
|
|
|
|
<h4>{t('Comments')}</h4>
|
|
|
|
|
<For each={topComments()}>
|
|
|
|
|
{(comment) => {
|
|
|
|
|
return (
|
|
|
|
|
<div class={styles.comment}>
|
2023-05-26 11:30:27 +00:00
|
|
|
|
<div class={clsx('text-truncate', styles.commentBody)}>
|
|
|
|
|
<a
|
|
|
|
|
href={`${getPagePath(router, 'article', {
|
|
|
|
|
slug: comment.shout.slug
|
|
|
|
|
})}?commentId=${comment.id}`}
|
|
|
|
|
innerHTML={comment.body}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2023-06-12 17:58:02 +00:00
|
|
|
|
<div class={styles.commentDetails}>
|
|
|
|
|
<AuthorCard
|
|
|
|
|
author={comment.createdBy as Author}
|
|
|
|
|
isFeedMode={true}
|
|
|
|
|
hideWriteButton={true}
|
|
|
|
|
hideFollow={true}
|
2023-08-12 10:39:58 +00:00
|
|
|
|
hasLink={true}
|
2023-06-12 17:58:02 +00:00
|
|
|
|
/>
|
|
|
|
|
<CommentDate comment={comment} isShort={true} isLastInRow={true} />
|
|
|
|
|
</div>
|
2023-03-10 17:42:48 +00:00
|
|
|
|
<div class={clsx('text-truncate', styles.commentArticleTitle)}>
|
|
|
|
|
<a href={`/${comment.shout.slug}`}>{comment.shout.title}</a>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}}
|
|
|
|
|
</For>
|
|
|
|
|
</section>
|
2023-01-25 22:13:01 +00:00
|
|
|
|
|
2023-03-10 17:42:48 +00:00
|
|
|
|
<Show when={topTopics().length > 0}>
|
2023-01-25 22:13:01 +00:00
|
|
|
|
<section class={styles.asideSection}>
|
2023-06-12 20:11:25 +00:00
|
|
|
|
<h4>{t('Hot topics')}</h4>
|
2023-06-15 20:47:54 +00:00
|
|
|
|
<For each={topTopics().slice(0, 7)}>
|
2023-03-10 17:42:48 +00:00
|
|
|
|
{(topic) => (
|
|
|
|
|
<span class={clsx(stylesTopic.shoutTopic, styles.topic)}>
|
|
|
|
|
<a href={`/topic/${topic.slug}`}>{topic.title}</a>{' '}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
2022-09-09 11:53:35 +00:00
|
|
|
|
</For>
|
|
|
|
|
</section>
|
2023-03-10 17:42:48 +00:00
|
|
|
|
</Show>
|
2023-01-25 22:13:01 +00:00
|
|
|
|
|
2023-03-10 17:42:48 +00:00
|
|
|
|
<section class={clsx(styles.asideSection, styles.pinnedLinks)}>
|
2023-06-12 20:27:50 +00:00
|
|
|
|
<h4>{t('Knowledge base')}</h4>
|
2023-03-10 17:42:48 +00:00
|
|
|
|
<ul class="nodash">
|
|
|
|
|
<li>
|
|
|
|
|
<a href={getPagePath(router, 'guide')}>Как устроен Дискурс</a>
|
|
|
|
|
</li>
|
|
|
|
|
<li>
|
|
|
|
|
<a href="/how-to-write-a-good-article">Как создать хороший текст</a>
|
|
|
|
|
</li>
|
|
|
|
|
<li>
|
|
|
|
|
<a href="#">Правила конструктивных дискуссий</a>
|
|
|
|
|
</li>
|
|
|
|
|
<li>
|
|
|
|
|
<a href={getPagePath(router, 'principles')}>Принципы сообщества</a>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
</section>
|
|
|
|
|
</aside>
|
2022-09-09 11:53:35 +00:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2023-04-29 13:43:50 +00:00
|
|
|
|
</div>
|
2022-09-09 11:53:35 +00:00
|
|
|
|
)
|
|
|
|
|
}
|