webapp/src/stores/zine/articles.ts
Igor Lobanov 5bcef1d1e2 Merge remote-tracking branch 'origin/dev' into editor
# Conflicts:
#	src/components/Feed/Row3.tsx
#	src/components/Nav/AuthModal/EmailConfirm.tsx
#	src/components/Nav/AuthModal/ForgotPasswordForm.tsx
#	src/components/Nav/AuthModal/LoginForm.tsx
#	src/components/Nav/AuthModal/RegisterForm.tsx
#	src/components/Nav/AuthModal/index.tsx
#	src/components/Nav/Header.tsx
#	src/components/Nav/Modal.tsx
#	src/components/Nav/Popup.tsx
#	src/components/Nav/Private.tsx
#	src/components/Root.tsx
#	src/components/Topic/Card.tsx
#	src/components/Views/AllAuthors.tsx
#	src/components/Views/Author.tsx
#	src/components/Views/Home.tsx
#	src/components/Views/Topic.tsx
#	src/graphql/mutation/auth-confirm-email.ts
#	src/locales/ru.json
#	src/pages/welcome.astro
#	src/stores/auth.ts
#	src/stores/ui.ts
#	src/stores/zine/authors.ts
#	src/utils/apiClient.ts
#	src/utils/config.ts
2022-10-31 14:06:32 +01:00

300 lines
7.0 KiB
TypeScript

import type { Author, Shout, Topic } from '../../graphql/types.gen'
import { apiClient } from '../../utils/apiClient'
import { addAuthorsByTopic } from './authors'
import { addTopicsByAuthor } from './topics'
import { byStat } from '../../utils/sortby'
import { createSignal } from 'solid-js'
import { createLazyMemo } from '@solid-primitives/memo'
const [sortedArticles, setSortedArticles] = createSignal<Shout[]>([])
const [articleEntities, setArticleEntities] = createSignal<{ [articleSlug: string]: Shout }>({})
const [topArticles, setTopArticles] = createSignal<Shout[]>([])
const [topMonthArticles, setTopMonthArticles] = createSignal<Shout[]>([])
const articlesByAuthor = createLazyMemo(() => {
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[] })
})
const articlesByTopic = createLazyMemo(() => {
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[] })
})
const articlesByLayout = createLazyMemo(() => {
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 = createLazyMemo(() => {
const result = Object.values(articleEntities())
result.sort(byStat('viewed'))
return result
})
const topCommentedArticles = createLazyMemo(() => {
const result = Object.values(articleEntities())
result.sort(byStat('commented'))
return result
})
// eslint-disable-next-line sonarjs/cognitive-complexity
const addArticles = (...args: Shout[][]) => {
const allArticles = args.flatMap((articles) => articles || [])
const newArticleEntities = allArticles.reduce((acc, article) => {
acc[article.slug] = article
return acc
}, {} as { [articleSLug: string]: Shout })
setArticleEntities((prevArticleEntities) => {
return {
...prevArticleEntities,
...newArticleEntities
}
})
const authorsByTopic = allArticles.reduce((acc, article) => {
const { authors, topics } = article
topics.forEach((topic) => {
if (!acc[topic.slug]) {
acc[topic.slug] = []
}
authors.forEach((author) => {
if (!acc[topic.slug].some((a) => a.slug === author.slug)) {
acc[topic.slug].push(author)
}
})
})
return acc
}, {} as { [topicSlug: string]: Author[] })
addAuthorsByTopic(authorsByTopic)
const topicsByAuthor = allArticles.reduce((acc, article) => {
const { authors, topics } = article
authors.forEach((author) => {
if (!acc[author.slug]) {
acc[author.slug] = []
}
topics.forEach((topic) => {
if (!acc[author.slug].some((t) => t.slug === topic.slug)) {
acc[author.slug].push(topic)
}
})
})
return acc
}, {} as { [authorSlug: string]: Topic[] })
addTopicsByAuthor(topicsByAuthor)
}
const addSortedArticles = (articles: Shout[]) => {
setSortedArticles((prevSortedArticles) => [...prevSortedArticles, ...articles])
}
export const loadFeed = async ({
limit,
offset
}: {
limit: number
offset?: number
}): Promise<{ hasMore: boolean }> => {
// TODO: load actual feed
return await loadRecentArticles({ limit, offset })
}
export const loadRecentArticles = async ({
limit,
offset
}: {
limit: number
offset?: number
}): Promise<{ hasMore: boolean }> => {
const newArticles = await apiClient.getRecentArticles({ limit: limit + 1, offset })
const hasMore = newArticles.length === limit + 1
if (hasMore) {
newArticles.splice(-1)
}
addArticles(newArticles)
addSortedArticles(newArticles)
return { hasMore }
}
export const loadPublishedArticles = async ({
limit,
offset = 0
}: {
limit: number
offset?: number
}): Promise<{ hasMore: boolean }> => {
const newArticles = await apiClient.getPublishedArticles({ limit: limit + 1, offset })
const hasMore = newArticles.length === limit + 1
if (hasMore) {
newArticles.splice(-1)
}
addArticles(newArticles)
addSortedArticles(newArticles)
return { hasMore }
}
export const loadAuthorArticles = async ({
authorSlug,
limit,
offset = 0
}: {
authorSlug: string
limit: number
offset?: number
}): Promise<{ hasMore: boolean }> => {
const newArticles = await apiClient.getArticlesForAuthors({
authorSlugs: [authorSlug],
limit: limit + 1,
offset
})
const hasMore = newArticles.length === limit + 1
if (hasMore) {
newArticles.splice(-1)
}
addArticles(newArticles)
addSortedArticles(newArticles)
return { hasMore }
}
export const loadTopicArticles = async ({
topicSlug,
limit,
offset
}: {
topicSlug: string
limit: number
offset: number
}): Promise<{ hasMore: boolean }> => {
const newArticles = await apiClient.getArticlesForTopics({
topicSlugs: [topicSlug],
limit: limit + 1,
offset
})
const hasMore = newArticles.length === limit + 1
if (hasMore) {
newArticles.splice(-1)
}
addArticles(newArticles)
addSortedArticles(newArticles)
return { hasMore }
}
export const resetSortedArticles = () => {
setSortedArticles([])
}
export const loadTopMonthArticles = async (): Promise<void> => {
const articles = await apiClient.getTopMonthArticles()
addArticles(articles)
setTopMonthArticles(articles)
}
export const loadTopArticles = async (): Promise<void> => {
const articles = await apiClient.getTopArticles()
addArticles(articles)
setTopArticles(articles)
}
export const loadSearchResults = async ({
query,
limit,
offset
}: {
query: string
limit?: number
offset?: number
}): Promise<void> => {
const newArticles = await apiClient.getSearchResults({ query, limit, offset })
addArticles(newArticles)
addSortedArticles(newArticles)
}
export const incrementView = async ({ articleSlug }: { articleSlug: string }): Promise<void> => {
await apiClient.incrementView({ articleSlug })
}
export const loadArticle = async ({ slug }: { slug: string }): Promise<void> => {
const article = await apiClient.getArticle({ slug })
if (!article) {
throw new Error(`Can't load article, slug: "${slug}"`)
}
addArticles([article])
}
type InitialState = {
sortedArticles?: Shout[]
topRatedArticles?: Shout[]
topRatedMonthArticles?: Shout[]
}
export const useArticlesStore = (initialState: InitialState = {}) => {
addArticles([...(initialState.sortedArticles || [])])
if (initialState.sortedArticles) {
setSortedArticles([...initialState.sortedArticles])
}
return {
articleEntities,
sortedArticles,
articlesByTopic,
articlesByAuthor,
topArticles,
topMonthArticles,
topViewedArticles,
topCommentedArticles,
articlesByLayout
}
}