wip-layouts
This commit is contained in:
parent
fab46a6ef2
commit
30fe44979e
0
src/components/Pages/ArtworksPage.tsx
Normal file
0
src/components/Pages/ArtworksPage.tsx
Normal file
0
src/components/Pages/AudioPage.tsx
Normal file
0
src/components/Pages/AudioPage.tsx
Normal file
48
src/components/Pages/ReadingPage.tsx
Normal file
48
src/components/Pages/ReadingPage.tsx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { MainLayout } from '../Layouts/MainLayout'
|
||||||
|
import { PRERENDERED_ARTICLES_COUNT, TopicView } from '../Views/Topic'
|
||||||
|
import type { PageProps } from '../types'
|
||||||
|
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
||||||
|
import { resetSortedArticles } from '../../stores/zine/articles'
|
||||||
|
import { useRouter } from '../../stores/router'
|
||||||
|
import { loadLayoutShouts, loadLayout } from '../../stores/zine/layouts'
|
||||||
|
import { Loading } from '../Loading'
|
||||||
|
|
||||||
|
export const ReadingPage = (props: PageProps) => {
|
||||||
|
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.authorArticles) && Boolean(props.author))
|
||||||
|
|
||||||
|
const slug = createMemo(() => {
|
||||||
|
const { page: getPage } = useRouter()
|
||||||
|
|
||||||
|
const page = getPage()
|
||||||
|
|
||||||
|
if (page.route !== 'topic') {
|
||||||
|
throw new Error('ts guard')
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.params.slug
|
||||||
|
})
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
if (isLoaded()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadLayoutShouts({ topicSlug: slug(), limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
|
||||||
|
await loadLayout({ slug: slug() })
|
||||||
|
|
||||||
|
setIsLoaded(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
onCleanup(() => resetSortedArticles())
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MainLayout>
|
||||||
|
<Show when={isLoaded()} fallback={<Loading />}>
|
||||||
|
<TopicView topic={props.topic} topicArticles={props.topicArticles} topicSlug={slug()} />
|
||||||
|
</Show>
|
||||||
|
</MainLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for lazy loading
|
||||||
|
export default TopicPage
|
0
src/components/Pages/VIdeoPage.tsx
Normal file
0
src/components/Pages/VIdeoPage.tsx
Normal file
|
@ -31,26 +31,13 @@ import { ThanksPage } from './Pages/about/ThanksPage'
|
||||||
import { CreatePage } from './Pages/CreatePage'
|
import { CreatePage } from './Pages/CreatePage'
|
||||||
import { ConnectPage } from './Pages/ConnectPage'
|
import { ConnectPage } from './Pages/ConnectPage'
|
||||||
import { renewSession } from '../stores/auth'
|
import { renewSession } from '../stores/auth'
|
||||||
|
import { AudioPage } from './Pages/AudioPage'
|
||||||
|
import { VideoPage } from './Pages/VideoPage'
|
||||||
|
import { ReadingPage } from './Pages/ReadingPage'
|
||||||
|
import { ArtworksPage } from './Pages/ArtworksPage'
|
||||||
|
|
||||||
// TODO: lazy load
|
// TODO: lazy load
|
||||||
// const HomePage = lazy(() => import('./Pages/HomePage'))
|
// const SomePage = lazy(() => import('./Pages/SomePage'))
|
||||||
// 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 CreatePage = lazy(() => import('./Pages/about/CreatePage'))
|
|
||||||
|
|
||||||
const log = getLogger('root')
|
const log = getLogger('root')
|
||||||
|
|
||||||
|
@ -60,6 +47,10 @@ type RootSearchParams = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const pagesMap: Record<keyof Routes, Component<PageProps>> = {
|
const pagesMap: Record<keyof Routes, Component<PageProps>> = {
|
||||||
|
audio: AudioPage,
|
||||||
|
video: VideoPage,
|
||||||
|
literature: ReadingPage,
|
||||||
|
artworks: ArtworksPage,
|
||||||
connect: ConnectPage,
|
connect: ConnectPage,
|
||||||
create: CreatePage,
|
create: CreatePage,
|
||||||
home: HomePage,
|
home: HomePage,
|
||||||
|
|
40
src/graphql/query/articles-for-layout.ts
Normal file
40
src/graphql/query/articles-for-layout.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { gql } from '@urql/core'
|
||||||
|
|
||||||
|
export default gql`
|
||||||
|
query ShoutsForLayoutQuery($amount: Int, $offset: Int, $layout: String) {
|
||||||
|
shoutsByLayout(amount: $amount, offset: $offset, layout: $layout) {
|
||||||
|
_id: slug
|
||||||
|
title
|
||||||
|
subtitle
|
||||||
|
layout
|
||||||
|
slug
|
||||||
|
cover
|
||||||
|
# community
|
||||||
|
mainTopic
|
||||||
|
topics {
|
||||||
|
title
|
||||||
|
body
|
||||||
|
slug
|
||||||
|
stat {
|
||||||
|
_id: shouts
|
||||||
|
shouts
|
||||||
|
authors
|
||||||
|
followers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authors {
|
||||||
|
_id: slug
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
userpic
|
||||||
|
}
|
||||||
|
createdAt
|
||||||
|
publishedAt
|
||||||
|
stat {
|
||||||
|
_id: viewed
|
||||||
|
viewed
|
||||||
|
reacted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
23
src/pages/layout/[...layout].astro
Normal file
23
src/pages/layout/[...layout].astro
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
import { Root } from '../../components/Root'
|
||||||
|
import Zine from '../../layouts/zine.astro'
|
||||||
|
import { apiClient } from '../../utils/apiClient'
|
||||||
|
import { initRouter } from '../../stores/router'
|
||||||
|
|
||||||
|
const layout = Astro.params.layout?.toString()
|
||||||
|
if (layout.endsWith('.map')) {
|
||||||
|
return Astro.redirect('/404')
|
||||||
|
}
|
||||||
|
const LAYOUTS = ['literature', 'audio', 'video', 'artworks']
|
||||||
|
if (!LAYOUTS.includes(layout)) {
|
||||||
|
return Astro.redirect('/404')
|
||||||
|
}
|
||||||
|
const shouts = await apiClient.getRecentByLayout(layout)
|
||||||
|
const { pathname, search } = Astro.url
|
||||||
|
initRouter(pathname, search)
|
||||||
|
---
|
||||||
|
|
||||||
|
<Zine>
|
||||||
|
<Root shouts={shouts} layout={layout} client:load />
|
||||||
|
</Zine>
|
||||||
|
|
196
src/stores/zine/layouts.ts
Normal file
196
src/stores/zine/layouts.ts
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
import type { Author, Shout, ShoutInput, 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 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 loadLayoutShouts = async ({
|
||||||
|
layout,
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
}: {
|
||||||
|
layout: string
|
||||||
|
limit: number
|
||||||
|
offset?: number
|
||||||
|
}): Promise<{ hasMore: boolean }> => {
|
||||||
|
const newArticles = await apiClient.getLayoutShouts({ layout, 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])
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createArticle = async ({ article }: { article: ShoutInput }) => {
|
||||||
|
try {
|
||||||
|
await apiClient.createArticle({ article })
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
topArticles,
|
||||||
|
topMonthArticles,
|
||||||
|
topViewedArticles,
|
||||||
|
topCommentedArticles,
|
||||||
|
articlesByLayout
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import authorsBySlugs from '../graphql/query/authors-by-slugs'
|
||||||
import incrementView from '../graphql/mutation/increment-view'
|
import incrementView from '../graphql/mutation/increment-view'
|
||||||
import createArticle from '../graphql/mutation/article-create'
|
import createArticle from '../graphql/mutation/article-create'
|
||||||
import myChats from '../graphql/query/my-chats'
|
import myChats from '../graphql/query/my-chats'
|
||||||
|
import getLayout from '../graphql/query/articles-for-layout'
|
||||||
|
|
||||||
const FEED_SIZE = 50
|
const FEED_SIZE = 50
|
||||||
|
|
||||||
|
@ -334,5 +335,8 @@ export const apiClient = {
|
||||||
getInboxes: async (payload = {}) => {
|
getInboxes: async (payload = {}) => {
|
||||||
const resp = await privateGraphQLClient.query(myChats, payload).toPromise()
|
const resp = await privateGraphQLClient.query(myChats, payload).toPromise()
|
||||||
return resp.data.myChats
|
return resp.data.myChats
|
||||||
|
},
|
||||||
|
getLayoutShouts: async (layout = 'article', amount = 50, offset = 0) => {
|
||||||
|
const resp = await publicGraphQLClient.query(getLayout, { amount, offset, layout })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export const isDev = import.meta.env.MODE === 'development'
|
export const isDev = import.meta.env.MODE === 'development'
|
||||||
|
|
||||||
export const apiBaseUrl = 'https://newapi.discours.io'
|
// export const apiBaseUrl = 'https://newapi.discours.io'
|
||||||
|
export const apiBaseUrl = 'https://testapi.discours.io'
|
||||||
// export const apiBaseUrl = 'http://localhost:8080'
|
// export const apiBaseUrl = 'http://localhost:8080'
|
||||||
|
|
Loading…
Reference in New Issue
Block a user