commit
f7ebc383f2
|
@ -77,7 +77,8 @@ module.exports = {
|
|||
|
||||
eqeqeq: 'error',
|
||||
'no-param-reassign': 'error',
|
||||
'no-nested-ternary': 'error'
|
||||
'no-nested-ternary': 'error',
|
||||
'no-shadow': 'error'
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
|
|
|
@ -72,113 +72,109 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
|||
'shout-card--feed': props.settings?.isFeedMode
|
||||
}}
|
||||
>
|
||||
<Show when={mainTopic}>
|
||||
<Show when={!props.settings?.noimage && cover}>
|
||||
<div class="shout-card__cover-container">
|
||||
<div class="shout-card__cover">
|
||||
<img src={cover || ''} alt={title || ''} loading="lazy" />
|
||||
</div>
|
||||
<Show when={!props.settings?.noimage && cover}>
|
||||
<div class="shout-card__cover-container">
|
||||
<div class="shout-card__cover">
|
||||
<img src={cover || ''} alt={title || ''} loading="lazy" />
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div class="shout-card__content">
|
||||
<Show when={layout && layout !== 'article' && !(props.settings?.noicon || props.settings?.noimage)}>
|
||||
<div class="shout-card__type">
|
||||
<a href={`/topic/${mainTopic.slug}`}>
|
||||
<Icon name={layout} />
|
||||
</a>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div class="shout-card__content">
|
||||
<Show
|
||||
when={layout && layout !== 'article' && !(props.settings?.noicon || props.settings?.noimage)}
|
||||
>
|
||||
<div class="shout-card__type">
|
||||
<a href={`/topic/${mainTopic.slug}`}>
|
||||
<Icon name={layout} />
|
||||
</a>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={!props.settings?.isGroup}>
|
||||
<div class="shout__topic">
|
||||
<a href={`/topic/${mainTopic.slug}`}>
|
||||
{locale() === 'ru' && mainTopic.title ? mainTopic.title : mainTopic.slug.replace('-', ' ')}
|
||||
</a>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<div class="shout-card__titles-container">
|
||||
<a href={`/${slug || ''}`} onClick={handleClientRouteLinkClick}>
|
||||
<div class="shout-card__title">
|
||||
<span class="shout-card__link-container">{title}</span>
|
||||
</div>
|
||||
|
||||
<Show when={!props.settings?.nosubtitle && subtitle}>
|
||||
<div class="shout-card__subtitle">
|
||||
<span class="shout-card__link-container">{subtitle}</span>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={!props.settings?.isGroup}>
|
||||
<div class="shout__topic">
|
||||
<a href={`/topic/${mainTopic.slug}`}>
|
||||
{locale() === 'ru' && mainTopic.title ? mainTopic.title : mainTopic.slug.replace('-', ' ')}
|
||||
</a>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={!props.settings?.noauthor || !props.settings?.nodate}>
|
||||
<div class="shout__details">
|
||||
<Show when={!props.settings?.noauthor}>
|
||||
<div class="shout__author">
|
||||
<For each={authors}>
|
||||
{(author, index) => {
|
||||
const name =
|
||||
author.name === 'Дискурс' && locale() !== 'ru'
|
||||
? 'Discours'
|
||||
: translit(author.name || '', locale() || 'ru')
|
||||
|
||||
return (
|
||||
<>
|
||||
<Show when={index() > 0}>, </Show>
|
||||
<a href={`/author/${author.slug}`}>{name}</a>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={!props.settings?.nodate}>
|
||||
<div class="shout__date">{formattedDate()}</div>
|
||||
</Show>
|
||||
<div class="shout-card__titles-container">
|
||||
<a href={`/${slug || ''}`} onClick={handleClientRouteLinkClick}>
|
||||
<div class="shout-card__title">
|
||||
<span class="shout-card__link-container">{title}</span>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={props.settings?.isFeedMode}>
|
||||
<section class="shout-card__details">
|
||||
<div class="shout-card__details-content">
|
||||
<div class="shout-card__details-item rating">
|
||||
<button class="rating__control">−</button>
|
||||
<span class="rating__value">{stat?.rating || ''}</span>
|
||||
<button class="rating__control">+</button>
|
||||
</div>
|
||||
<div class="shout-card__details-item shout-card__comments">
|
||||
<Icon name="eye" />
|
||||
{stat?.viewed}
|
||||
</div>
|
||||
<div class="shout-card__details-item shout-card__comments">
|
||||
<a href={`/${slug + '#comments' || ''}`}>
|
||||
<Icon name="comment" />
|
||||
{stat?.commented || ''}
|
||||
</a>
|
||||
</div>
|
||||
<Show when={!props.settings?.nosubtitle && subtitle}>
|
||||
<div class="shout-card__subtitle">
|
||||
<span class="shout-card__link-container">{subtitle}</span>
|
||||
</div>
|
||||
</Show>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="shout-card__details-item">
|
||||
<button>
|
||||
<Icon name="bookmark" />
|
||||
</button>
|
||||
</div>
|
||||
<Show when={!props.settings?.noauthor || !props.settings?.nodate}>
|
||||
<div class="shout__details">
|
||||
<Show when={!props.settings?.noauthor}>
|
||||
<div class="shout__author">
|
||||
<For each={authors}>
|
||||
{(author, index) => {
|
||||
const name =
|
||||
author.name === 'Дискурс' && locale() !== 'ru'
|
||||
? 'Discours'
|
||||
: translit(author.name || '', locale() || 'ru')
|
||||
|
||||
<div class="shout-card__details-item">
|
||||
<button>
|
||||
<Icon name="ellipsis" />
|
||||
</button>
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
<Show when={index() > 0}>, </Show>
|
||||
<a href={`/author/${author.slug}`}>{name}</a>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={!props.settings?.nodate}>
|
||||
<div class="shout__date">{formattedDate()}</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={props.settings?.isFeedMode}>
|
||||
<section class="shout-card__details">
|
||||
<div class="shout-card__details-content">
|
||||
<div class="shout-card__details-item rating">
|
||||
<button class="rating__control">−</button>
|
||||
<span class="rating__value">{stat?.rating || ''}</span>
|
||||
<button class="rating__control">+</button>
|
||||
</div>
|
||||
<div class="shout-card__details-item shout-card__comments">
|
||||
<Icon name="eye" />
|
||||
{stat?.viewed}
|
||||
</div>
|
||||
<div class="shout-card__details-item shout-card__comments">
|
||||
<a href={`/${slug + '#comments' || ''}`}>
|
||||
<Icon name="comment" />
|
||||
{stat?.commented || ''}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button class="button--light shout-card__edit-control">{t('Collaborate')}</button>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="shout-card__details-item">
|
||||
<button>
|
||||
<Icon name="bookmark" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="shout-card__details-item">
|
||||
<button>
|
||||
<Icon name="ellipsis" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="button--light shout-card__edit-control">{t('Collaborate')}</button>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -16,12 +16,12 @@ type FeedSidebarProps = {
|
|||
export const FeedSidebar = (props: FeedSidebarProps) => {
|
||||
const { getSeen: seen } = useSeenStore()
|
||||
const auth = useStore(session)
|
||||
const { getSortedAuthors: authors } = useAuthorsStore({ authors: props.authors })
|
||||
const { getArticlesByTopic } = useArticlesStore()
|
||||
const { getTopicEntities } = useTopicsStore()
|
||||
const { authorEntities } = useAuthorsStore({ authors: props.authors })
|
||||
const { articlesByTopic } = useArticlesStore()
|
||||
const { topicEntities } = useTopicsStore()
|
||||
|
||||
const checkTopicIsSeen = (topicSlug: string) => {
|
||||
return getArticlesByTopic()[topicSlug].every((article) => Boolean(seen()[article.slug]))
|
||||
return articlesByTopic()[topicSlug].every((article) => Boolean(seen()[article.slug]))
|
||||
}
|
||||
|
||||
const checkAuthorIsSeen = (authorSlug: string) => {
|
||||
|
@ -69,7 +69,7 @@ export const FeedSidebar = (props: FeedSidebarProps) => {
|
|||
<li>
|
||||
<a href={`/author/${authorSlug}`} classList={{ unread: checkAuthorIsSeen(authorSlug) }}>
|
||||
<small>@{authorSlug}</small>
|
||||
{authors()[authorSlug].name}
|
||||
{authorEntities()[authorSlug].name}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
|
@ -79,7 +79,7 @@ export const FeedSidebar = (props: FeedSidebarProps) => {
|
|||
{(topicSlug) => (
|
||||
<li>
|
||||
<a href={`/author/${topicSlug}`} classList={{ unread: checkTopicIsSeen(topicSlug) }}>
|
||||
{getTopicEntities()[topicSlug]?.title}
|
||||
{topicEntities()[topicSlug]?.title}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
|
|
|
@ -18,17 +18,17 @@ export const ArticlePage = (props: PageProps) => {
|
|||
throw new Error('ts guard')
|
||||
}
|
||||
|
||||
const { getArticleEntities } = useArticlesStore({
|
||||
const { articleEntities } = useArticlesStore({
|
||||
sortedArticles
|
||||
})
|
||||
|
||||
const article = createMemo<Shout>(() => getArticleEntities()[page.params.slug])
|
||||
const article = createMemo<Shout>(() => articleEntities()[page.params.slug])
|
||||
|
||||
onMount(() => {
|
||||
const slug = page.params.slug
|
||||
const article = getArticleEntities()[slug]
|
||||
const articleValue = articleEntities()[slug]
|
||||
|
||||
if (!article || !article.body) {
|
||||
if (!articleValue || !articleValue.body) {
|
||||
loadArticle({ slug })
|
||||
}
|
||||
})
|
||||
|
|
|
@ -9,36 +9,42 @@ import { getLogger } from '../utils/logger'
|
|||
|
||||
import type { PageProps } from './types'
|
||||
|
||||
// do not remove
|
||||
// for debugging, to disable lazy loading
|
||||
// import HomePage from './Pages/HomePage'
|
||||
// import AllTopicsPage from './Pages/AllTopicsPage'
|
||||
// import TopicPage from './Pages/TopicPage'
|
||||
// import AllAuthorsPage from './Pages/AllAuthorsPage'
|
||||
// import AuthorPage from './Pages/AuthorPage'
|
||||
// import FeedPage from './Pages/FeedPage'
|
||||
// import ArticlePage from './Pages/ArticlePage'
|
||||
// import SearchPage from './Pages/SearchPage'
|
||||
// import FourOuFourPage from './Pages/FourOuFourPage'
|
||||
import { HomePage } from './Pages/HomePage'
|
||||
import { AllTopicsPage } from './Pages/AllTopicsPage'
|
||||
import { TopicPage } from './Pages/TopicPage'
|
||||
import { AllAuthorsPage } from './Pages/AllAuthorsPage'
|
||||
import { AuthorPage } from './Pages/AuthorPage'
|
||||
import { FeedPage } from './Pages/FeedPage'
|
||||
import { ArticlePage } from './Pages/ArticlePage'
|
||||
import { SearchPage } from './Pages/SearchPage'
|
||||
import { FourOuFourPage } from './Pages/FourOuFourPage'
|
||||
import { DogmaPage } from './Pages/about/DogmaPage'
|
||||
import { GuidePage } from './Pages/about/GuidePage'
|
||||
import { HelpPage } from './Pages/about/HelpPage'
|
||||
import { ManifestPage } from './Pages/about/ManifestPage'
|
||||
import { PartnersPage } from './Pages/about/PartnersPage'
|
||||
import { ProjectsPage } from './Pages/about/ProjectsPage'
|
||||
import { TermsOfUsePage } from './Pages/about/TermsOfUsePage'
|
||||
import { ThanksPage } from './Pages/about/ThanksPage'
|
||||
|
||||
const HomePage = lazy(() => import('./Pages/HomePage'))
|
||||
const AllTopicsPage = lazy(() => import('./Pages/AllTopicsPage'))
|
||||
const TopicPage = lazy(() => import('./Pages/TopicPage'))
|
||||
const AllAuthorsPage = lazy(() => import('./Pages/AllAuthorsPage'))
|
||||
const AuthorPage = lazy(() => import('./Pages/AuthorPage'))
|
||||
const FeedPage = lazy(() => import('./Pages/FeedPage'))
|
||||
const ArticlePage = lazy(() => import('./Pages/ArticlePage'))
|
||||
const SearchPage = lazy(() => import('./Pages/SearchPage'))
|
||||
const FourOuFourPage = lazy(() => import('./Pages/FourOuFourPage'))
|
||||
const DogmaPage = lazy(() => import('./Pages/about/DogmaPage'))
|
||||
|
||||
const GuidePage = lazy(() => import('./Pages/about/GuidePage'))
|
||||
const HelpPage = lazy(() => import('./Pages/about/HelpPage'))
|
||||
const ManifestPage = lazy(() => import('./Pages/about/ManifestPage'))
|
||||
const PartnersPage = lazy(() => import('./Pages/about/PartnersPage'))
|
||||
const ProjectsPage = lazy(() => import('./Pages/about/ProjectsPage'))
|
||||
const TermsOfUsePage = lazy(() => import('./Pages/about/TermsOfUsePage'))
|
||||
const ThanksPage = lazy(() => import('./Pages/about/ThanksPage'))
|
||||
// TODO: lazy load
|
||||
// const HomePage = lazy(() => import('./Pages/HomePage'))
|
||||
// const AllTopicsPage = lazy(() => import('./Pages/AllTopicsPage'))
|
||||
// const TopicPage = lazy(() => import('./Pages/TopicPage'))
|
||||
// const AllAuthorsPage = lazy(() => import('./Pages/AllAuthorsPage'))
|
||||
// const AuthorPage = lazy(() => import('./Pages/AuthorPage'))
|
||||
// const FeedPage = lazy(() => import('./Pages/FeedPage'))
|
||||
// const ArticlePage = lazy(() => import('./Pages/ArticlePage'))
|
||||
// const SearchPage = lazy(() => import('./Pages/SearchPage'))
|
||||
// const FourOuFourPage = lazy(() => import('./Pages/FourOuFourPage'))
|
||||
// const DogmaPage = lazy(() => import('./Pages/about/DogmaPage'))
|
||||
// const GuidePage = lazy(() => import('./Pages/about/GuidePage'))
|
||||
// const HelpPage = lazy(() => import('./Pages/about/HelpPage'))
|
||||
// const ManifestPage = lazy(() => import('./Pages/about/ManifestPage'))
|
||||
// const PartnersPage = lazy(() => import('./Pages/about/PartnersPage'))
|
||||
// const ProjectsPage = lazy(() => import('./Pages/about/ProjectsPage'))
|
||||
// const TermsOfUsePage = lazy(() => import('./Pages/about/TermsOfUsePage'))
|
||||
// const ThanksPage = lazy(() => import('./Pages/about/ThanksPage'))
|
||||
|
||||
const log = getLogger('root')
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ type Props = {
|
|||
}
|
||||
|
||||
export const AllAuthorsView = (props: Props) => {
|
||||
const { getSortedAuthors: authorslist } = useAuthorsStore({ authors: props.authors })
|
||||
const { sortedAuthors: authorList } = useAuthorsStore({ authors: props.authors })
|
||||
const [sortedAuthors, setSortedAuthors] = createSignal<Author[]>([])
|
||||
const [sortedKeys, setSortedKeys] = createSignal<string[]>([])
|
||||
const [abc, setAbc] = createSignal([])
|
||||
|
@ -32,7 +32,7 @@ export const AllAuthorsView = (props: Props) => {
|
|||
createEffect(() => {
|
||||
if ((!getSearchParams().by || getSearchParams().by === 'name') && abc().length === 0) {
|
||||
console.log('[authors] default grouping by abc')
|
||||
const grouped = { ...groupByName(authorslist()) }
|
||||
const grouped = { ...groupByName(authorList()) }
|
||||
grouped['A-Z'] = sortBy(grouped['A-Z'], byFirstChar)
|
||||
setAbc(grouped)
|
||||
const keys = Object.keys(abc)
|
||||
|
@ -40,13 +40,13 @@ export const AllAuthorsView = (props: Props) => {
|
|||
setSortedKeys(keys as string[])
|
||||
} else {
|
||||
console.log('[authors] sorting by ' + getSearchParams().by)
|
||||
setSortedAuthors(sortBy(authorslist(), getSearchParams().by))
|
||||
setSortedAuthors(sortBy(authorList(), getSearchParams().by))
|
||||
}
|
||||
}, [authorslist(), getSearchParams().by])
|
||||
}, [authorList(), getSearchParams().by])
|
||||
|
||||
return (
|
||||
<div class="all-topics-page">
|
||||
<Show when={sortedAuthors()}>
|
||||
<Show when={sortedAuthors().length > 0}>
|
||||
<div class="wide-container">
|
||||
<div class="shift-content">
|
||||
<div class="row">
|
||||
|
|
|
@ -2,7 +2,7 @@ import { createEffect, For, Show } from 'solid-js'
|
|||
import type { Topic } from '../../graphql/types.gen'
|
||||
import { Icon } from '../Nav/Icon'
|
||||
import { t } from '../../utils/intl'
|
||||
import { setSortAllTopicsBy, useTopicsStore } from '../../stores/zine/topics'
|
||||
import { setSortAllBy as setSortAllTopicsBy, useTopicsStore } from '../../stores/zine/topics'
|
||||
import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
|
||||
import { TopicCard } from '../Topic/Card'
|
||||
import { session } from '../../stores/auth'
|
||||
|
@ -20,7 +20,7 @@ type Props = {
|
|||
export const AllTopicsView = (props: Props) => {
|
||||
const { getSearchParams, changeSearchParam } = useRouter<AllTopicsPageSearchParams>()
|
||||
|
||||
const { getSortedTopics } = useTopicsStore({
|
||||
const { sortedTopics } = useTopicsStore({
|
||||
topics: props.topics,
|
||||
sortBy: getSearchParams().by || 'shouts'
|
||||
})
|
||||
|
@ -34,7 +34,7 @@ export const AllTopicsView = (props: Props) => {
|
|||
|
||||
return (
|
||||
<div class="all-topics-page">
|
||||
<Show when={getSortedTopics().length > 0}>
|
||||
<Show when={sortedTopics().length > 0}>
|
||||
<div class="wide-container">
|
||||
<div class="shift-content">
|
||||
<div class="row">
|
||||
|
@ -78,7 +78,7 @@ export const AllTopicsView = (props: Props) => {
|
|||
</ul>
|
||||
|
||||
<div class="stats">
|
||||
<For each={getSortedTopics()}>
|
||||
<For each={sortedTopics()}>
|
||||
{(topic) => (
|
||||
<TopicCard topic={topic} compact={false} subscribed={subscribed(topic.slug)} />
|
||||
)}
|
||||
|
|
|
@ -25,12 +25,12 @@ type AuthorPageSearchParams = {
|
|||
}
|
||||
|
||||
export const AuthorView = (props: AuthorProps) => {
|
||||
const { getSortedArticles: articles } = useArticlesStore({
|
||||
const { sortedArticles } = useArticlesStore({
|
||||
sortedArticles: props.authorArticles
|
||||
})
|
||||
const { getAuthorEntities: authors } = useAuthorsStore({ authors: [props.author] })
|
||||
const { authorEntities } = useAuthorsStore({ authors: [props.author] })
|
||||
|
||||
const author = createMemo(() => authors()[props.author.slug])
|
||||
const author = createMemo(() => authorEntities()[props.author.slug])
|
||||
const { getSearchParams, changeSearchParam } = useRouter<AuthorPageSearchParams>()
|
||||
|
||||
//const slug = createMemo(() => author().slug)
|
||||
|
@ -90,7 +90,7 @@ export const AuthorView = (props: AuthorProps) => {
|
|||
<div class="floor">
|
||||
<h3 class="col-12">{title()}</h3>
|
||||
<div class="row">
|
||||
<Show when={articles()?.length > 0}>
|
||||
<Show when={sortedArticles().length > 0}>
|
||||
{/*FIXME*/}
|
||||
{/*<Beside*/}
|
||||
{/* title={t('Topics which supported by author')}*/}
|
||||
|
@ -99,10 +99,10 @@ export const AuthorView = (props: AuthorProps) => {
|
|||
{/* wrapper={'topic'}*/}
|
||||
{/* topicShortDescription={true}*/}
|
||||
{/*/>*/}
|
||||
<Row3 articles={articles().slice(1, 4)} />
|
||||
<Row2 articles={articles().slice(4, 6)} />
|
||||
<Row3 articles={articles().slice(10, 13)} />
|
||||
<Row3 articles={articles().slice(13, 16)} />
|
||||
<Row3 articles={sortedArticles().slice(1, 4)} />
|
||||
<Row2 articles={sortedArticles().slice(4, 6)} />
|
||||
<Row3 articles={sortedArticles().slice(10, 13)} />
|
||||
<Row3 articles={sortedArticles().slice(13, 16)} />
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -31,11 +31,11 @@ interface FeedProps {
|
|||
|
||||
export const FeedView = (props: FeedProps) => {
|
||||
// state
|
||||
const { getSortedArticles: articles } = useArticlesStore({ sortedArticles: props.articles })
|
||||
const { sortedArticles } = useArticlesStore({ sortedArticles: props.articles })
|
||||
const reactions = useReactionsStore()
|
||||
const { getSortedAuthors: authors } = useAuthorsStore()
|
||||
const { getTopTopics } = useTopicsStore()
|
||||
const { getTopAuthors } = useTopAuthorsStore()
|
||||
const { sortedAuthors } = useAuthorsStore()
|
||||
const { topTopics } = useTopicsStore()
|
||||
const { topAuthors } = useTopAuthorsStore()
|
||||
|
||||
const auth = useStore(session)
|
||||
|
||||
|
@ -66,7 +66,7 @@ export const FeedView = (props: FeedProps) => {
|
|||
<div class="container feed">
|
||||
<div class="row">
|
||||
<div class="col-md-3 feed-navigation">
|
||||
<FeedSidebar authors={authors()} />
|
||||
<FeedSidebar authors={sortedAuthors()} />
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
|
@ -87,8 +87,8 @@ export const FeedView = (props: FeedProps) => {
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
<Show when={articles().length > 0}>
|
||||
<For each={articles().slice(0, 4)}>
|
||||
<Show when={sortedArticles().length > 0}>
|
||||
<For each={sortedArticles().slice(0, 4)}>
|
||||
{(article) => <ArticleCard article={article} settings={{ isFeedMode: true }} />}
|
||||
</For>
|
||||
|
||||
|
@ -101,7 +101,7 @@ export const FeedView = (props: FeedProps) => {
|
|||
</div>
|
||||
|
||||
<ul class="beside-column">
|
||||
<For each={getTopAuthors().slice(0, 5)}>
|
||||
<For each={topAuthors().slice(0, 5)}>
|
||||
{(author) => (
|
||||
<li>
|
||||
<AuthorCard author={author} compact={true} hasLink={true} />
|
||||
|
@ -110,7 +110,7 @@ export const FeedView = (props: FeedProps) => {
|
|||
</For>
|
||||
</ul>
|
||||
|
||||
<For each={articles().slice(4)}>
|
||||
<For each={sortedArticles().slice(4)}>
|
||||
{(article) => <ArticleCard article={article} settings={{ isFeedMode: true }} />}
|
||||
</For>
|
||||
</Show>
|
||||
|
@ -127,10 +127,10 @@ export const FeedView = (props: FeedProps) => {
|
|||
{(comment) => <CommentCard comment={comment} compact={true} />}
|
||||
</For>
|
||||
</section>
|
||||
<Show when={getTopTopics().length > 0}>
|
||||
<Show when={topTopics().length > 0}>
|
||||
<section class="feed-topics">
|
||||
<h4>{t('Topics')}</h4>
|
||||
<For each={getTopTopics().slice(0, 5)}>
|
||||
<For each={topTopics().slice(0, 5)}>
|
||||
{(topic) => <TopicCard topic={topic} subscribeButtonBottom={true} />}
|
||||
</For>
|
||||
</section>
|
||||
|
|
|
@ -36,44 +36,43 @@ const LOAD_MORE_ARTICLES_COUNT = 30
|
|||
|
||||
export const HomeView = (props: HomeProps) => {
|
||||
const {
|
||||
getSortedArticles,
|
||||
getTopArticles,
|
||||
getTopMonthArticles,
|
||||
getTopViewedArticles,
|
||||
getTopCommentedArticles,
|
||||
getArticlesByLayout
|
||||
sortedArticles,
|
||||
topArticles,
|
||||
topMonthArticles,
|
||||
topViewedArticles,
|
||||
topCommentedArticles,
|
||||
articlesByLayout
|
||||
} = useArticlesStore({
|
||||
sortedArticles: props.recentPublishedArticles
|
||||
})
|
||||
const { getRandomTopics, getTopTopics } = useTopicsStore({
|
||||
const { randomTopics, topTopics } = useTopicsStore({
|
||||
randomTopics: props.randomTopics
|
||||
})
|
||||
|
||||
const { getTopAuthors } = useTopAuthorsStore()
|
||||
const { topAuthors } = useTopAuthorsStore()
|
||||
|
||||
onMount(() => {
|
||||
loadTopArticles()
|
||||
loadTopMonthArticles()
|
||||
loadPublishedArticles({ limit: CLIENT_LOAD_ARTICLES_COUNT, offset: getSortedArticles().length })
|
||||
loadPublishedArticles({ limit: CLIENT_LOAD_ARTICLES_COUNT, offset: sortedArticles().length })
|
||||
})
|
||||
|
||||
const randomLayout = createMemo(() => {
|
||||
const articlesByLayout = getArticlesByLayout()
|
||||
const filledLayouts = Object.keys(articlesByLayout).filter(
|
||||
const filledLayouts = Object.keys(articlesByLayout()).filter(
|
||||
// FIXME: is 7 ok? or more complex logic needed?
|
||||
(layout) => articlesByLayout[layout].length > 7
|
||||
(layout) => articlesByLayout()[layout].length > 7
|
||||
)
|
||||
|
||||
const randomLayout =
|
||||
const selectedRandomLayout =
|
||||
filledLayouts.length > 0 ? filledLayouts[Math.floor(Math.random() * filledLayouts.length)] : ''
|
||||
|
||||
return (
|
||||
<Show when={Boolean(randomLayout)}>
|
||||
<Show when={Boolean(selectedRandomLayout)}>
|
||||
<Group
|
||||
articles={articlesByLayout[randomLayout]}
|
||||
articles={articlesByLayout()[selectedRandomLayout]}
|
||||
header={
|
||||
<div class="layout-icon">
|
||||
<Icon name={randomLayout} />
|
||||
<Icon name={selectedRandomLayout} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
@ -82,68 +81,70 @@ export const HomeView = (props: HomeProps) => {
|
|||
})
|
||||
|
||||
const loadMore = () => {
|
||||
loadPublishedArticles({ limit: LOAD_MORE_ARTICLES_COUNT, offset: getSortedArticles().length })
|
||||
loadPublishedArticles({ limit: LOAD_MORE_ARTICLES_COUNT, offset: sortedArticles().length })
|
||||
}
|
||||
|
||||
return (
|
||||
<Show when={locale()}>
|
||||
<NavTopics topics={getRandomTopics()} />
|
||||
<NavTopics topics={randomTopics()} />
|
||||
|
||||
<Row5 articles={getSortedArticles().slice(0, 5)} />
|
||||
<Row5 articles={sortedArticles().slice(0, 5)} />
|
||||
|
||||
<Hero />
|
||||
|
||||
<Beside
|
||||
beside={getSortedArticles().slice(4, 5)[0]}
|
||||
title={t('Top viewed')}
|
||||
values={getTopViewedArticles().slice(0, 5)}
|
||||
wrapper={'top-article'}
|
||||
/>
|
||||
|
||||
<Row3 articles={getSortedArticles().slice(6, 9)} />
|
||||
|
||||
{/*FIXME: ?*/}
|
||||
<Show when={getTopAuthors().length === 5}>
|
||||
<Show when={sortedArticles().length > 5}>
|
||||
<Beside
|
||||
beside={getSortedArticles().slice(8, 9)[0]}
|
||||
title={t('Top authors')}
|
||||
values={getTopAuthors()}
|
||||
wrapper={'author'}
|
||||
beside={sortedArticles()[5]}
|
||||
title={t('Top viewed')}
|
||||
values={topViewedArticles().slice(0, 5)}
|
||||
wrapper={'top-article'}
|
||||
/>
|
||||
|
||||
<Row3 articles={sortedArticles().slice(6, 9)} />
|
||||
|
||||
{/*FIXME: ?*/}
|
||||
<Show when={topAuthors().length === 5}>
|
||||
<Beside
|
||||
beside={sortedArticles().slice(8, 9)[0]}
|
||||
title={t('Top authors')}
|
||||
values={topAuthors()}
|
||||
wrapper={'author'}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<Slider title={t('Top month articles')} articles={topMonthArticles()} />
|
||||
|
||||
<Row2 articles={sortedArticles().slice(10, 12)} />
|
||||
|
||||
<RowShort articles={sortedArticles().slice(12, 16)} />
|
||||
|
||||
<Row1 article={sortedArticles().slice(15, 16)[0]} />
|
||||
<Row3 articles={sortedArticles().slice(17, 20)} />
|
||||
<Row3 articles={topCommentedArticles()} header={<h2>{t('Top commented')}</h2>} />
|
||||
|
||||
{randomLayout()}
|
||||
|
||||
<Slider title={t('Favorite')} articles={topArticles()} />
|
||||
|
||||
<Beside
|
||||
beside={sortedArticles()[20]}
|
||||
title={t('Top topics')}
|
||||
values={topTopics().slice(0, 5)}
|
||||
wrapper={'topic'}
|
||||
isTopicCompact={true}
|
||||
/>
|
||||
|
||||
<Row3 articles={sortedArticles().slice(21, 24)} />
|
||||
|
||||
<Banner />
|
||||
|
||||
<Row2 articles={sortedArticles().slice(24, 26)} />
|
||||
<Row3 articles={sortedArticles().slice(26, 29)} />
|
||||
<Row2 articles={sortedArticles().slice(29, 31)} />
|
||||
<Row3 articles={sortedArticles().slice(31, 34)} />
|
||||
</Show>
|
||||
|
||||
<Slider title={t('Top month articles')} articles={getTopMonthArticles()} />
|
||||
|
||||
<Row2 articles={getSortedArticles().slice(10, 12)} />
|
||||
|
||||
<RowShort articles={getSortedArticles().slice(12, 16)} />
|
||||
|
||||
<Row1 article={getSortedArticles().slice(15, 16)[0]} />
|
||||
<Row3 articles={getSortedArticles().slice(17, 20)} />
|
||||
<Row3 articles={getTopCommentedArticles()} header={<h2>{t('Top commented')}</h2>} />
|
||||
|
||||
{randomLayout()}
|
||||
|
||||
<Slider title={t('Favorite')} articles={getTopArticles()} />
|
||||
|
||||
<Beside
|
||||
beside={getSortedArticles().slice(19, 20)[0]}
|
||||
title={t('Top topics')}
|
||||
values={getTopTopics().slice(0, 5)}
|
||||
wrapper={'topic'}
|
||||
isTopicCompact={true}
|
||||
/>
|
||||
|
||||
<Row3 articles={getSortedArticles().slice(21, 24)} />
|
||||
|
||||
<Banner />
|
||||
|
||||
<Row2 articles={getSortedArticles().slice(24, 26)} />
|
||||
<Row3 articles={getSortedArticles().slice(26, 29)} />
|
||||
<Row2 articles={getSortedArticles().slice(29, 31)} />
|
||||
<Row3 articles={getSortedArticles().slice(31, 34)} />
|
||||
|
||||
<For each={getSortedArticles().slice(35)}>{(article) => <Row1 article={article} />}</For>
|
||||
<For each={sortedArticles().slice(35)}>{(article) => <Row1 article={article} />}</For>
|
||||
|
||||
<p class="load-more-container">
|
||||
<button class="button" onClick={loadMore}>
|
||||
|
|
|
@ -16,7 +16,7 @@ type Props = {
|
|||
}
|
||||
|
||||
export const SearchView = (props: Props) => {
|
||||
const { getSortedArticles } = useArticlesStore({ sortedArticles: props.results })
|
||||
const { sortedArticles } = useArticlesStore({ sortedArticles: props.results })
|
||||
const [getQuery, setQuery] = createSignal(props.query)
|
||||
|
||||
const { getSearchParams } = useRouter<SearchPageSearchParams>()
|
||||
|
@ -66,12 +66,12 @@ export const SearchView = (props: Props) => {
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
<Show when={getSortedArticles().length > 0}>
|
||||
<Show when={sortedArticles().length > 0}>
|
||||
<h3>{t('Publications')}</h3>
|
||||
|
||||
<div class="floor">
|
||||
<div class="row">
|
||||
<For each={getSortedArticles()}>
|
||||
<For each={sortedArticles()}>
|
||||
{(article) => (
|
||||
<div class="col-md-3">
|
||||
<ArticleCard article={article} />
|
||||
|
|
|
@ -24,12 +24,12 @@ interface TopicProps {
|
|||
export const TopicView = (props: TopicProps) => {
|
||||
const { getSearchParams, changeSearchParam } = useRouter<TopicsPageSearchParams>()
|
||||
|
||||
const { getSortedArticles: sortedArticles } = useArticlesStore({ sortedArticles: props.topicArticles })
|
||||
const { getTopicEntities } = useTopicsStore({ topics: [props.topic] })
|
||||
const { sortedArticles } = useArticlesStore({ sortedArticles: props.topicArticles })
|
||||
const { topicEntities } = useTopicsStore({ topics: [props.topic] })
|
||||
|
||||
const { getAuthorsByTopic } = useAuthorsStore()
|
||||
const { authorsByTopic } = useAuthorsStore()
|
||||
|
||||
const topic = createMemo(() => getTopicEntities()[props.topic.slug])
|
||||
const topic = createMemo(() => topicEntities()[props.topic.slug])
|
||||
|
||||
/*
|
||||
const slug = createMemo<string>(() => {
|
||||
|
@ -104,7 +104,7 @@ export const TopicView = (props: TopicProps) => {
|
|||
<Show when={sortedArticles().length > 5}>
|
||||
<Beside
|
||||
title={t('Topic is supported by')}
|
||||
values={getAuthorsByTopic()[topic().slug].slice(0, 7)}
|
||||
values={authorsByTopic()[topic().slug].slice(0, 7)}
|
||||
beside={sortedArticles()[6]}
|
||||
wrapper={'author'}
|
||||
/>
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
"Join the community": "Присоединиться к сообществу",
|
||||
"Join the global community of authors!": "Присоединятесь к глобальному сообществу авторов со всего мира!",
|
||||
"Knowledge base": "База знаний",
|
||||
"Load more": "Загрузить ещё",
|
||||
"Load more": "Показать ещё",
|
||||
"Loading": "Загрузка",
|
||||
"Manifest": "Манифест",
|
||||
"More": "Ещё",
|
||||
|
|
|
@ -1,87 +1,69 @@
|
|||
import { atom, computed, map, ReadableAtom } from 'nanostores'
|
||||
import type { Author, Shout, Topic } from '../../graphql/types.gen'
|
||||
import type { WritableAtom } from 'nanostores'
|
||||
import { useStore } from '@nanostores/solid'
|
||||
import { apiClient } from '../../utils/apiClient'
|
||||
import { addAuthorsByTopic } from './authors'
|
||||
import { addTopicsByAuthor } from './topics'
|
||||
import { byStat } from '../../utils/sortby'
|
||||
|
||||
import { getLogger } from '../../utils/logger'
|
||||
import { createSignal } from 'solid-js'
|
||||
import { createMemo, createSignal } from 'solid-js'
|
||||
|
||||
const log = getLogger('articles store')
|
||||
|
||||
let articleEntitiesStore: WritableAtom<{ [articleSlug: string]: Shout }>
|
||||
let articlesByAuthorsStore: ReadableAtom<{ [authorSlug: string]: Shout[] }>
|
||||
let articlesByLayoutStore: ReadableAtom<{ [layout: string]: Shout[] }>
|
||||
let articlesByTopicsStore: ReadableAtom<{ [topicSlug: string]: Shout[] }>
|
||||
let topViewedArticlesStore: ReadableAtom<Shout[]>
|
||||
let topCommentedArticlesStore: ReadableAtom<Shout[]>
|
||||
const [sortedArticles, setSortedArticles] = createSignal<Shout[]>([])
|
||||
const [articleEntities, setArticleEntities] = createSignal<{ [articleSlug: string]: Shout }>({})
|
||||
|
||||
const [getSortedArticles, setSortedArticles] = createSignal<Shout[]>([])
|
||||
const [topArticles, setTopArticles] = createSignal<Shout[]>([])
|
||||
const [topMonthArticles, setTopMonthArticles] = createSignal<Shout[]>([])
|
||||
|
||||
const topArticlesStore = atom<Shout[]>()
|
||||
const topMonthArticlesStore = atom<Shout[]>()
|
||||
|
||||
const initStore = (initial?: Record<string, Shout>) => {
|
||||
log.debug('initStore')
|
||||
if (articleEntitiesStore) {
|
||||
throw new Error('articles store already initialized')
|
||||
}
|
||||
|
||||
articleEntitiesStore = map(initial)
|
||||
|
||||
articlesByAuthorsStore = computed(articleEntitiesStore, (articleEntities) => {
|
||||
return Object.values(articleEntities).reduce((acc, article) => {
|
||||
article.authors.forEach((author) => {
|
||||
if (!acc[author.slug]) {
|
||||
acc[author.slug] = []
|
||||
}
|
||||
acc[author.slug].push(article)
|
||||
})
|
||||
|
||||
return acc
|
||||
}, {} as { [authorSlug: string]: Shout[] })
|
||||
})
|
||||
|
||||
articlesByTopicsStore = computed(articleEntitiesStore, (articleEntities) => {
|
||||
return Object.values(articleEntities).reduce((acc, article) => {
|
||||
article.topics.forEach((topic) => {
|
||||
if (!acc[topic.slug]) {
|
||||
acc[topic.slug] = []
|
||||
}
|
||||
acc[topic.slug].push(article)
|
||||
})
|
||||
|
||||
return acc
|
||||
}, {} as { [authorSlug: string]: Shout[] })
|
||||
})
|
||||
|
||||
articlesByLayoutStore = computed(articleEntitiesStore, (articleEntities) => {
|
||||
return Object.values(articleEntities).reduce((acc, article) => {
|
||||
if (!acc[article.layout]) {
|
||||
acc[article.layout] = []
|
||||
const articlesByAuthor = createMemo(() => {
|
||||
return Object.values(articleEntities()).reduce((acc, article) => {
|
||||
article.authors.forEach((author) => {
|
||||
if (!acc[author.slug]) {
|
||||
acc[author.slug] = []
|
||||
}
|
||||
acc[author.slug].push(article)
|
||||
})
|
||||
|
||||
acc[article.layout].push(article)
|
||||
return acc
|
||||
}, {} as { [authorSlug: string]: Shout[] })
|
||||
})
|
||||
|
||||
return acc
|
||||
}, {} as { [layout: string]: Shout[] })
|
||||
})
|
||||
const articlesByTopic = createMemo(() => {
|
||||
return Object.values(articleEntities()).reduce((acc, article) => {
|
||||
article.topics.forEach((topic) => {
|
||||
if (!acc[topic.slug]) {
|
||||
acc[topic.slug] = []
|
||||
}
|
||||
acc[topic.slug].push(article)
|
||||
})
|
||||
|
||||
topViewedArticlesStore = computed(articleEntitiesStore, (articleEntities) => {
|
||||
const sortedArticles = Object.values(articleEntities)
|
||||
sortedArticles.sort(byStat('viewed'))
|
||||
return sortedArticles
|
||||
})
|
||||
return acc
|
||||
}, {} as { [authorSlug: string]: Shout[] })
|
||||
})
|
||||
|
||||
topCommentedArticlesStore = computed(articleEntitiesStore, (articleEntities) => {
|
||||
const sortedArticles = Object.values(articleEntities)
|
||||
sortedArticles.sort(byStat('commented'))
|
||||
return sortedArticles
|
||||
})
|
||||
}
|
||||
const articlesByLayout = createMemo(() => {
|
||||
return Object.values(articleEntities()).reduce((acc, article) => {
|
||||
if (!acc[article.layout]) {
|
||||
acc[article.layout] = []
|
||||
}
|
||||
|
||||
acc[article.layout].push(article)
|
||||
|
||||
return acc
|
||||
}, {} as { [layout: string]: Shout[] })
|
||||
})
|
||||
|
||||
const topViewedArticles = createMemo(() => {
|
||||
const result = Object.values(articleEntities())
|
||||
result.sort(byStat('viewed'))
|
||||
return result
|
||||
})
|
||||
|
||||
const topCommentedArticles = createMemo(() => {
|
||||
const result = Object.values(articleEntities())
|
||||
result.sort(byStat('commented'))
|
||||
return result
|
||||
})
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const addArticles = (...args: Shout[][]) => {
|
||||
|
@ -92,14 +74,12 @@ const addArticles = (...args: Shout[][]) => {
|
|||
return acc
|
||||
}, {} as { [articleSLug: string]: Shout })
|
||||
|
||||
if (!articleEntitiesStore) {
|
||||
initStore(newArticleEntities)
|
||||
} else {
|
||||
articleEntitiesStore.set({
|
||||
...articleEntitiesStore.get(),
|
||||
setArticleEntities((prevArticleEntities) => {
|
||||
return {
|
||||
...prevArticleEntities,
|
||||
...newArticleEntities
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const authorsByTopic = allArticles.reduce((acc, article) => {
|
||||
const { authors, topics } = article
|
||||
|
@ -173,13 +153,13 @@ export const loadPublishedArticles = async ({
|
|||
export const loadTopMonthArticles = async (): Promise<void> => {
|
||||
const articles = await apiClient.getTopMonthArticles()
|
||||
addArticles(articles)
|
||||
topMonthArticlesStore.set(articles)
|
||||
setTopMonthArticles(articles)
|
||||
}
|
||||
|
||||
export const loadTopArticles = async (): Promise<void> => {
|
||||
const articles = await apiClient.getTopArticles()
|
||||
addArticles(articles)
|
||||
topArticlesStore.set(articles)
|
||||
setTopArticles(articles)
|
||||
}
|
||||
|
||||
export const loadSearchResults = async ({
|
||||
|
@ -217,34 +197,21 @@ type InitialState = {
|
|||
}
|
||||
|
||||
export const useArticlesStore = (initialState: InitialState = {}) => {
|
||||
const sortedArticles = [...(initialState.sortedArticles || [])]
|
||||
addArticles([...(initialState.sortedArticles || [])])
|
||||
|
||||
addArticles(sortedArticles)
|
||||
|
||||
if (sortedArticles) {
|
||||
addSortedArticles(sortedArticles)
|
||||
if (initialState.sortedArticles) {
|
||||
setSortedArticles([...initialState.sortedArticles])
|
||||
}
|
||||
|
||||
const getArticleEntities = useStore(articleEntitiesStore)
|
||||
const getTopArticles = useStore(topArticlesStore)
|
||||
const getTopMonthArticles = useStore(topMonthArticlesStore)
|
||||
const getArticlesByAuthor = useStore(articlesByAuthorsStore)
|
||||
const getArticlesByTopic = useStore(articlesByTopicsStore)
|
||||
const getArticlesByLayout = useStore(articlesByLayoutStore)
|
||||
// TODO: get from server
|
||||
const getTopViewedArticles = useStore(topViewedArticlesStore)
|
||||
// TODO: get from server
|
||||
const getTopCommentedArticles = useStore(topCommentedArticlesStore)
|
||||
|
||||
return {
|
||||
getArticleEntities,
|
||||
getSortedArticles,
|
||||
getArticlesByTopic,
|
||||
getArticlesByAuthor,
|
||||
getTopArticles,
|
||||
getTopMonthArticles,
|
||||
getTopViewedArticles,
|
||||
getTopCommentedArticles,
|
||||
getArticlesByLayout
|
||||
articleEntities,
|
||||
sortedArticles,
|
||||
articlesByTopic,
|
||||
articlesByAuthor,
|
||||
topArticles,
|
||||
topMonthArticles,
|
||||
topViewedArticles,
|
||||
topCommentedArticles,
|
||||
articlesByLayout
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,50 +1,36 @@
|
|||
import { apiClient } from '../../utils/apiClient'
|
||||
import type { ReadableAtom, WritableAtom } from 'nanostores'
|
||||
import { atom, computed } from 'nanostores'
|
||||
import type { Author } from '../../graphql/types.gen'
|
||||
import { useStore } from '@nanostores/solid'
|
||||
import { byCreated } from '../../utils/sortby'
|
||||
|
||||
import { getLogger } from '../../utils/logger'
|
||||
import { Accessor, createMemo, createSignal } from 'solid-js'
|
||||
import type { Signal } from 'solid-js'
|
||||
|
||||
const log = getLogger('authors store')
|
||||
|
||||
export type AuthorsSortBy = 'created' | 'name'
|
||||
|
||||
const sortAllByStore = atom<AuthorsSortBy>('created')
|
||||
const [sortAllBy, setSortAllBy] = createSignal<AuthorsSortBy>('created')
|
||||
|
||||
let authorEntitiesStore: WritableAtom<{ [authorSlug: string]: Author }>
|
||||
let authorsByTopicStore: WritableAtom<{ [topicSlug: string]: Author[] }>
|
||||
let sortedAuthorsStore: ReadableAtom<Author[]>
|
||||
const [authorEntities, setAuthorEntities] = createSignal<{ [authorSlug: string]: Author }>({})
|
||||
const [authorsByTopic, setAuthorsByTopic] = createSignal<{ [topicSlug: string]: Author[] }>({})
|
||||
|
||||
const initStore = (initial: { [authorSlug: string]: Author }) => {
|
||||
if (authorEntitiesStore) {
|
||||
return
|
||||
}
|
||||
|
||||
authorEntitiesStore = atom(initial)
|
||||
|
||||
sortedAuthorsStore = computed([authorEntitiesStore, sortAllByStore], (authorEntities, sortBy) => {
|
||||
const authors = Object.values(authorEntities)
|
||||
switch (sortBy) {
|
||||
case 'created': {
|
||||
// log.debug('sorted by created')
|
||||
authors.sort(byCreated)
|
||||
break
|
||||
}
|
||||
case 'name': {
|
||||
// log.debug('sorted by name')
|
||||
authors.sort((a, b) => a.name.localeCompare(b.name))
|
||||
break
|
||||
}
|
||||
const sortedAuthors = createMemo(() => {
|
||||
const authors = Object.values(authorEntities())
|
||||
switch (sortAllBy()) {
|
||||
case 'created': {
|
||||
// log.debug('sorted by created')
|
||||
authors.sort(byCreated)
|
||||
break
|
||||
}
|
||||
return authors
|
||||
})
|
||||
}
|
||||
|
||||
export const setSortAllBy = (sortBy: AuthorsSortBy) => {
|
||||
sortAllByStore.set(sortBy)
|
||||
}
|
||||
case 'name': {
|
||||
// log.debug('sorted by name')
|
||||
authors.sort((a, b) => a.name.localeCompare(b.name))
|
||||
break
|
||||
}
|
||||
}
|
||||
return authors
|
||||
})
|
||||
|
||||
const addAuthors = (authors: Author[]) => {
|
||||
const newAuthorEntities = authors.reduce((acc, author) => {
|
||||
|
@ -52,24 +38,20 @@ const addAuthors = (authors: Author[]) => {
|
|||
return acc
|
||||
}, {} as Record<string, Author>)
|
||||
|
||||
if (!authorEntitiesStore) {
|
||||
initStore(newAuthorEntities)
|
||||
} else {
|
||||
authorEntitiesStore.set({
|
||||
...authorEntitiesStore.get(),
|
||||
setAuthorEntities((prevAuthorEntities) => {
|
||||
return {
|
||||
...prevAuthorEntities,
|
||||
...newAuthorEntities
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const addAuthorsByTopic = (authorsByTopic: { [topicSlug: string]: Author[] }) => {
|
||||
const allAuthors = Object.values(authorsByTopic).flat()
|
||||
export const addAuthorsByTopic = (newAuthorsByTopic: { [topicSlug: string]: Author[] }) => {
|
||||
const allAuthors = Object.values(newAuthorsByTopic).flat()
|
||||
addAuthors(allAuthors)
|
||||
|
||||
if (!authorsByTopicStore) {
|
||||
authorsByTopicStore = atom<{ [topicSlug: string]: Author[] }>(authorsByTopic)
|
||||
} else {
|
||||
const newState = Object.entries(authorsByTopic).reduce((acc, [topicSlug, authors]) => {
|
||||
setAuthorsByTopic((prevAuthorsByTopic) => {
|
||||
return Object.entries(newAuthorsByTopic).reduce((acc, [topicSlug, authors]) => {
|
||||
if (!acc[topicSlug]) {
|
||||
acc[topicSlug] = []
|
||||
}
|
||||
|
@ -81,10 +63,8 @@ export const addAuthorsByTopic = (authorsByTopic: { [topicSlug: string]: Author[
|
|||
})
|
||||
|
||||
return acc
|
||||
}, authorsByTopicStore.get())
|
||||
|
||||
authorsByTopicStore.set(newState)
|
||||
}
|
||||
}, prevAuthorsByTopic)
|
||||
})
|
||||
}
|
||||
|
||||
export const loadAllAuthors = async (): Promise<void> => {
|
||||
|
@ -97,12 +77,7 @@ type InitialState = {
|
|||
}
|
||||
|
||||
export const useAuthorsStore = (initialState: InitialState = {}) => {
|
||||
const authors = [...(initialState.authors || [])]
|
||||
addAuthors(authors)
|
||||
addAuthors([...(initialState.authors || [])])
|
||||
|
||||
const getAuthorEntities = useStore(authorEntitiesStore)
|
||||
const getSortedAuthors = useStore(sortedAuthorsStore)
|
||||
const getAuthorsByTopic = useStore(authorsByTopicStore)
|
||||
|
||||
return { getAuthorEntities, getSortedAuthors, getAuthorsByTopic }
|
||||
return { authorEntities, sortedAuthors, authorsByTopic }
|
||||
}
|
||||
|
|
|
@ -5,20 +5,17 @@ import { useAuthorsStore } from './authors'
|
|||
const TOP_AUTHORS_COUNT = 5
|
||||
|
||||
export const useTopAuthorsStore = () => {
|
||||
const { getArticlesByAuthor } = useArticlesStore()
|
||||
const { getAuthorEntities } = useAuthorsStore()
|
||||
const { articlesByAuthor } = useArticlesStore()
|
||||
const { authorEntities } = useAuthorsStore()
|
||||
|
||||
const getTopAuthors = createMemo(() => {
|
||||
const articlesByAuthor = getArticlesByAuthor()
|
||||
const authorEntities = getAuthorEntities()
|
||||
|
||||
return Object.keys(articlesByAuthor)
|
||||
const topAuthors = createMemo(() => {
|
||||
return Object.keys(articlesByAuthor())
|
||||
.sort((authorSlug1, authorSlug2) => {
|
||||
const author1Rating = articlesByAuthor[authorSlug1].reduce(
|
||||
const author1Rating = articlesByAuthor()[authorSlug1].reduce(
|
||||
(acc, article) => acc + article.stat?.rating,
|
||||
0
|
||||
)
|
||||
const author2Rating = articlesByAuthor[authorSlug2].reduce(
|
||||
const author2Rating = articlesByAuthor()[authorSlug2].reduce(
|
||||
(acc, article) => acc + article.stat?.rating,
|
||||
0
|
||||
)
|
||||
|
@ -29,9 +26,9 @@ export const useTopAuthorsStore = () => {
|
|||
return author1Rating > author2Rating ? 1 : -1
|
||||
})
|
||||
.slice(0, TOP_AUTHORS_COUNT)
|
||||
.map((authorSlug) => authorEntities[authorSlug])
|
||||
.map((authorSlug) => authorEntities()[authorSlug])
|
||||
.filter(Boolean)
|
||||
})
|
||||
|
||||
return { getTopAuthors }
|
||||
return { topAuthors }
|
||||
}
|
||||
|
|
|
@ -1,66 +1,51 @@
|
|||
import { createMemo, createSignal } from 'solid-js'
|
||||
import { apiClient } from '../../utils/apiClient'
|
||||
import { map, MapStore, ReadableAtom, atom, computed } from 'nanostores'
|
||||
import type { Topic } from '../../graphql/types.gen'
|
||||
import { useStore } from '@nanostores/solid'
|
||||
import { byCreated, byTopicStatDesc } from '../../utils/sortby'
|
||||
import { getLogger } from '../../utils/logger'
|
||||
import { createSignal } from 'solid-js'
|
||||
|
||||
const log = getLogger('topics store')
|
||||
|
||||
export type TopicsSortBy = 'created' | 'title' | 'authors' | 'shouts'
|
||||
|
||||
const sortAllByStore = atom<TopicsSortBy>('shouts')
|
||||
const [sortAllBy, setSortAllBy] = createSignal<TopicsSortBy>('shouts')
|
||||
|
||||
let topicEntitiesStore: MapStore<Record<string, Topic>>
|
||||
let sortedTopicsStore: ReadableAtom<Topic[]>
|
||||
let topTopicsStore: ReadableAtom<Topic[]>
|
||||
export { setSortAllBy }
|
||||
|
||||
const [getRandomTopics, setRandomTopics] = createSignal<Topic[]>()
|
||||
let topicsByAuthorStore: MapStore<Record<string, Topic[]>>
|
||||
const [topicEntities, setTopicEntities] = createSignal<{ [topicSlug: string]: Topic }>({})
|
||||
const [randomTopics, setRandomTopics] = createSignal<Topic[]>([])
|
||||
const [topicsByAuthor, setTopicByAuthor] = createSignal<{ [authorSlug: string]: Topic[] }>({})
|
||||
|
||||
const initStore = (initial?: { [topicSlug: string]: Topic }) => {
|
||||
if (topicEntitiesStore) {
|
||||
return
|
||||
}
|
||||
const sortedTopics = createMemo(() => {
|
||||
const topics = Object.values(topicEntities)
|
||||
const sortAllByValue = sortAllBy()
|
||||
|
||||
topicEntitiesStore = map<Record<string, Topic>>(initial)
|
||||
|
||||
sortedTopicsStore = computed([topicEntitiesStore, sortAllByStore], (topicEntities, sortBy) => {
|
||||
const topics = Object.values(topicEntities)
|
||||
switch (sortBy) {
|
||||
case 'created': {
|
||||
// log.debug('sorted by created')
|
||||
topics.sort(byCreated)
|
||||
break
|
||||
}
|
||||
case 'shouts':
|
||||
case 'authors':
|
||||
// log.debug(`sorted by ${sortBy}`)
|
||||
topics.sort(byTopicStatDesc(sortBy))
|
||||
break
|
||||
case 'title':
|
||||
// log.debug('sorted by title')
|
||||
topics.sort((a, b) => a.title.localeCompare(b.title))
|
||||
break
|
||||
default:
|
||||
log.error(`Unknown sort: ${sortBy}`)
|
||||
switch (sortAllByValue) {
|
||||
case 'created': {
|
||||
// log.debug('sorted by created')
|
||||
topics.sort(byCreated)
|
||||
break
|
||||
}
|
||||
return topics
|
||||
})
|
||||
|
||||
topTopicsStore = computed(topicEntitiesStore, (topicEntities) => {
|
||||
const topics = Object.values(topicEntities)
|
||||
topics.sort(byTopicStatDesc('shouts'))
|
||||
return topics
|
||||
})
|
||||
}
|
||||
|
||||
export const setSortAllTopicsBy = (sortBy: TopicsSortBy) => {
|
||||
if (sortAllByStore.get() !== sortBy) {
|
||||
sortAllByStore.set(sortBy)
|
||||
case 'shouts':
|
||||
case 'authors':
|
||||
// log.debug(`sorted by ${sortBy}`)
|
||||
topics.sort(byTopicStatDesc(sortAllByValue))
|
||||
break
|
||||
case 'title':
|
||||
// log.debug('sorted by title')
|
||||
topics.sort((a, b) => a.title.localeCompare(b.title))
|
||||
break
|
||||
default:
|
||||
log.error(`Unknown sort: ${sortAllByValue}`)
|
||||
}
|
||||
}
|
||||
return topics
|
||||
})
|
||||
|
||||
const topTopics = createMemo(() => {
|
||||
const topics = Object.values(topicEntities())
|
||||
topics.sort(byTopicStatDesc('shouts'))
|
||||
return topics
|
||||
})
|
||||
|
||||
const addTopics = (...args: Topic[][]) => {
|
||||
const allTopics = args.flatMap((topics) => topics || [])
|
||||
|
@ -70,24 +55,20 @@ const addTopics = (...args: Topic[][]) => {
|
|||
return acc
|
||||
}, {} as Record<string, Topic>)
|
||||
|
||||
if (!topicEntitiesStore) {
|
||||
initStore(newTopicEntities)
|
||||
} else {
|
||||
topicEntitiesStore.set({
|
||||
...topicEntitiesStore.get(),
|
||||
setTopicEntities((prevTopicEntities) => {
|
||||
return {
|
||||
...prevTopicEntities,
|
||||
...newTopicEntities
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const addTopicsByAuthor = (topicsByAuthors: { [authorSlug: string]: Topic[] }) => {
|
||||
const allTopics = Object.values(topicsByAuthors).flat()
|
||||
export const addTopicsByAuthor = (newTopicsByAuthors: { [authorSlug: string]: Topic[] }) => {
|
||||
const allTopics = Object.values(newTopicsByAuthors).flat()
|
||||
addTopics(allTopics)
|
||||
|
||||
if (!topicsByAuthorStore) {
|
||||
topicsByAuthorStore = map<Record<string, Topic[]>>(topicsByAuthors)
|
||||
} else {
|
||||
const newState = Object.entries(topicsByAuthors).reduce((acc, [authorSlug, topics]) => {
|
||||
setTopicByAuthor((prevTopicsByAuthor) => {
|
||||
return Object.entries(newTopicsByAuthors).reduce((acc, [authorSlug, topics]) => {
|
||||
if (!acc[authorSlug]) {
|
||||
acc[authorSlug] = []
|
||||
}
|
||||
|
@ -99,10 +80,8 @@ export const addTopicsByAuthor = (topicsByAuthors: { [authorSlug: string]: Topic
|
|||
})
|
||||
|
||||
return acc
|
||||
}, topicsByAuthorStore.get())
|
||||
|
||||
topicsByAuthorStore.set(newState)
|
||||
}
|
||||
}, prevTopicsByAuthor)
|
||||
})
|
||||
}
|
||||
|
||||
export const loadAllTopics = async (): Promise<void> => {
|
||||
|
@ -117,24 +96,15 @@ type InitialState = {
|
|||
}
|
||||
|
||||
export const useTopicsStore = (initialState: InitialState = {}) => {
|
||||
const topics = [...(initialState.topics || [])]
|
||||
const randomTopics = [...(initialState.randomTopics || [])]
|
||||
|
||||
if (initialState.sortBy) {
|
||||
sortAllByStore.set(initialState.sortBy)
|
||||
setSortAllBy(initialState.sortBy)
|
||||
}
|
||||
|
||||
addTopics(topics, randomTopics)
|
||||
addTopics(initialState.topics, initialState.randomTopics)
|
||||
|
||||
if (randomTopics) {
|
||||
setRandomTopics(randomTopics)
|
||||
if (initialState.randomTopics) {
|
||||
setRandomTopics(initialState.randomTopics)
|
||||
}
|
||||
|
||||
const getTopicEntities = useStore(topicEntitiesStore)
|
||||
|
||||
const getSortedTopics = useStore(sortedTopicsStore)
|
||||
|
||||
const getTopTopics = useStore(topTopicsStore)
|
||||
|
||||
return { getTopicEntities, getSortedTopics, getRandomTopics, getTopTopics }
|
||||
return { topicEntities, sortedTopics, randomTopics, topTopics, topicsByAuthor }
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user