webapp/src/stores/zine/articles.ts

308 lines
7.2 KiB
TypeScript
Raw Normal View History

2022-11-09 17:57:35 +00:00
import type { Author, Shout, ShoutInput, Topic } from '../../graphql/types.gen'
2022-09-09 11:53:35 +00:00
import { apiClient } from '../../utils/apiClient'
2022-09-13 09:59:04 +00:00
import { addAuthorsByTopic } from './authors'
import { addTopicsByAuthor } from './topics'
import { byStat } from '../../utils/sortby'
2022-09-30 14:22:33 +00:00
import { createSignal } from 'solid-js'
2022-09-29 17:37:21 +00:00
import { createLazyMemo } from '@solid-primitives/memo'
2022-09-22 09:37:49 +00:00
2022-09-28 20:16:44 +00:00
const [sortedArticles, setSortedArticles] = createSignal<Shout[]>([])
const [articleEntities, setArticleEntities] = createSignal<{ [articleSlug: string]: Shout }>({})
2022-09-09 11:53:35 +00:00
2022-09-28 20:16:44 +00:00
const [topArticles, setTopArticles] = createSignal<Shout[]>([])
const [topMonthArticles, setTopMonthArticles] = createSignal<Shout[]>([])
2022-09-22 09:37:49 +00:00
2022-09-29 17:37:21 +00:00
const articlesByAuthor = createLazyMemo(() => {
2022-09-28 20:16:44 +00:00
return Object.values(articleEntities()).reduce((acc, article) => {
article.authors.forEach((author) => {
if (!acc[author.slug]) {
acc[author.slug] = []
}
acc[author.slug].push(article)
})
2022-09-13 09:59:04 +00:00
2022-09-28 20:16:44 +00:00
return acc
}, {} as { [authorSlug: string]: Shout[] })
})
2022-09-13 09:59:04 +00:00
2022-09-29 17:37:21 +00:00
const articlesByTopic = createLazyMemo(() => {
2022-09-28 20:16:44 +00:00
return Object.values(articleEntities()).reduce((acc, article) => {
article.topics.forEach((topic) => {
if (!acc[topic.slug]) {
acc[topic.slug] = []
}
acc[topic.slug].push(article)
})
2022-09-13 09:59:04 +00:00
2022-09-28 20:16:44 +00:00
return acc
}, {} as { [authorSlug: string]: Shout[] })
})
2022-09-13 09:59:04 +00:00
2022-09-29 17:37:21 +00:00
const articlesByLayout = createLazyMemo(() => {
2022-09-28 20:16:44 +00:00
return Object.values(articleEntities()).reduce((acc, article) => {
if (!acc[article.layout]) {
acc[article.layout] = []
}
2022-09-22 09:37:49 +00:00
2022-09-28 20:16:44 +00:00
acc[article.layout].push(article)
2022-09-22 09:37:49 +00:00
2022-09-28 20:16:44 +00:00
return acc
}, {} as { [layout: string]: Shout[] })
})
2022-09-22 09:37:49 +00:00
2022-09-29 17:37:21 +00:00
const topViewedArticles = createLazyMemo(() => {
2022-09-28 20:16:44 +00:00
const result = Object.values(articleEntities())
result.sort(byStat('viewed'))
return result
})
2022-09-13 09:59:04 +00:00
2022-09-29 17:37:21 +00:00
const topCommentedArticles = createLazyMemo(() => {
2022-09-28 20:16:44 +00:00
const result = Object.values(articleEntities())
result.sort(byStat('commented'))
return result
})
2022-09-09 11:53:35 +00:00
// eslint-disable-next-line sonarjs/cognitive-complexity
2022-09-13 09:59:04 +00:00
const addArticles = (...args: Shout[][]) => {
const allArticles = args.flatMap((articles) => articles || [])
const newArticleEntities = allArticles.reduce((acc, article) => {
2022-09-09 11:53:35 +00:00
acc[article.slug] = article
return acc
2022-09-22 09:37:49 +00:00
}, {} as { [articleSLug: string]: Shout })
2022-09-09 11:53:35 +00:00
2022-09-28 20:16:44 +00:00
setArticleEntities((prevArticleEntities) => {
return {
...prevArticleEntities,
2022-09-09 11:53:35 +00:00
...newArticleEntities
2022-09-28 20:16:44 +00:00
}
})
2022-09-09 11:53:35 +00:00
2022-09-13 09:59:04 +00:00
const authorsByTopic = allArticles.reduce((acc, article) => {
const { authors, topics } = article
topics.forEach((topic) => {
if (!acc[topic.slug]) {
acc[topic.slug] = []
}
2022-09-09 11:53:35 +00:00
2022-09-13 09:59:04 +00:00
authors.forEach((author) => {
if (!acc[topic.slug].some((a) => a.slug === author.slug)) {
acc[topic.slug].push(author)
}
2022-09-09 11:53:35 +00:00
})
2022-09-13 09:59:04 +00:00
})
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)
}
2022-09-09 11:53:35 +00:00
})
})
2022-09-13 09:59:04 +00:00
return acc
}, {} as { [authorSlug: string]: Topic[] })
addTopicsByAuthor(topicsByAuthor)
}
const addSortedArticles = (articles: Shout[]) => {
2022-09-22 09:37:49 +00:00
setSortedArticles((prevSortedArticles) => [...prevSortedArticles, ...articles])
2022-09-09 11:53:35 +00:00
}
export const loadFeed = async ({
limit,
offset
}: {
limit: number
offset?: number
}): Promise<{ hasMore: boolean }> => {
// TODO: load actual feed
return await loadRecentArticles({ limit, offset })
}
2022-09-14 11:27:10 +00:00
export const loadRecentArticles = async ({
limit,
offset
}: {
limit: number
2022-09-14 11:27:10 +00:00
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)
}
2022-09-09 12:18:09 +00:00
addArticles(newArticles)
2022-09-13 09:59:04 +00:00
addSortedArticles(newArticles)
return { hasMore }
2022-09-09 12:18:09 +00:00
}
2022-09-14 11:27:10 +00:00
export const loadPublishedArticles = async ({
limit,
offset = 0
2022-09-14 11:27:10 +00:00
}: {
limit: number
2022-09-14 11:27:10 +00:00
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)
}
2022-09-09 11:53:35 +00:00
addArticles(newArticles)
2022-09-13 09:59:04 +00:00
addSortedArticles(newArticles)
return { hasMore }
2022-09-13 09:59:04 +00:00
}
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 }
2022-10-05 15:11:14 +00:00
}
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 }
2022-10-05 15:11:14 +00:00
}
export const resetSortedArticles = () => {
setSortedArticles([])
}
2022-09-22 09:37:49 +00:00
export const loadTopMonthArticles = async (): Promise<void> => {
const articles = await apiClient.getTopMonthArticles()
addArticles(articles)
2022-09-28 20:16:44 +00:00
setTopMonthArticles(articles)
2022-09-22 09:37:49 +00:00
}
export const loadTopArticles = async (): Promise<void> => {
const articles = await apiClient.getTopArticles()
addArticles(articles)
2022-09-28 20:16:44 +00:00
setTopArticles(articles)
2022-09-22 09:37:49 +00:00
}
2022-09-14 11:27:10 +00:00
export const loadSearchResults = async ({
query,
limit,
offset
}: {
query: string
limit?: number
offset?: number
}): Promise<void> => {
const newArticles = await apiClient.getSearchResults({ query, limit, offset })
2022-09-13 09:59:04 +00:00
addArticles(newArticles)
addSortedArticles(newArticles)
2022-09-09 11:53:35 +00:00
}
2022-09-14 11:28:43 +00:00
export const incrementView = async ({ articleSlug }: { articleSlug: string }): Promise<void> => {
await apiClient.incrementView({ articleSlug })
}
2022-09-22 09:37:49 +00:00
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])
2022-09-14 11:28:43 +00:00
}
2022-11-09 17:57:35 +00:00
export const createArticle = async ({ article }: { article: ShoutInput }) => {
try {
await apiClient.createArticle({ article })
} catch (error) {
console.error(error)
}
}
2022-09-09 11:53:35 +00:00
type InitialState = {
sortedArticles?: Shout[]
2022-09-13 09:59:04 +00:00
topRatedArticles?: Shout[]
topRatedMonthArticles?: Shout[]
2022-09-09 11:53:35 +00:00
}
2022-09-23 07:38:48 +00:00
export const useArticlesStore = (initialState: InitialState = {}) => {
2022-09-28 20:16:44 +00:00
addArticles([...(initialState.sortedArticles || [])])
2022-09-09 12:18:09 +00:00
2022-09-28 20:16:44 +00:00
if (initialState.sortedArticles) {
setSortedArticles([...initialState.sortedArticles])
2022-09-13 09:59:04 +00:00
}
2022-09-09 12:18:09 +00:00
2022-09-13 09:59:04 +00:00
return {
2022-09-28 20:16:44 +00:00
articleEntities,
sortedArticles,
articlesByTopic,
articlesByAuthor,
topArticles,
topMonthArticles,
topViewedArticles,
topCommentedArticles,
articlesByLayout
2022-09-13 09:59:04 +00:00
}
2022-09-09 12:18:09 +00:00
}