This commit is contained in:
tonyrewin 2022-11-13 15:14:04 +03:00
parent dd1840d41f
commit 42489bb297
14 changed files with 274 additions and 236 deletions

View File

@ -1,7 +1,7 @@
import { defineConfig, AstroUserConfig } from 'astro/config' import { defineConfig, AstroUserConfig } from 'astro/config'
import vercel from '@astrojs/vercel/serverless' import vercel from '@astrojs/vercel/serverless'
import solidJs from '@astrojs/solid-js' import solidJs from '@astrojs/solid-js'
import type { CSSOptions } from 'vite' import type { CSSOptions, PluginOption } from 'vite'
import defaultGenerateScopedName from 'postcss-modules/build/generateScopedName' import defaultGenerateScopedName from 'postcss-modules/build/generateScopedName'
import { isDev } from './src/utils/config' import { isDev } from './src/utils/config'
import { visualizer } from 'rollup-plugin-visualizer' import { visualizer } from 'rollup-plugin-visualizer'
@ -37,6 +37,7 @@ const astroConfig: AstroUserConfig = {
adapter: vercel(), adapter: vercel(),
vite: { vite: {
build: { build: {
chunkSizeWarningLimit: 777,
rollupOptions: { rollupOptions: {
plugins: [visualizer()], plugins: [visualizer()],
output: { output: {

View File

@ -1,5 +1,5 @@
overwrite: true overwrite: true
schema: 'https://newapi.discours.io/graphql' schema: 'https://testapi.discours.io/graphql'
generates: generates:
src/graphql/introspec.gen.ts: src/graphql/introspec.gen.ts:
plugins: plugins:

View File

@ -110,7 +110,7 @@
"prosemirror-schema-list": "^1.2.2", "prosemirror-schema-list": "^1.2.2",
"prosemirror-state": "^1.4.2", "prosemirror-state": "^1.4.2",
"prosemirror-view": "^1.29.0", "prosemirror-view": "^1.29.0",
"rollup": "~2.79.1", "rollup": "^2.0.0",
"rollup-plugin-visualizer": "^5.8.3", "rollup-plugin-visualizer": "^5.8.3",
"sass": "^1.56.1", "sass": "^1.56.1",
"solid-devtools": "^0.22.0", "solid-devtools": "^0.22.0",
@ -120,7 +120,7 @@
"solid-social": "^0.9.0", "solid-social": "^0.9.0",
"solid-utils": "^0.8.1", "solid-utils": "^0.8.1",
"sort-package-json": "^2.1.0", "sort-package-json": "^2.1.0",
"stylelint": "^14.14.1", "stylelint": "^14.15.0",
"stylelint-config-css-modules": "^4.1.0", "stylelint-config-css-modules": "^4.1.0",
"stylelint-config-prettier-scss": "^0.0.1", "stylelint-config-prettier-scss": "^0.0.1",
"stylelint-config-standard-scss": "^6.1.0", "stylelint-config-standard-scss": "^6.1.0",

View File

@ -79,7 +79,7 @@ specifiers:
prosemirror-schema-list: ^1.2.2 prosemirror-schema-list: ^1.2.2
prosemirror-state: ^1.4.2 prosemirror-state: ^1.4.2
prosemirror-view: ^1.29.0 prosemirror-view: ^1.29.0
rollup: ~2.79.1 rollup: ^2.0.0
rollup-plugin-visualizer: ^5.8.3 rollup-plugin-visualizer: ^5.8.3
sass: ^1.56.1 sass: ^1.56.1
solid-devtools: ^0.22.0 solid-devtools: ^0.22.0
@ -89,7 +89,7 @@ specifiers:
solid-social: ^0.9.0 solid-social: ^0.9.0
solid-utils: ^0.8.1 solid-utils: ^0.8.1
sort-package-json: ^2.1.0 sort-package-json: ^2.1.0
stylelint: ^14.14.1 stylelint: ^14.15.0
stylelint-config-css-modules: ^4.1.0 stylelint-config-css-modules: ^4.1.0
stylelint-config-prettier-scss: ^0.0.1 stylelint-config-prettier-scss: ^0.0.1
stylelint-config-standard-scss: ^6.1.0 stylelint-config-standard-scss: ^6.1.0
@ -199,12 +199,12 @@ devDependencies:
solid-social: 0.9.0_solid-js@1.6.2 solid-social: 0.9.0_solid-js@1.6.2
solid-utils: 0.8.1_solid-js@1.6.2 solid-utils: 0.8.1_solid-js@1.6.2
sort-package-json: 2.1.0 sort-package-json: 2.1.0
stylelint: 14.14.1 stylelint: 14.15.0
stylelint-config-css-modules: 4.1.0_stylelint@14.14.1 stylelint-config-css-modules: 4.1.0_stylelint@14.15.0
stylelint-config-prettier-scss: 0.0.1_stylelint@14.14.1 stylelint-config-prettier-scss: 0.0.1_stylelint@14.15.0
stylelint-config-standard-scss: 6.1.0_ave2i6l4ingtbwj4aquhd5witq stylelint-config-standard-scss: 6.1.0_a37symlv4urgexnspmy4gyeh7i
stylelint-order: 5.0.0_stylelint@14.14.1 stylelint-order: 5.0.0_stylelint@14.15.0
stylelint-scss: 4.3.0_stylelint@14.14.1 stylelint-scss: 4.3.0_stylelint@14.15.0
swiper: 8.4.4 swiper: 8.4.4
ts-node: 10.9.1_cbe7ovvae6zqfnmtgctpgpys54 ts-node: 10.9.1_cbe7ovvae6zqfnmtgctpgpys54
typescript: 4.8.4 typescript: 4.8.4
@ -9776,38 +9776,38 @@ packages:
inline-style-parser: 0.1.1 inline-style-parser: 0.1.1
dev: true dev: true
/stylelint-config-css-modules/4.1.0_stylelint@14.14.1: /stylelint-config-css-modules/4.1.0_stylelint@14.15.0:
resolution: {integrity: sha512-w6d552NscwvpUEaUcmq8GgWXKRv6lVHLbDj6QIHSM2vCWr83qRqRvXBJCfXDyaG/J3Zojw2inU9VvU99ZlXuUw==} resolution: {integrity: sha512-w6d552NscwvpUEaUcmq8GgWXKRv6lVHLbDj6QIHSM2vCWr83qRqRvXBJCfXDyaG/J3Zojw2inU9VvU99ZlXuUw==}
peerDependencies: peerDependencies:
stylelint: ^14.5.1 stylelint: ^14.5.1
dependencies: dependencies:
stylelint: 14.14.1 stylelint: 14.15.0
optionalDependencies: optionalDependencies:
stylelint-scss: 4.3.0_stylelint@14.14.1 stylelint-scss: 4.3.0_stylelint@14.15.0
dev: true dev: true
/stylelint-config-prettier-scss/0.0.1_stylelint@14.14.1: /stylelint-config-prettier-scss/0.0.1_stylelint@14.15.0:
resolution: {integrity: sha512-lBAYG9xYOh2LeWEPC/64xeUxwOTnQ8nDyBijQoWoJb10/bMGrUwnokpt8jegGck2Vbtxh6XGwH63z5qBcVHreQ==} resolution: {integrity: sha512-lBAYG9xYOh2LeWEPC/64xeUxwOTnQ8nDyBijQoWoJb10/bMGrUwnokpt8jegGck2Vbtxh6XGwH63z5qBcVHreQ==}
engines: {node: '>= 12'} engines: {node: '>= 12'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
stylelint: '>=11.0.0' stylelint: '>=11.0.0'
dependencies: dependencies:
stylelint: 14.14.1 stylelint: 14.15.0
stylelint-config-prettier: 9.0.4_stylelint@14.14.1 stylelint-config-prettier: 9.0.4_stylelint@14.15.0
dev: true dev: true
/stylelint-config-prettier/9.0.4_stylelint@14.14.1: /stylelint-config-prettier/9.0.4_stylelint@14.15.0:
resolution: {integrity: sha512-38nIGTGpFOiK5LjJ8Ma1yUgpKENxoKSOhbDNSemY7Ep0VsJoXIW9Iq/2hSt699oB9tReynfWicTAoIHiq8Rvbg==} resolution: {integrity: sha512-38nIGTGpFOiK5LjJ8Ma1yUgpKENxoKSOhbDNSemY7Ep0VsJoXIW9Iq/2hSt699oB9tReynfWicTAoIHiq8Rvbg==}
engines: {node: '>= 12'} engines: {node: '>= 12'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
stylelint: '>=11.0.0' stylelint: '>=11.0.0'
dependencies: dependencies:
stylelint: 14.14.1 stylelint: 14.15.0
dev: true dev: true
/stylelint-config-recommended-scss/8.0.0_ave2i6l4ingtbwj4aquhd5witq: /stylelint-config-recommended-scss/8.0.0_a37symlv4urgexnspmy4gyeh7i:
resolution: {integrity: sha512-BxjxEzRaZoQb7Iinc3p92GS6zRdRAkIuEu2ZFLTxJK2e1AIcCb5B5MXY9KOXdGTnYFZ+KKx6R4Fv9zU6CtMYPQ==} resolution: {integrity: sha512-BxjxEzRaZoQb7Iinc3p92GS6zRdRAkIuEu2ZFLTxJK2e1AIcCb5B5MXY9KOXdGTnYFZ+KKx6R4Fv9zU6CtMYPQ==}
peerDependencies: peerDependencies:
postcss: ^8.3.3 postcss: ^8.3.3
@ -9818,20 +9818,20 @@ packages:
dependencies: dependencies:
postcss: 8.4.19 postcss: 8.4.19
postcss-scss: 4.0.5_postcss@8.4.19 postcss-scss: 4.0.5_postcss@8.4.19
stylelint: 14.14.1 stylelint: 14.15.0
stylelint-config-recommended: 9.0.0_stylelint@14.14.1 stylelint-config-recommended: 9.0.0_stylelint@14.15.0
stylelint-scss: 4.3.0_stylelint@14.14.1 stylelint-scss: 4.3.0_stylelint@14.15.0
dev: true dev: true
/stylelint-config-recommended/9.0.0_stylelint@14.14.1: /stylelint-config-recommended/9.0.0_stylelint@14.15.0:
resolution: {integrity: sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==} resolution: {integrity: sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==}
peerDependencies: peerDependencies:
stylelint: ^14.10.0 stylelint: ^14.10.0
dependencies: dependencies:
stylelint: 14.14.1 stylelint: 14.15.0
dev: true dev: true
/stylelint-config-standard-scss/6.1.0_ave2i6l4ingtbwj4aquhd5witq: /stylelint-config-standard-scss/6.1.0_a37symlv4urgexnspmy4gyeh7i:
resolution: {integrity: sha512-iZ2B5kQT2G3rUzx+437cEpdcnFOQkwnwqXuY8Z0QUwIHQVE8mnYChGAquyKFUKZRZ0pRnrciARlPaR1RBtPb0Q==} resolution: {integrity: sha512-iZ2B5kQT2G3rUzx+437cEpdcnFOQkwnwqXuY8Z0QUwIHQVE8mnYChGAquyKFUKZRZ0pRnrciARlPaR1RBtPb0Q==}
peerDependencies: peerDependencies:
postcss: ^8.3.3 postcss: ^8.3.3
@ -9841,31 +9841,31 @@ packages:
optional: true optional: true
dependencies: dependencies:
postcss: 8.4.19 postcss: 8.4.19
stylelint: 14.14.1 stylelint: 14.15.0
stylelint-config-recommended-scss: 8.0.0_ave2i6l4ingtbwj4aquhd5witq stylelint-config-recommended-scss: 8.0.0_a37symlv4urgexnspmy4gyeh7i
stylelint-config-standard: 29.0.0_stylelint@14.14.1 stylelint-config-standard: 29.0.0_stylelint@14.15.0
dev: true dev: true
/stylelint-config-standard/29.0.0_stylelint@14.14.1: /stylelint-config-standard/29.0.0_stylelint@14.15.0:
resolution: {integrity: sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==} resolution: {integrity: sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==}
peerDependencies: peerDependencies:
stylelint: ^14.14.0 stylelint: ^14.14.0
dependencies: dependencies:
stylelint: 14.14.1 stylelint: 14.15.0
stylelint-config-recommended: 9.0.0_stylelint@14.14.1 stylelint-config-recommended: 9.0.0_stylelint@14.15.0
dev: true dev: true
/stylelint-order/5.0.0_stylelint@14.14.1: /stylelint-order/5.0.0_stylelint@14.15.0:
resolution: {integrity: sha512-OWQ7pmicXufDw5BlRqzdz3fkGKJPgLyDwD1rFY3AIEfIH/LQY38Vu/85v8/up0I+VPiuGRwbc2Hg3zLAsJaiyw==} resolution: {integrity: sha512-OWQ7pmicXufDw5BlRqzdz3fkGKJPgLyDwD1rFY3AIEfIH/LQY38Vu/85v8/up0I+VPiuGRwbc2Hg3zLAsJaiyw==}
peerDependencies: peerDependencies:
stylelint: ^14.0.0 stylelint: ^14.0.0
dependencies: dependencies:
postcss: 8.4.19 postcss: 8.4.19
postcss-sorting: 7.0.1_postcss@8.4.19 postcss-sorting: 7.0.1_postcss@8.4.19
stylelint: 14.14.1 stylelint: 14.15.0
dev: true dev: true
/stylelint-scss/4.3.0_stylelint@14.14.1: /stylelint-scss/4.3.0_stylelint@14.15.0:
resolution: {integrity: sha512-GvSaKCA3tipzZHoz+nNO7S02ZqOsdBzMiCx9poSmLlb3tdJlGddEX/8QzCOD8O7GQan9bjsvLMsO5xiw6IhhIQ==} resolution: {integrity: sha512-GvSaKCA3tipzZHoz+nNO7S02ZqOsdBzMiCx9poSmLlb3tdJlGddEX/8QzCOD8O7GQan9bjsvLMsO5xiw6IhhIQ==}
peerDependencies: peerDependencies:
stylelint: ^14.5.1 stylelint: ^14.5.1
@ -9875,11 +9875,11 @@ packages:
postcss-resolve-nested-selector: 0.1.1 postcss-resolve-nested-selector: 0.1.1
postcss-selector-parser: 6.0.10 postcss-selector-parser: 6.0.10
postcss-value-parser: 4.2.0 postcss-value-parser: 4.2.0
stylelint: 14.14.1 stylelint: 14.15.0
dev: true dev: true
/stylelint/14.14.1: /stylelint/14.15.0:
resolution: {integrity: sha512-Jnftu+lSD8cSpcV/+Z2nfgfgFpTIS1FcujezXPngtoIQ6wtwutL22MsNE0dJuMiM1h1790g2qIjAyUZCMrX4sw==} resolution: {integrity: sha512-JOgDAo5QRsqiOZPZO+B9rKJvBm64S0xasbuRPAbPs6/vQDgDCnZLIiw6XcAS6GQKk9k1sBWR6rmH3Mfj8OknKg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
hasBin: true hasBin: true
dependencies: dependencies:

View File

@ -39,10 +39,12 @@ export default (props: ArticleListProps) => {
setLoadingMore(false) setLoadingMore(false)
} }
} }
const x: number = Math.floor(articles().length / 6)
// eslint-disable-next-line unicorn/new-for-builtins
const numbers: number[] = [...Array(x).keys()]
return ( return (
<Suspense fallback={<div class="article-preview">{t('Loading')}</div>}> <Suspense fallback={<div class="article-preview">{t('Loading')}</div>}>
<For each={[...new Array(Math.floor(articles().length / 6)).keys()]}> <For each={numbers}>
{() => <Block6 articles={articles().slice(0, Math.min(6, articles().length))} />} {() => <Block6 articles={articles().slice(0, Math.min(6, articles().length))} />}
</For> </For>
<a href={''} onClick={handleMore} classList={{ disabled: loadingMore() }}> <a href={''} onClick={handleMore} classList={{ disabled: loadingMore() }}>

View File

@ -1,45 +1,147 @@
import { PageWrap } from '../Wraps/PageWrap' import { PageWrap } from '../Wraps/PageWrap'
import { LayoutType, LayoutView } from '../Views/LayoutView'
import type { PageProps } from '../types' import type { PageProps } from '../types'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js' import { createMemo, createSignal, For, onCleanup, onMount, Show } from 'solid-js'
import { resetSortedArticles } from '../../stores/zine/articles' import { resetSortedArticles } from '../../stores/zine/articles'
import { useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
import { loadRecentLayoutShouts } from '../../stores/zine/layouts' import { LayoutType, loadRecentLayoutShouts, useLayoutsStore } from '../../stores/zine/layouts'
import { Loading } from '../Loading' import { Loading } from '../Loading'
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
import type { Shout } from '../../graphql/types.gen'
import { splitToPages } from '../../utils/splitToPages'
import clsx from 'clsx'
import { t } from '../../utils/intl'
import { Row3 } from '../Feed/Row3'
import { Row2 } from '../Feed/Row2'
import { Beside } from '../Feed/Beside'
import Slider from '../Feed/Slider'
import { Row1 } from '../Feed/Row1'
import styles from '../../styles/Topic.module.scss'
const PER_PAGE = 50 export const PRERENDERED_ARTICLES_COUNT = 21
const LOAD_MORE_PAGE_SIZE = 9 // Row3 + Row3 + Row3
export const LayoutShoutsPage = (props: PageProps) => { export const LayoutShoutsPage = (props: PageProps) => {
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.shouts))
const layout = createMemo<LayoutType>(() => { const layout = createMemo<LayoutType>(() => {
const { page: getPage } = useRouter() const { page: getPage } = useRouter()
const page = getPage() const page = getPage()
if (page.route !== 'expo') throw new Error('ts guard')
if (page.route !== 'expo') {
throw new Error('ts guard')
}
return page.params.layout as LayoutType return page.params.layout as LayoutType
}) })
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const { sortedLayoutShouts } = useLayoutsStore(layout(), props.shouts)
const sortedArticles = createMemo(() => sortedLayoutShouts().get(layout()))
const loadMoreLayout = async (kind: LayoutType) => {
saveScrollPosition()
onMount(async () => { const { hasMore } = await loadRecentLayoutShouts({
if (isLoaded()) { layout: kind,
return amount: LOAD_MORE_PAGE_SIZE,
offset: sortedArticles().length
})
setIsLoadMoreButtonVisible(hasMore)
restoreScrollPosition()
} }
await loadRecentLayoutShouts({ layout: layout(), amount: PER_PAGE, offset: 0 }) onMount(async () => {
if (sortedArticles().length === PRERENDERED_ARTICLES_COUNT) {
loadMoreLayout(layout())
}
})
const title = createMemo(() => {
const l = layout()
if (l === 'audio') return t('Audio')
if (l === 'video') return t('Video')
if (l === 'image') return t('Artworks')
return t('Literature')
})
const pages = createMemo<Shout[][]>(() =>
splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
)
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.shouts))
onMount(async () => {
if (!isLoaded()) {
await loadRecentLayoutShouts({ layout: layout(), amount: PRERENDERED_ARTICLES_COUNT, offset: 0 })
setIsLoaded(true) setIsLoaded(true)
}
}) })
onCleanup(() => resetSortedArticles()) onCleanup(() => resetSortedArticles())
const ModeSwitcher = () => (
<div class="container">
<div class={clsx(styles.groupControls, 'row group__controls')}>
<div class="col-md-8">
<ul class="view-switcher">
<li classList={{ selected: layout() === 'audio' }}>
<a href="/expo/audio">{t('Audio')}</a>
</li>
<li classList={{ selected: layout() === 'video' }}>
<a href="/expo/video">{t('Video')}</a>
</li>
<li classList={{ selected: layout() === 'image' }}>
<a href="/expo/image">{t('Artworks')}</a>
</li>
<li classList={{ selected: layout() === 'literature' }}>
<a href="/expo/literature">{t('Literature')}</a>
</li>
</ul>
</div>
<div class="col-md-4">
<div class="mode-switcher">
{`${t('Show')} `}
<span class="mode-switcher__control">{t('All posts')}</span>
</div>
</div>
</div>
</div>
)
return ( return (
<PageWrap> <PageWrap>
<Show when={isLoaded()} fallback={<Loading />}> <Show when={isLoaded()} fallback={<Loading />}>
<LayoutView layout={layout() as LayoutType} shouts={props.shouts} /> <div class={styles.topicPage}>
<Show when={layout()}>
<h1>{title()}</h1>
<ModeSwitcher />
<Row1 article={sortedArticles()[0]} />
<Row2 articles={sortedArticles().slice(1, 3)} />
<Slider title={title()} articles={sortedArticles().slice(5, 11)} />
<Beside
beside={sortedArticles()[12]}
title={t('Top viewed')}
values={sortedArticles().slice(0, 5)}
wrapper={'top-article'}
/>
<Show when={sortedArticles().length > 5}>
<Row3 articles={sortedArticles().slice(13, 16)} />
<Row2 articles={sortedArticles().slice(16, 18)} />
<Row3 articles={sortedArticles().slice(18, 21)} />
<Row3 articles={sortedArticles().slice(21, 24)} />
<Row3 articles={sortedArticles().slice(24, 27)} />
</Show>
<For each={pages()}>
{(page) => (
<>
<Row3 articles={page.slice(0, 3)} />
<Row3 articles={page.slice(3, 6)} />
<Row3 articles={page.slice(6, 9)} />
</>
)}
</For>
<Show when={isLoadMoreButtonVisible()}>
<p class="load-more-container">
<button class="button" onClick={() => loadMoreLayout(layout())}>
{t('Load more')}
</button>
</p>
</Show>
</Show>
</div>
</Show> </Show>
</PageWrap> </PageWrap>
) )

View File

@ -23,7 +23,7 @@ type AuthorProps = {
} }
type AuthorPageSearchParams = { type AuthorPageSearchParams = {
by: '' | 'viewed' | 'rating' | 'commented' | 'recent' by: '' | 'viewed' | 'rating' | 'commented' | 'recent' | 'followed'
} }
export const PRERENDERED_ARTICLES_COUNT = 12 export const PRERENDERED_ARTICLES_COUNT = 12
@ -76,27 +76,21 @@ export const AuthorView = (props: AuthorProps) => {
<div class="row group__controls"> <div class="row group__controls">
<div class="col-md-8"> <div class="col-md-8">
<ul class="view-switcher"> <ul class="view-switcher">
<li classList={{ selected: !searchParams().by || searchParams().by === 'recent' }}> <li classList={{ selected: searchParams().by === 'rating' }}>
<button type="button" onClick={() => changeSearchParam('by', 'recent')}> <button type="button" onClick={() => changeSearchParam('by', 'rating')}>
{t('Recent')} {t('Popular')}
</button>
</li>
<li classList={{ selected: searchParams().by === 'followed' }}>
<button type="button" onClick={() => changeSearchParam('by', 'followed')}>
{t('Followers')}
</button>
</li>
<li classList={{ selected: searchParams().by === 'commented' }}>
<button type="button" onClick={() => changeSearchParam('by', 'commented')}>
{t('Discussing')}
</button> </button>
</li> </li>
{/*TODO: server sort*/}
{/*<li classList={{ selected: getSearchParams().by === 'rating' }}>*/}
{/* <button type="button" onClick={() => changeSearchParam('by', 'rating')}>*/}
{/* {t('Popular')}*/}
{/* </button>*/}
{/*</li>*/}
{/*<li classList={{ selected: getSearchParams().by === 'viewed' }}>*/}
{/* <button type="button" onClick={() => changeSearchParam('by', 'viewed')}>*/}
{/* {t('Views')}*/}
{/* </button>*/}
{/*</li>*/}
{/*<li classList={{ selected: getSearchParams().by === 'commented' }}>*/}
{/* <button type="button" onClick={() => changeSearchParam('by', 'commented')}>*/}
{/* {t('Discussing')}*/}
{/* </button>*/}
{/*</li>*/}
</ul> </ul>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">

View File

@ -1,133 +0,0 @@
import { For, Show, createMemo, onMount, createSignal } from 'solid-js'
import type { Shout } from '../../graphql/types.gen'
import { Row3 } from '../Feed/Row3'
import { Row2 } from '../Feed/Row2'
import { Beside } from '../Feed/Beside'
import styles from '../../styles/Topic.module.scss'
import { t } from '../../utils/intl'
import { useLayoutsStore } from '../../stores/zine/layouts'
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
import { splitToPages } from '../../utils/splitToPages'
import { clsx } from 'clsx'
import Slider from '../Feed/Slider'
import { Row1 } from '../Feed/Row1'
import { loadRecentLayoutShouts } from '../../stores/zine/layouts'
export type LayoutType = 'article' | 'audio' | 'video' | 'image' | 'literature'
interface LayoutProps {
layout: LayoutType
shouts: Shout[]
}
export const PRERENDERED_ARTICLES_COUNT = 21
const LOAD_MORE_PAGE_SIZE = 9 // Row3 + Row3 + Row3
export const LayoutView = (props: LayoutProps) => {
const layout = createMemo<LayoutType>(() => props.layout)
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const { sortedLayoutShouts } = useLayoutsStore(layout(), props.shouts)
const sortedArticles = createMemo(() => sortedLayoutShouts().get(layout()))
const loadMoreLayout = async (kind: LayoutType) => {
saveScrollPosition()
const { hasMore } = await loadRecentLayoutShouts({
layout: kind,
amount: LOAD_MORE_PAGE_SIZE,
offset: sortedArticles().length
})
setIsLoadMoreButtonVisible(hasMore)
restoreScrollPosition()
}
onMount(async () => {
if (sortedArticles().length === PRERENDERED_ARTICLES_COUNT) {
loadMoreLayout(layout())
}
})
const title = createMemo(() => {
const l = layout()
if (l === 'audio') return t('Audio')
if (l === 'video') return t('Video')
if (l === 'image') return t('Artworks')
return t('Literature')
})
const pages = createMemo<Shout[][]>(() =>
splitToPages(sortedArticles(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE)
)
const ModeSwitcher = () => (
<div class="container">
<div class={clsx(styles.groupControls, 'row group__controls')}>
<div class="col-md-8">
<ul class="view-switcher">
<li classList={{ selected: layout() === 'audio' }}>
<a href="/expo/audio">{t('Audio')}</a>
</li>
<li classList={{ selected: layout() === 'video' }}>
<a href="/expo/video">{t('Video')}</a>
</li>
<li classList={{ selected: layout() === 'image' }}>
<a href="/expo/image">{t('Artworks')}</a>
</li>
<li classList={{ selected: layout() === 'literature' }}>
<a href="/expo/literature">{t('Literature')}</a>
</li>
</ul>
</div>
<div class="col-md-4">
<div class="mode-switcher">
{`${t('Show')} `}
<span class="mode-switcher__control">{t('All posts')}</span>
</div>
</div>
</div>
</div>
)
return (
<div class={styles.topicPage}>
<Show when={layout()}>
<h1>{title()}</h1>
<ModeSwitcher />
<Row1 article={sortedArticles()[0]} />
<Row2 articles={sortedArticles().slice(1, 3)} />
<Slider title={title()} articles={sortedArticles().slice(5, 11)} />
<Beside
beside={sortedArticles()[12]}
title={t('Top viewed')}
values={sortedArticles().slice(0, 5)}
wrapper={'top-article'}
/>
<Show when={sortedArticles().length > 5}>
<Row3 articles={sortedArticles().slice(13, 16)} />
<Row2 articles={sortedArticles().slice(16, 18)} />
<Row3 articles={sortedArticles().slice(18, 21)} />
<Row3 articles={sortedArticles().slice(21, 24)} />
<Row3 articles={sortedArticles().slice(24, 27)} />
</Show>
<For each={pages()}>
{(page) => (
<>
<Row3 articles={page.slice(0, 3)} />
<Row3 articles={page.slice(3, 6)} />
<Row3 articles={page.slice(6, 9)} />
</>
)}
</For>
<Show when={isLoadMoreButtonVisible()}>
<p class="load-more-container">
<button class="button" onClick={() => loadMoreLayout(layout())}>
{t('Load more')}
</button>
</p>
</Show>
</Show>
</div>
)
}

View File

@ -1,5 +1,6 @@
// in a separate file to avoid circular dependencies // in a separate file to avoid circular dependencies
import type { Author, Chat, Shout, Topic } from '../graphql/types.gen' import type { Author, Chat, Shout, Topic } from '../graphql/types.gen'
import type { LayoutType } from '../stores/zine/layouts'
// all the things (she said) that could be passed from the server // all the things (she said) that could be passed from the server
export type PageProps = { export type PageProps = {
@ -11,7 +12,7 @@ export type PageProps = {
topic?: Topic topic?: Topic
allTopics?: Topic[] allTopics?: Topic[]
searchQuery?: string searchQuery?: string
layout?: string layout?: LayoutType
// other types? // other types?
searchResults?: Shout[] searchResults?: Shout[]
chats?: Chat[] chats?: Chat[]

View File

@ -25,18 +25,30 @@ export type Author = {
bio?: Maybe<Scalars['String']> bio?: Maybe<Scalars['String']>
caption?: Maybe<Scalars['String']> caption?: Maybe<Scalars['String']>
id: Scalars['Int'] id: Scalars['Int']
lastSeen?: Maybe<Scalars['DateTime']>
links?: Maybe<Array<Maybe<Scalars['String']>>> links?: Maybe<Array<Maybe<Scalars['String']>>>
name: Scalars['String'] name: Scalars['String']
roles?: Maybe<Array<Maybe<Role>>>
slug: Scalars['String'] slug: Scalars['String']
stat?: Maybe<AuthorStat>
userpic?: Maybe<Scalars['String']> userpic?: Maybe<Scalars['String']>
} }
export type AuthorStat = {
followers?: Maybe<Scalars['Int']>
followings?: Maybe<Scalars['Int']>
rating?: Maybe<Scalars['Int']>
commented?: Maybe<Scalars['Int']>
}
export type Chat = { export type Chat = {
admins?: Maybe<Array<Maybe<User>>>
createdAt: Scalars['Int'] createdAt: Scalars['Int']
createdBy: User createdBy: User
description?: Maybe<Scalars['String']> description?: Maybe<Scalars['String']>
id: Scalars['String'] id: Scalars['String']
messages: Array<Maybe<Message>> messages: Array<Maybe<Message>>
private?: Maybe<Scalars['Boolean']>
title?: Maybe<Scalars['String']> title?: Maybe<Scalars['String']>
unread?: Maybe<Scalars['Int']> unread?: Maybe<Scalars['Int']>
updatedAt: Scalars['Int'] updatedAt: Scalars['Int']
@ -53,8 +65,8 @@ export type ChatMember = {
invitedAt?: Maybe<Scalars['DateTime']> invitedAt?: Maybe<Scalars['DateTime']>
invitedBy?: Maybe<Scalars['String']> invitedBy?: Maybe<Scalars['String']>
name: Scalars['String'] name: Scalars['String']
pic?: Maybe<Scalars['String']>
slug: Scalars['String'] slug: Scalars['String']
userpic?: Maybe<Scalars['String']>
} }
export type Collab = { export type Collab = {
@ -130,6 +142,7 @@ export type Mutation = {
createReaction: Result createReaction: Result
createShout: Result createShout: Result
createTopic: Result createTopic: Result
deleteChat: Result
deleteCollection: Result deleteCollection: Result
deleteCommunity: Result deleteCommunity: Result
deleteMessage: Result deleteMessage: Result
@ -193,6 +206,10 @@ export type MutationCreateTopicArgs = {
input: TopicInput input: TopicInput
} }
export type MutationDeleteChatArgs = {
chatId: Scalars['String']
}
export type MutationDeleteCollectionArgs = { export type MutationDeleteCollectionArgs = {
slug: Scalars['String'] slug: Scalars['String']
} }
@ -330,7 +347,7 @@ export type ProfileInput = {
} }
export type Query = { export type Query = {
authorsAll: Array<Maybe<User>> authorsAll: Array<Maybe<Author>>
collectionsAll: Array<Maybe<Collection>> collectionsAll: Array<Maybe<Collection>>
getAuthor: User getAuthor: User
getCollabs: Array<Maybe<Collab>> getCollabs: Array<Maybe<Collab>>
@ -340,39 +357,46 @@ export type Query = {
getTopic: Topic getTopic: Topic
getUserCollections: Array<Maybe<Collection>> getUserCollections: Array<Maybe<Collection>>
getUserRoles: Array<Maybe<Role>> getUserRoles: Array<Maybe<Role>>
getUsersBySlugs: Array<Maybe<User>> getUsersBySlugs: Array<Maybe<Author>>
isEmailUsed: Scalars['Boolean'] isEmailUsed: Scalars['Boolean']
loadChat: Result loadChats: Result
loadMessages: Result
markdownBody: Scalars['String'] markdownBody: Scalars['String']
myChats: Result
reactionsByAuthor: Array<Maybe<Reaction>> reactionsByAuthor: Array<Maybe<Reaction>>
reactionsForShouts: Array<Maybe<Reaction>> reactionsForShouts: Array<Maybe<Reaction>>
recentAll: Array<Maybe<Shout>> recentAll: Array<Maybe<Shout>>
recentCandidates: Array<Maybe<Shout>> recentCandidates: Array<Maybe<Shout>>
recentCommented: Array<Maybe<Shout>> recentCommented: Array<Maybe<Shout>>
recentLayoutShouts: Array<Maybe<Shout>>
recentPublished: Array<Maybe<Shout>> recentPublished: Array<Maybe<Shout>>
recentReacted: Array<Maybe<Shout>> recentReacted: Array<Maybe<Shout>>
searchChats: Result
searchMessages: Result
searchQuery?: Maybe<Array<Maybe<Shout>>> searchQuery?: Maybe<Array<Maybe<Shout>>>
searchUsers: Result
shoutsByAuthors: Array<Maybe<Shout>> shoutsByAuthors: Array<Maybe<Shout>>
shoutsByCollection: Array<Maybe<Shout>> shoutsByCollection: Array<Maybe<Shout>>
shoutsByCommunities: Array<Maybe<Shout>> shoutsByCommunities: Array<Maybe<Shout>>
shoutsByLayout: Array<Maybe<Shout>>
shoutsByTopics: Array<Maybe<Shout>> shoutsByTopics: Array<Maybe<Shout>>
shoutsForFeed: Array<Maybe<Shout>> shoutsForFeed: Array<Maybe<Shout>>
signIn: AuthResult signIn: AuthResult
signOut: AuthResult signOut: AuthResult
topAuthors: Array<Maybe<Author>> topAuthors: Array<Maybe<Author>>
topCommented: Array<Maybe<Shout>> topCommented: Array<Maybe<Shout>>
topLayoutShouts: Array<Maybe<Shout>>
topMonth: Array<Maybe<Shout>> topMonth: Array<Maybe<Shout>>
topMonthLayoutShouts: Array<Maybe<Shout>>
topOverall: Array<Maybe<Shout>> topOverall: Array<Maybe<Shout>>
topPublished: Array<Maybe<Shout>> topPublished: Array<Maybe<Shout>>
topicsAll: Array<Maybe<Topic>> topicsAll: Array<Maybe<Topic>>
topicsByAuthor: Array<Maybe<Topic>> topicsByAuthor: Array<Maybe<Topic>>
topicsByCommunity: Array<Maybe<Topic>> topicsByCommunity: Array<Maybe<Topic>>
topicsRandom: Array<Maybe<Topic>> topicsRandom: Array<Maybe<Topic>>
userFollowedAuthors: Array<Maybe<User>> userFollowedAuthors: Array<Maybe<Author>>
userFollowedCommunities: Array<Maybe<Community>> userFollowedCommunities: Array<Maybe<Community>>
userFollowedTopics: Array<Maybe<Topic>> userFollowedTopics: Array<Maybe<Topic>>
userFollowers: Array<Maybe<User>> userFollowers: Array<Maybe<Author>>
userReactedShouts: Array<Maybe<Shout>> userReactedShouts: Array<Maybe<Shout>>
} }
@ -408,7 +432,12 @@ export type QueryIsEmailUsedArgs = {
email: Scalars['String'] email: Scalars['String']
} }
export type QueryLoadChatArgs = { export type QueryLoadChatsArgs = {
amount?: InputMaybe<Scalars['Int']>
offset?: InputMaybe<Scalars['Int']>
}
export type QueryLoadMessagesArgs = {
amount?: InputMaybe<Scalars['Int']> amount?: InputMaybe<Scalars['Int']>
chatId: Scalars['String'] chatId: Scalars['String']
offset?: InputMaybe<Scalars['Int']> offset?: InputMaybe<Scalars['Int']>
@ -445,6 +474,12 @@ export type QueryRecentCommentedArgs = {
offset: Scalars['Int'] offset: Scalars['Int']
} }
export type QueryRecentLayoutShoutsArgs = {
amount?: InputMaybe<Scalars['Int']>
layout: Scalars['String']
offset?: InputMaybe<Scalars['Int']>
}
export type QueryRecentPublishedArgs = { export type QueryRecentPublishedArgs = {
limit: Scalars['Int'] limit: Scalars['Int']
offset: Scalars['Int'] offset: Scalars['Int']
@ -455,12 +490,30 @@ export type QueryRecentReactedArgs = {
offset: Scalars['Int'] offset: Scalars['Int']
} }
export type QuerySearchChatsArgs = {
amount?: InputMaybe<Scalars['Int']>
offset?: InputMaybe<Scalars['Int']>
q: Scalars['String']
}
export type QuerySearchMessagesArgs = {
amount?: InputMaybe<Scalars['Int']>
offset?: InputMaybe<Scalars['Int']>
q: Scalars['String']
}
export type QuerySearchQueryArgs = { export type QuerySearchQueryArgs = {
limit: Scalars['Int'] limit: Scalars['Int']
offset: Scalars['Int'] offset: Scalars['Int']
q?: InputMaybe<Scalars['String']> q?: InputMaybe<Scalars['String']>
} }
export type QuerySearchUsersArgs = {
amount?: InputMaybe<Scalars['Int']>
offset?: InputMaybe<Scalars['Int']>
q: Scalars['String']
}
export type QueryShoutsByAuthorsArgs = { export type QueryShoutsByAuthorsArgs = {
limit: Scalars['Int'] limit: Scalars['Int']
offset: Scalars['Int'] offset: Scalars['Int']
@ -479,6 +532,12 @@ export type QueryShoutsByCommunitiesArgs = {
slugs: Array<InputMaybe<Scalars['String']>> slugs: Array<InputMaybe<Scalars['String']>>
} }
export type QueryShoutsByLayoutArgs = {
amount: Scalars['Int']
layout?: InputMaybe<Scalars['String']>
offset: Scalars['Int']
}
export type QueryShoutsByTopicsArgs = { export type QueryShoutsByTopicsArgs = {
limit: Scalars['Int'] limit: Scalars['Int']
offset: Scalars['Int'] offset: Scalars['Int']
@ -506,11 +565,23 @@ export type QueryTopCommentedArgs = {
offset: Scalars['Int'] offset: Scalars['Int']
} }
export type QueryTopLayoutShoutsArgs = {
amount?: InputMaybe<Scalars['Int']>
layout: Scalars['String']
offset?: InputMaybe<Scalars['Int']>
}
export type QueryTopMonthArgs = { export type QueryTopMonthArgs = {
limit: Scalars['Int'] limit: Scalars['Int']
offset: Scalars['Int'] offset: Scalars['Int']
} }
export type QueryTopMonthLayoutShoutsArgs = {
amount?: InputMaybe<Scalars['Int']>
layout: Scalars['String']
offset?: InputMaybe<Scalars['Int']>
}
export type QueryTopOverallArgs = { export type QueryTopOverallArgs = {
limit: Scalars['Int'] limit: Scalars['Int']
offset: Scalars['Int'] offset: Scalars['Int']
@ -619,8 +690,8 @@ export type Resource = {
} }
export type Result = { export type Result = {
author?: Maybe<User> author?: Maybe<Author>
authors?: Maybe<Array<Maybe<User>>> authors?: Maybe<Array<Maybe<Author>>>
chat?: Maybe<Chat> chat?: Maybe<Chat>
chats?: Maybe<Array<Maybe<Chat>>> chats?: Maybe<Array<Maybe<Chat>>>
communities?: Maybe<Array<Maybe<Community>>> communities?: Maybe<Array<Maybe<Community>>>
@ -633,8 +704,10 @@ export type Result = {
reactions?: Maybe<Array<Maybe<Reaction>>> reactions?: Maybe<Array<Maybe<Reaction>>>
shout?: Maybe<Shout> shout?: Maybe<Shout>
shouts?: Maybe<Array<Maybe<Shout>>> shouts?: Maybe<Array<Maybe<Shout>>>
slugs?: Maybe<Array<Maybe<Scalars['String']>>>
topic?: Maybe<Topic> topic?: Maybe<Topic>
topics?: Maybe<Array<Maybe<Topic>>> topics?: Maybe<Array<Maybe<Topic>>>
uids?: Maybe<Array<Maybe<Scalars['String']>>>
} }
export type Role = { export type Role = {
@ -653,7 +726,6 @@ export type Shout = {
createdAt: Scalars['DateTime'] createdAt: Scalars['DateTime']
deletedAt?: Maybe<Scalars['DateTime']> deletedAt?: Maybe<Scalars['DateTime']>
deletedBy?: Maybe<User> deletedBy?: Maybe<User>
draft?: Maybe<Scalars['Boolean']>
id: Scalars['Int'] id: Scalars['Int']
lang?: Maybe<Scalars['String']> lang?: Maybe<Scalars['String']>
layout?: Maybe<Scalars['String']> layout?: Maybe<Scalars['String']>
@ -667,8 +739,8 @@ export type Shout = {
topics?: Maybe<Array<Maybe<Topic>>> topics?: Maybe<Array<Maybe<Topic>>>
updatedAt?: Maybe<Scalars['DateTime']> updatedAt?: Maybe<Scalars['DateTime']>
updatedBy?: Maybe<User> updatedBy?: Maybe<User>
versionOf?: Maybe<Shout> versionOf?: Maybe<Scalars['String']>
visibleFor?: Maybe<Array<Maybe<User>>> visibility?: Maybe<Scalars['String']>
} }
export type ShoutInput = { export type ShoutInput = {

View File

@ -3,16 +3,14 @@ import { Root } from '../../components/Root'
import Prerendered from '../../main.astro' import Prerendered from '../../main.astro'
import { apiClient } from '../../utils/apiClient' import { apiClient } from '../../utils/apiClient'
import { initRouter } from '../../stores/router' import { initRouter } from '../../stores/router'
import type { LayoutType } from '../../stores/zine/layouts'
import { Layout } from '../../components/EditorExample/components/Layout'
const layout = Astro.params.layout?.toString() const layout = (Astro.params.layout?.toString() || 'article') as LayoutType
if (layout.endsWith('.map')) { if (!layout || layout.endsWith('.map')) {
return Astro.redirect('/404') return Astro.redirect('/404')
} }
const LAYOUTS = ['literature', 'audio', 'video', 'artworks'] const shouts = await apiClient.getRecentLayoutShouts({ layout })
if (!LAYOUTS.includes(layout)) {
return Astro.redirect('/404')
}
const shouts = await apiClient.getLayoutShouts({ layout })
const { pathname, search } = Astro.url const { pathname, search } = Astro.url
initRouter(pathname, search) initRouter(pathname, search)
--- ---

View File

@ -2,9 +2,10 @@ import type { Shout } from '../../graphql/types.gen'
import { apiClient } from '../../utils/apiClient' import { apiClient } from '../../utils/apiClient'
import { useArticlesStore } from './articles' import { useArticlesStore } from './articles'
import { createSignal } from 'solid-js' import { createSignal } from 'solid-js'
import type { LayoutType } from '../../components/Views/LayoutView'
import { byCreated } from '../../utils/sortby' import { byCreated } from '../../utils/sortby'
export type LayoutType = 'article' | 'audio' | 'video' | 'image' | 'literature'
const [sortedLayoutShouts, setSortedLayoutShouts] = createSignal<Map<LayoutType, Shout[]>>(new Map()) const [sortedLayoutShouts, setSortedLayoutShouts] = createSignal<Map<LayoutType, Shout[]>>(new Map())
const addLayoutShouts = (layout: LayoutType, shouts: Shout[]) => { const addLayoutShouts = (layout: LayoutType, shouts: Shout[]) => {

View File

@ -32,7 +32,7 @@ import myChats from '../graphql/query/my-chats'
import getRecentByLayout from '../graphql/query/layout-recent' import getRecentByLayout from '../graphql/query/layout-recent'
import getTopByLayout from '../graphql/query/layout-top' import getTopByLayout from '../graphql/query/layout-top'
import getTopMonthByLayout from '../graphql/query/layout-top-month' import getTopMonthByLayout from '../graphql/query/layout-top-month'
import type { LayoutType } from '../components/Views/LayoutView' import type { LayoutType } from '../stores/zine/layouts'
const FEED_SIZE = 50 const FEED_SIZE = 50

View File

@ -1,5 +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 = 'https://testapi.discours.io'
// export const apiBaseUrl = 'http://localhost:8080' export const apiBaseUrl = 'http://localhost:8080'