Merge pull request #56 from Discours/layouts

Layouts WIP
This commit is contained in:
Tony 2022-11-17 09:33:55 +03:00 committed by GitHub
commit 4b29bdad0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
136 changed files with 13928 additions and 2896 deletions

View File

@ -3,5 +3,6 @@ public
*.cjs *.cjs
src/graphql/*.gen.ts src/graphql/*.gen.ts
src/legacy_* src/legacy_*
src/components/EditorExample
dist/ dist/
.vercel/ .vercel/

View File

@ -74,6 +74,8 @@ module.exports = {
'unicorn/numeric-separators-style': 'off', 'unicorn/numeric-separators-style': 'off',
'unicorn/prefer-node-protocol': 'off', 'unicorn/prefer-node-protocol': 'off',
'promise/always-return': 'off',
eqeqeq: 'error', eqeqeq: 'error',
'no-param-reassign': 'error', 'no-param-reassign': 'error',
'no-nested-ternary': 'error', 'no-nested-ternary': 'error',

View File

@ -1,5 +1,18 @@
[0.6.1]
[+] auth ver. 0.9
[+] load-by interfaces for shouts, authors and messages
[+] inbox logix and markup
[-] old views counting
[0.6.0] [0.6.0]
[+] editor enabled [+] hybrid routing ssr/spa
[+] 'expo' pages
[-] layout term usage with an exception
[-] less nanostores
[+] inbox
[+] css modules
[+] draft editor
[+] solid-driven storages
[0.5.1] [0.5.1]
[+] nanostores-base global store [+] nanostores-base global store

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: {
@ -67,7 +68,7 @@ const astroConfig: AstroUserConfig = {
} }
*/ */
}, },
external: ['@aws-sdk/clients/s3'] external: []
} }
}, },
css css

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

@ -1,6 +1,6 @@
{ {
"name": "discoursio-webapp", "name": "discoursio-webapp",
"version": "0.5.1", "version": "0.6.1",
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
@ -30,113 +30,113 @@
"vercel-build": "astro build" "vercel-build": "astro build"
}, },
"dependencies": { "dependencies": {
"mailgun.js": "^8.0.1" "mailgun.js": "^8.0.2"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/solid-js": "^1.1.0", "@astrojs/solid-js": "^1.2.3",
"@astrojs/vercel": "^2.1.0", "@astrojs/vercel": "^2.3.3",
"@babel/core": "^7.18.13", "@babel/core": "^7.20.2",
"@graphql-codegen/cli": "^2.12.1", "@graphql-codegen/cli": "^2.13.12",
"@graphql-codegen/typescript": "^2.7.3", "@graphql-codegen/typescript": "^2.8.2",
"@graphql-codegen/typescript-operations": "^2.5.3", "@graphql-codegen/typescript-operations": "^2.5.7",
"@graphql-codegen/typescript-urql": "^3.7.0", "@graphql-codegen/typescript-urql": "^3.7.3",
"@graphql-codegen/urql-introspection": "^2.2.1", "@graphql-codegen/urql-introspection": "^2.2.1",
"@graphql-tools/url-loader": "^7.16.4", "@graphql-tools/url-loader": "^7.16.16",
"@graphql-typed-document-node/core": "^3.1.1", "@graphql-typed-document-node/core": "^3.1.1",
"@nanostores/persistent": "^0.7.0",
"@nanostores/router": "^0.7.0", "@nanostores/router": "^0.7.0",
"@nanostores/solid": "^0.3.0", "@nanostores/solid": "^0.3.0",
"@popperjs/core": "^2.11.6", "@popperjs/core": "^2.11.6",
"@solid-devtools/debugger": "^0.13.1", "@solid-devtools/debugger": "^0.14.0",
"@solid-devtools/logger": "^0.4.9", "@solid-devtools/logger": "^0.5.0",
"@solid-primitives/memo": "^1.0.2", "@solid-primitives/memo": "^1.1.2",
"@solid-primitives/storage": "^1.3.3",
"@types/express": "^4.17.14", "@types/express": "^4.17.14",
"@types/node": "^18.7.19", "@types/node": "^18.11.9",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.29.0", "@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.35.1", "@typescript-eslint/parser": "^5.43.0",
"@urql/core": "^3.0.1", "@urql/core": "^3.0.5",
"@urql/devtools": "^2.0.3", "@urql/devtools": "^2.0.3",
"@urql/exchange-auth": "^1.0.0", "@urql/exchange-auth": "^1.0.0",
"@urql/exchange-graphcache": "^5.0.0", "@urql/exchange-graphcache": "^5.0.5",
"astro": "^1.1.1", "astro": "^1.6.8",
"astro-eslint-parser": "^0.9.0", "astro-eslint-parser": "^0.9.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"bootstrap": "5.1.3", "bootstrap": "5.1.3",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"cookie": "^0.5.0", "cookie": "^0.5.0",
"cookie-signature": "^1.2.0", "cookie-signature": "^1.2.0",
"eslint": "^8.26.0", "eslint": "^8.27.0",
"eslint-config-stylelint": "^17.0.0", "eslint-config-stylelint": "^17.0.0",
"eslint-import-resolver-typescript": "^3.5.0", "eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-astro": "^0.21.0", "eslint-plugin-astro": "^0.21.0",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-promise": "^6.0.1", "eslint-plugin-promise": "^6.1.1",
"eslint-plugin-solid": "^0.7.3", "eslint-plugin-solid": "^0.8.0",
"eslint-plugin-sonarjs": "^0.16.0", "eslint-plugin-sonarjs": "^0.16.0",
"eslint-plugin-unicorn": "^44.0.2", "eslint-plugin-unicorn": "^44.0.2",
"graphql": "^16.6.0", "graphql": "^16.6.0",
"graphql-tag": "^2.12.6", "graphql-tag": "^2.12.6",
"graphql-ws": "^5.11.2", "graphql-ws": "^5.11.2",
"hast-util-select": "^5.0.2", "hast-util-select": "^5.0.2",
"husky": "^8.0.1", "husky": "^8.0.2",
"idb": "^7.1.0", "idb": "^7.1.1",
"jest": "^29.2.1", "jest": "^29.3.1",
"lint-staged": "^13.0.3", "lint-staged": "^13.0.3",
"loglevel": "^1.8.0", "loglevel": "^1.8.1",
"loglevel-plugin-prefix": "^0.8.4", "loglevel-plugin-prefix": "^0.8.4",
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
"markdown-it-container": "^3.0.0", "markdown-it-container": "^3.0.0",
"markdown-it-implicit-figures": "^0.10.0", "markdown-it-implicit-figures": "^0.10.0",
"markdown-it-mark": "^3.0.1", "markdown-it-mark": "^3.0.1",
"markdown-it-replace-link": "^1.1.0", "markdown-it-replace-link": "^1.1.0",
"nanostores": "^0.7.0", "nanostores": "^0.7.1",
"orderedmap": "^2.1.0", "orderedmap": "^2.1.0",
"postcss": "^8.4.16", "postcss": "^8.4.19",
"postcss-modules": "^5.0.0", "postcss-modules": "5.0.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"prettier-eslint": "^15.0.1", "prettier-eslint": "^15.0.1",
"prosemirror-commands": "^1.3.1", "prosemirror-commands": "^1.3.1",
"prosemirror-dropcursor": "^1.6.0", "prosemirror-dropcursor": "^1.6.1",
"prosemirror-example-setup": "^1.2.1", "prosemirror-example-setup": "^1.2.1",
"prosemirror-gapcursor": "^1.3.1", "prosemirror-gapcursor": "^1.3.1",
"prosemirror-history": "^1.3.0", "prosemirror-history": "^1.3.0",
"prosemirror-inputrules": "^1.2.0", "prosemirror-inputrules": "^1.2.0",
"prosemirror-keymap": "^1.2.0", "prosemirror-keymap": "^1.2.0",
"prosemirror-markdown": "^1.9.4", "prosemirror-markdown": "^1.10.1",
"prosemirror-menu": "^1.2.1", "prosemirror-menu": "^1.2.1",
"prosemirror-model": "^1.16.0", "prosemirror-model": "^1.18.2",
"prosemirror-schema-list": "^1.2.2", "prosemirror-schema-list": "^1.2.2",
"prosemirror-state": "^1.4.1", "prosemirror-state": "^1.4.2",
"prosemirror-view": "^1.28.1", "prosemirror-view": "^1.29.1",
"rollup": "~2.79.1", "rollup": "^2.79.1",
"rollup-plugin-visualizer": "^5.8.2", "rollup-plugin-visualizer": "^5.8.3",
"sass": "^1.55.0", "sass": "^1.56.1",
"solid-devtools": "^0.20.1", "solid-devtools": "^0.22.0",
"solid-js": "^1.6.0", "solid-js": "^1.6.2",
"solid-js-form": "^0.1.5", "solid-js-form": "^0.1.5",
"solid-jsx": "^0.9.1", "solid-jsx": "^0.9.1",
"solid-social": "^0.9.0", "solid-social": "^0.9.0",
"solid-utils": "^0.8.1", "solid-utils": "^0.8.1",
"sort-package-json": "^2.0.0", "sort-package-json": "^2.1.0",
"stylelint": "^14.12.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.0.0", "stylelint-config-standard-scss": "^6.1.0",
"stylelint-order": "^5.0.0", "stylelint-order": "^5.0.0",
"stylelint-scss": "^4.3.0", "stylelint-scss": "^4.3.0",
"swiper": "^8.4.2", "swiper": "^8.4.4",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^4.8.3", "typescript": "^4.8.4",
"undici": "^5.10.0", "undici": "^5.12.0",
"unique-names-generator": "^4.7.1", "unique-names-generator": "^4.7.1",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"vite": "^3.1.3", "vite": "^3.2.4",
"ws": "^8.9.0", "ws": "^8.11.0",
"y-prosemirror": "^1.2.0", "y-prosemirror": "^1.2.0",
"y-protocols": "^1.0.5", "y-protocols": "^1.0.5",
"y-webrtc": "^10.2.3", "y-webrtc": "^10.2.3",
"yjs": "^13.5.41" "yjs": "^13.5.42"
} }
} }

10965
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,66 @@
import { createEffect, createMemo, createSignal, onMount, For } from 'solid-js'
import type { Shout } from '../../graphql/types.gen'
import { Soundwave } from './Soundwave'
type MediaItem = any
export default (props: { shout: Shout }) => {
const media = createMemo<any[]>(() => {
if (props.shout.media) {
console.debug(props.shout.media)
return [...JSON.parse(props.shout.media)]
}
return []
})
let audioRef: HTMLAudioElement
const [currentTrack, setCurrentTrack] = createSignal(media()[0])
const [paused, setPaused] = createSignal(true)
const togglePlayPause = () => setPaused(!paused())
const playMedia = (m: MediaItem) => {
audioRef.src = m.get('src')
audioRef.play()
}
const [audioContext, setAudioContext] = createSignal<AudioContext>()
onMount(() => setAudioContext(new AudioContext()))
createEffect(() => (paused() ? audioRef.play : audioRef.pause)())
return (
<div class="audio-container">
<div class="audio-img">
<img
class="ligthbox-img lazyload zoom-in"
width="320"
height="320"
alt={props.shout.title}
title={props.shout.title}
src={props.shout.cover}
/>
</div>
<div class="audio-player-list">
<div class="player current-track">
<div class="player-title">{currentTrack().title}</div>
<i class="fas fa-pause fa-3x fa-fw" onClick={togglePlayPause} />
<div class="player-progress">
<Soundwave context={audioContext()} url={currentTrack().src} />
<span class="track-position">{`${audioRef.currentTime} / ${audioRef.duration}`}</span>
</div>
<audio ref={audioRef} />
</div>
<ul class="all-tracks">
<For each={media()}>
{(m: MediaItem) => (
<li>
<div class="player-status">
<i class="fas fa-play fa-fw" onClick={() => playMedia(m)} />
</div>
<span class="track-title">{m.title}</span>
</li>
)}
</For>
</ul>
</div>
</div>
)
}

View File

@ -1,5 +1,5 @@
import './Comment.scss' import './Comment.scss'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import { AuthorCard } from '../Author/Card' import { AuthorCard } from '../Author/Card'
import { Show, createMemo } from 'solid-js' import { Show, createMemo } from 'solid-js'
import { clsx } from 'clsx' import { clsx } from 'clsx'

View File

@ -1,13 +1,12 @@
import { capitalize } from '../../utils' import { capitalize } from '../../utils'
import './Full.scss' import './Full.scss'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import ArticleComment from './Comment' import ArticleComment from './Comment'
import { AuthorCard } from '../Author/Card' import { AuthorCard } from '../Author/Card'
import { createMemo, For, onMount, Show } from 'solid-js' import { createMemo, For, onMount, Show } from 'solid-js'
import type { Author, Reaction, Shout } from '../../graphql/types.gen' import type { Author, Reaction, Shout } from '../../graphql/types.gen'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { showModal } from '../../stores/ui' import { showModal } from '../../stores/ui'
import { incrementView } from '../../stores/zine/articles'
import MD from './MD' import MD from './MD'
import { SharePopup } from './SharePopup' import { SharePopup } from './SharePopup'
import { useSession } from '../../context/session' import { useSession } from '../../context/session'
@ -39,11 +38,6 @@ const formatDate = (date: Date) => {
export const FullArticle = (props: ArticleProps) => { export const FullArticle = (props: ArticleProps) => {
const { session } = useSession() const { session } = useSession()
onMount(() => {
incrementView({ articleSlug: props.article.slug })
})
const formattedDate = createMemo(() => formatDate(new Date(props.article.createdAt))) const formattedDate = createMemo(() => formatDate(new Date(props.article.createdAt)))
const mainTopic = () => const mainTopic = () =>

View File

@ -1,9 +0,0 @@
export default (props: { src?: string; cover?: string; title?: string }) => {
// TODO: styling
return (
<div class="audio-track">
<audio src={props.src} controls={true} />
<span class="audio-title">{props.title || ''}</span>
</div>
)
}

View File

@ -1,4 +1,4 @@
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import styles from '../_shared/Popup.module.scss' import styles from '../_shared/Popup.module.scss'

View File

@ -0,0 +1,109 @@
import { onMount } from 'solid-js'
/**
* A utility function for drawing our line segments
* @param {AudioContext} ctx the audio context
* @param {number} x the x coordinate of the beginning of the line segment
* @param {number} height the desired height of the line segment
* @param {number} width the desired width of the line segment
* @param {boolean} isEven whether or not the segmented is even-numbered
*/
const drawLineSegment = (ctx, x, height, width, isEven) => {
ctx.lineWidth = 1 // how thick the line is
ctx.strokeStyle = '#fff' // what color our line is
ctx.beginPath()
const h = isEven ? height : -height
ctx.moveTo(x, 0)
ctx.lineTo(x, h)
ctx.arc(x + width / 2, h, width / 2, Math.PI, 0, isEven)
ctx.lineTo(x + width, 0)
ctx.stroke()
}
/**
* Filters the AudioBuffer retrieved from an external source
* @param {AudioBuffer} audioBuffer the AudioBuffer from drawAudio()
* @returns {Array} an array of floating point numbers
*/
const filterData = (audioBuffer) => {
const rawData = audioBuffer.getChannelData(0) // We only need to work with one channel of data
const samples = 70 // Number of samples we want to have in our final data set
const blockSize = Math.floor(rawData.length / samples) // the number of samples in each subdivision
const filteredData = []
for (let i = 0; i < samples; i++) {
const blockStart = blockSize * i // the location of the first sample in the block
let sum = 0
for (let j = 0; j < blockSize; j++) {
sum = sum + Math.abs(rawData[blockStart + j]) // find the sum of all the samples in the block
}
filteredData.push(sum / blockSize) // divide the sum by the block size to get the average
}
return filteredData
}
/**
* Normalizes the audio data to make a cleaner illustration
* @param {Array} filteredData the data from filterData()
* @returns {Array} an normalized array of floating point numbers
*/
const normalizeData = (filteredData) => {
const multiplier = Math.pow(Math.max(...filteredData), -1)
return filteredData.map((n) => n * multiplier)
}
interface SoundwaveProps {
url: string
context: AudioContext
}
export const Soundwave = (props: SoundwaveProps) => {
let canvasRef: HTMLCanvasElement
/**
* Draws the audio file into a canvas element.
* @param {Array} normalizedData The filtered array returned from filterData()
* @returns {Array} a normalized array of data
*/
const draw = (normalizedData) => {
// set up the canvas
const canvas = canvasRef
const dpr = window.devicePixelRatio || 1
const padding = 20
canvas.width = canvas.offsetWidth * dpr
canvas.height = (canvas.offsetHeight + padding * 2) * dpr
const ctx = canvas.getContext('2d')
ctx.scale(dpr, dpr)
ctx.translate(0, canvas.offsetHeight / 2 + padding) // set Y = 0 to be in the middle of the canvas
// draw the line segments
const width = canvas.offsetWidth / normalizedData.length
// eslint-disable-next-line unicorn/no-for-loop
for (let i = 0; i < normalizedData.length; i++) {
const x = width * i
let height = normalizedData[i] * canvas.offsetHeight - padding
if (height < 0) {
height = 0
} else if (height > canvas.offsetHeight / 2) {
height = height - canvas.offsetHeight / 2
}
drawLineSegment(ctx, x, height, width, (i + 1) % 2)
}
}
/**
* Retrieves audio from an external source, the initializes the drawing function
* @param {AudioContext} audioContext the audio context
* @param {String} url the url of the audio we'd like to fetch
*/
const drawAudio = (audioContext, url) => {
fetch(url)
.then((response) => response.arrayBuffer())
.then((arrayBuffer) => audioContext.decodeAudioData(arrayBuffer))
.then((audioBuffer) => draw(normalizeData(filterData(audioBuffer))))
.catch(console.error)
}
onMount(() => {
drawAudio(props.context, props.url)
})
return <canvas ref={canvasRef} />
}

View File

@ -1,6 +1,6 @@
import type { Author } from '../../graphql/types.gen' import type { Author } from '../../graphql/types.gen'
import Userpic from './Userpic' import Userpic from './Userpic'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import styles from './Card.module.scss' import styles from './Card.module.scss'
import { createMemo, For, Show } from 'solid-js' import { createMemo, For, Show } from 'solid-js'
import { translit } from '../../utils/ru2en' import { translit } from '../../utils/ru2en'

View File

@ -20,7 +20,7 @@ export const Donate = () => {
const [period, setPeriod] = createSignal(monthly) const [period, setPeriod] = createSignal(monthly)
const [amount, setAmount] = createSignal(0) const [amount, setAmount] = createSignal(0)
onMount(() => { const initiated = () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const { const {
cp: { CloudPayments } cp: { CloudPayments }
@ -51,6 +51,16 @@ export const Donate = () => {
provision: 0 // Сумма оплаты встречным предоставлением (сертификаты, др. мат.ценности) (2 знака после запятой) provision: 0 // Сумма оплаты встречным предоставлением (сертификаты, др. мат.ценности) (2 знака после запятой)
} }
}) })
}
onMount(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = 'https://widget.cloudpayments.ru/bundles/cloudpayments.js'
script.async = true
script.addEventListener('load', initiated)
document.head.appendChild(script)
}) })
const show = () => { const show = () => {
@ -103,7 +113,6 @@ export const Donate = () => {
} }
return ( return (
<>
<form class="discours-form donate-form" action="" method="post"> <form class="discours-form donate-form" action="" method="post">
<input type="hidden" name="shopId" value="156465" /> <input type="hidden" name="shopId" value="156465" />
<input value="148805" name="scid" type="hidden" /> <input value="148805" name="scid" type="hidden" />
@ -167,6 +176,5 @@ export const Donate = () => {
</a> </a>
</div> </div>
</form> </form>
</>
) )
} }

View File

@ -1,6 +1,6 @@
import { createMemo, For } from 'solid-js' import { createMemo, For } from 'solid-js'
import styles from './Footer.module.scss' import styles from './Footer.module.scss'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import Subscribe from './Subscribe' import Subscribe from './Subscribe'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { locale } from '../../stores/ui' import { locale } from '../../stores/ui'

View File

@ -6,7 +6,7 @@ import { AuthorCard } from '../Author/Card'
import { TopicCard } from '../Topic/Card' import { TopicCard } from '../Topic/Card'
import styles from './Beside.module.scss' import styles from './Beside.module.scss'
import type { Author, Shout, Topic, User } from '../../graphql/types.gen' import type { Author, Shout, Topic, User } from '../../graphql/types.gen'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { clsx } from 'clsx' import { clsx } from 'clsx'

View File

@ -3,7 +3,7 @@ import { createMemo, For, Show } from 'solid-js'
import type { Shout } from '../../graphql/types.gen' import type { Shout } from '../../graphql/types.gen'
import { capitalize } from '../../utils' import { capitalize } from '../../utils'
import { translit } from '../../utils/ru2en' import { translit } from '../../utils/ru2en'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import styles from './Card.module.scss' import styles from './Card.module.scss'
import { locale } from '../../stores/ui' import { locale } from '../../stores/ui'
import { handleClientRouteLinkClick } from '../../stores/router' import { handleClientRouteLinkClick } from '../../stores/router'
@ -98,7 +98,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
<div class={styles.shoutCardContent}> <div class={styles.shoutCardContent}>
<Show when={layout && layout !== 'article' && !(props.settings?.noicon || props.settings?.noimage)}> <Show when={layout && layout !== 'article' && !(props.settings?.noicon || props.settings?.noimage)}>
<div class={styles.shoutCardType}> <div class={styles.shoutCardType}>
<a href={`/topic/${mainTopic.slug}`}> <a href={`/expo/${layout}`}>
<Icon name={layout} class={styles.icon} /> <Icon name={layout} class={styles.icon} />
</a> </a>
</div> </div>

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={[...Array.from({ length: 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

@ -2,7 +2,7 @@ import { For } from 'solid-js'
import type { Author } from '../../graphql/types.gen' import type { Author } from '../../graphql/types.gen'
import { useAuthorsStore } from '../../stores/zine/authors' import { useAuthorsStore } from '../../stores/zine/authors'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import { useTopicsStore } from '../../stores/zine/topics' import { useTopicsStore } from '../../stores/zine/topics'
import { useArticlesStore } from '../../stores/zine/articles' import { useArticlesStore } from '../../stores/zine/articles'
import { useSeenStore } from '../../stores/zine/seen' import { useSeenStore } from '../../stores/zine/seen'
@ -13,7 +13,7 @@ type FeedSidebarProps = {
} }
export const FeedSidebar = (props: FeedSidebarProps) => { export const FeedSidebar = (props: FeedSidebarProps) => {
const { getSeen: seen } = useSeenStore() const { seen } = useSeenStore()
const { session } = useSession() const { session } = useSession()
const { authorEntities } = useAuthorsStore({ authors: props.authors }) const { authorEntities } = useAuthorsStore({ authors: props.authors })
const { articlesByTopic } = useArticlesStore() const { articlesByTopic } = useArticlesStore()

View File

@ -7,7 +7,7 @@ import 'swiper/scss/pagination'
import './Slider.scss' import './Slider.scss'
import type { Shout } from '../../graphql/types.gen' import type { Shout } from '../../graphql/types.gen'
import { createEffect, createMemo, createSignal, Show, For } from 'solid-js' import { createEffect, createMemo, createSignal, Show, For } from 'solid-js'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
interface SliderProps { interface SliderProps {
title?: string title?: string

View File

@ -0,0 +1,35 @@
.DialogAvatar {
width: 40px;
height: 40px;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
&.online::before {
content: '';
position: absolute;
background: #2bb452;
width: 8px;
height: 8px;
top: -2px;
right: -2px;
border-radius: 50%;
border: 3px solid #fff;
}
> img,
> .letter {
display: block;
border-radius: 100%;
}
> .letter {
margin-bottom: -2px;
font-weight: 500;
font-size: 18px;
line-height: 10px;
color: #fff;
}
}

View File

@ -0,0 +1,48 @@
import { Show, createMemo } from 'solid-js'
import './DialogCard.module.scss'
import styles from './DialogAvatar.module.scss'
import { clsx } from 'clsx'
type Props = {
url: string
name: string
online?: boolean
}
const colors = [
'#001219',
'#005f73',
'#0a9396',
'#94d2bd',
'#ee9b00',
'#ca6702',
'#ae2012',
'#9b2226',
'#668cff',
'#c34cfe',
'#e699ff',
'#6633ff'
]
const getById = (letter: string) =>
colors[Math.abs(Number(BigInt(letter.toLowerCase().codePointAt(0) - 97) % BigInt(colors.length)))]
const DialogAvatar = (props: Props) => {
const nameFirstLetter = props.name.slice(0, 1)
const randomBg = createMemo(() => {
return getById(nameFirstLetter)
})
return (
<div
class={clsx(styles.DialogAvatar, props.online && styles.online)}
style={{ 'background-color': `${randomBg()}` }}
>
<Show when={props.url} fallback={() => <div class={styles.letter}>{nameFirstLetter}</div>}>
<img src={props.url} alt={props.name} />
</Show>
</div>
)
}
export default DialogAvatar

View File

@ -0,0 +1,70 @@
.DialogCard {
display: inline-flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
font-size: 14px;
padding: 12px;
transition: background 0.3s ease-in-out;
cursor: pointer;
&:hover {
background: #f7f7f7;
}
.avatar {
flex-basis: 40px;
margin-right: 12px;
}
.row {
flex-basis: 0;
flex-grow: 1;
min-width: 0;
.name,
.message {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.name {
color: #141414;
font-weight: 500;
}
.message {
color: #9fa1a7;
}
}
.activity {
font-size: 12px;
margin-left: 12px;
.time {
text-align: right;
color: #ccc;
}
.counter {
display: flex;
margin-left: auto;
align-items: center;
justify-content: center;
border-radius: 12px;
padding: 0 8px;
background: #d00820;
font-weight: 400;
color: #fff;
width: 22px;
height: 22px;
line-height: 6px;
> span {
margin-bottom: -2px;
}
}
}
}

View File

@ -0,0 +1,56 @@
import styles from './DialogCard.module.scss'
import DialogAvatar from './DialogAvatar'
import type { Author, AuthResult } from '../../graphql/types.gen'
import { useSession } from '../../context/session'
import { createMemo } from 'solid-js'
import { apiClient } from '../../utils/apiClient'
type DialogProps = {
online?: boolean
message?: string
counter?: number
author?: Author
}
const createChat = async ({ title, members }: { title?: string; members?: string[] }): Promise<void> => {
await apiClient.createChat({ title, members })
}
const DialogCard = (props: DialogProps) => {
const { session } = useSession()
const currentSession = createMemo<AuthResult>(() => session)
const handleOpenChat = async () => {
try {
const initChat = await apiClient.createChat({
title: 'test chat',
members: [props.author.slug, currentSession().user.slug]
})
// console.log('!!! test:', test)
} catch (error) {
console.log('!!! errr:', error)
}
}
return (
<div class={styles.DialogCard} onClick={handleOpenChat}>
<div class={styles.avatar}>
<DialogAvatar name={props.author.name} url={props.author.userpic} online={props.online} />
</div>
<div class={styles.row}>
<div class={styles.name}>{props.author.name}</div>
<div class={styles.message}>
Указать предпочтительные языки для результатов поиска можно в разделе
</div>
</div>
<div class={styles.activity}>
<div class={styles.time}>22:22</div>
<div class={styles.counter}>
<span>12</span>
</div>
</div>
</div>
)
}
export default DialogCard

View File

@ -0,0 +1,7 @@
.Message {
.own {
}
.body {
// message text
}
}

View File

@ -0,0 +1,17 @@
import { clsx } from 'clsx'
import styles from './Message.module.scss'
type Props = {
body: string
isOwn: boolean
}
const Message = (props: Props) => {
return (
<div class={clsx(styles.Message, props.isOwn && styles.own)}>
<div class={styles.body}>{props.body}</div>
</div>
)
}
export default Message

View File

@ -0,0 +1,46 @@
.Search {
.field {
position: relative;
background: #fff;
border: 2px solid #e8e8e8;
border-radius: 2px;
overflow: hidden;
input {
display: block;
height: 40px;
border: none;
box-shadow: none;
padding: 10px 36px 10px 12px;
width: 100%;
font-family: Muller, Arial, Helvetica, sans-serif;
font-style: normal;
font-weight: 400;
font-size: 15px;
&::placeholder {
color: #858585;
font-family: inherit;
}
&:focus {
outline: none;
& + .icon {
opacity: 0;
right: -30px;
}
}
}
.icon {
transition: 0.3s ease-in-out;
position: absolute;
width: 16px;
height: 16px;
top: 12px;
right: 12px;
opacity: 0.5;
}
}
}

View File

@ -0,0 +1,27 @@
import styles from './Search.module.scss'
import { createSignal } from 'solid-js'
import { Icon } from '../_shared/Icon'
type Props = {
placeholder: string
onChange: (value: () => string) => void
}
const Search = (props: Props) => {
const [value, setValue] = createSignal<string>('')
const search = (event) => {
event.preventDefault()
setValue(event.target.value)
props.onChange(value)
}
return (
<div class={styles.Search}>
<div class={styles.field}>
<input type="text" onInput={search} placeholder={props.placeholder} />
<Icon name="search" class={styles.icon} />
</div>
</div>
)
}
export default Search

View File

@ -1,5 +1,5 @@
import { t } from '../../../utils/intl' import { t } from '../../../utils/intl'
import { Icon } from '../Icon' import { Icon } from '../../_shared/Icon'
import { hideModal } from '../../../stores/ui' import { hideModal } from '../../../stores/ui'
import styles from './SocialProviders.module.scss' import styles from './SocialProviders.module.scss'

View File

@ -1,5 +1,5 @@
import { For, Show, createSignal, createEffect, onMount, onCleanup } from 'solid-js' import { For, Show, createSignal, createEffect, onMount, onCleanup } from 'solid-js'
import { Icon } from './Icon' import { Icon } from '../_shared/Icon'
import { Modal } from './Modal' import { Modal } from './Modal'
import { AuthModal } from './AuthModal' import { AuthModal } from './AuthModal'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'

View File

@ -2,8 +2,8 @@ import styles from './Header.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { handleClientRouteLinkClick, useRouter } from '../../stores/router' import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { Icon } from './Icon' import { Icon } from '../_shared/Icon'
import { createSignal, onMount, Show } from 'solid-js' import { createSignal, Show } from 'solid-js'
import Notifications from './Notifications' import Notifications from './Notifications'
import { ProfilePopup } from './ProfilePopup' import { ProfilePopup } from './ProfilePopup'
import Userpic from '../Author/Userpic' import Userpic from '../Author/Userpic'

View File

@ -1,6 +1,6 @@
import { For, Show } from 'solid-js' import { For, Show } from 'solid-js'
import type { Topic } from '../../graphql/types.gen' import type { Topic } from '../../graphql/types.gen'
import { Icon } from './Icon' import { Icon } from '../_shared/Icon'
import './Topics.scss' import './Topics.scss'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { locale } from '../../stores/ui' import { locale } from '../../stores/ui'

View File

@ -1,4 +1,4 @@
import { MainLayout } from '../Layouts/MainLayout' import { PageWrap } from '../_shared/PageWrap'
import { AllAuthorsView } from '../Views/AllAuthors' import { AllAuthorsView } from '../Views/AllAuthors'
import type { PageProps } from '../types' import type { PageProps } from '../types'
import { createSignal, onMount, Show } from 'solid-js' import { createSignal, onMount, Show } from 'solid-js'
@ -18,11 +18,11 @@ export const AllAuthorsPage = (props: PageProps) => {
}) })
return ( return (
<MainLayout> <PageWrap>
<Show when={isLoaded()} fallback={<Loading />}> <Show when={isLoaded()} fallback={<Loading />}>
<AllAuthorsView authors={props.allAuthors} /> <AllAuthorsView authors={props.allAuthors} />
</Show> </Show>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,4 +1,4 @@
import { MainLayout } from '../Layouts/MainLayout' import { PageWrap } from '../_shared/PageWrap'
import { AllTopicsView } from '../Views/AllTopics' import { AllTopicsView } from '../Views/AllTopics'
import type { PageProps } from '../types' import type { PageProps } from '../types'
import { createSignal, onMount, Show } from 'solid-js' import { createSignal, onMount, Show } from 'solid-js'
@ -18,11 +18,11 @@ export const AllTopicsPage = (props: PageProps) => {
}) })
return ( return (
<MainLayout> <PageWrap>
<Show when={isLoaded()} fallback={<Loading />}> <Show when={isLoaded()} fallback={<Loading />}>
<AllTopicsView topics={props.allTopics} /> <AllTopicsView topics={props.allTopics} />
</Show> </Show>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,14 +1,14 @@
import { MainLayout } from '../Layouts/MainLayout' import { PageWrap } from '../_shared/PageWrap'
import { ArticleView } from '../Views/Article' import { ArticleView } from '../Views/Article'
import type { PageProps } from '../types' import type { PageProps } from '../types'
import { loadArticle, useArticlesStore } from '../../stores/zine/articles' import { loadShoutsBy, useArticlesStore } from '../../stores/zine/articles'
import { createMemo, onMount, Show } from 'solid-js' import { createMemo, onMount, Show } from 'solid-js'
import type { Shout } from '../../graphql/types.gen' import type { Shout } from '../../graphql/types.gen'
import { useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
import { Loading } from '../Loading' import { Loading } from '../Loading'
export const ArticlePage = (props: PageProps) => { export const ArticlePage = (props: PageProps) => {
const sortedArticles = props.article ? [props.article] : [] const shouts = props.article ? [props.article] : []
const slug = createMemo(() => { const slug = createMemo(() => {
const { page: getPage } = useRouter() const { page: getPage } = useRouter()
@ -23,25 +23,25 @@ export const ArticlePage = (props: PageProps) => {
}) })
const { articleEntities } = useArticlesStore({ const { articleEntities } = useArticlesStore({
sortedArticles shouts
}) })
const article = createMemo<Shout>(() => articleEntities()[slug()]) const article = createMemo<Shout>(() => articleEntities()[slug()])
onMount(() => { onMount(async () => {
const articleValue = articleEntities()[slug()] const articleValue = articleEntities()[slug()]
if (!articleValue || !articleValue.body) { if (!articleValue || !articleValue.body) {
loadArticle({ slug: slug() }) await loadShoutsBy({ by: { slug: slug() }, limit: 1, offset: 0 })
} }
}) })
return ( return (
<MainLayout headerTitle={article()?.title || ''}> <PageWrap headerTitle={article()?.title || ''}>
<Show when={Boolean(article())} fallback={<Loading />}> <Show when={Boolean(article())} fallback={<Loading />}>
<ArticleView article={article()} /> <ArticleView article={article()} />
</Show> </Show>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,14 +1,14 @@
import { MainLayout } from '../Layouts/MainLayout' import { PageWrap } from '../_shared/PageWrap'
import { AuthorView, PRERENDERED_ARTICLES_COUNT } from '../Views/Author' import { AuthorView, PRERENDERED_ARTICLES_COUNT } from '../Views/Author'
import type { PageProps } from '../types' import type { PageProps } from '../types'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js' import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { loadAuthorArticles, resetSortedArticles } from '../../stores/zine/articles' import { loadShoutsBy, resetSortedArticles } from '../../stores/zine/articles'
import { useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
import { loadAuthor } from '../../stores/zine/authors' import { loadAuthor } from '../../stores/zine/authors'
import { Loading } from '../Loading' import { Loading } from '../Loading'
export const AuthorPage = (props: PageProps) => { export const AuthorPage = (props: PageProps) => {
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.authorArticles) && Boolean(props.author)) const [isLoaded, setIsLoaded] = createSignal(Boolean(props.shouts) && Boolean(props.author))
const slug = createMemo(() => { const slug = createMemo(() => {
const { page: getPage } = useRouter() const { page: getPage } = useRouter()
@ -27,7 +27,7 @@ export const AuthorPage = (props: PageProps) => {
return return
} }
await loadAuthorArticles({ authorSlug: slug(), limit: PRERENDERED_ARTICLES_COUNT }) await loadShoutsBy({ by: { author: slug() }, limit: PRERENDERED_ARTICLES_COUNT })
await loadAuthor({ slug: slug() }) await loadAuthor({ slug: slug() })
setIsLoaded(true) setIsLoaded(true)
@ -36,11 +36,11 @@ export const AuthorPage = (props: PageProps) => {
onCleanup(() => resetSortedArticles()) onCleanup(() => resetSortedArticles())
return ( return (
<MainLayout> <PageWrap>
<Show when={isLoaded()} fallback={<Loading />}> <Show when={isLoaded()} fallback={<Loading />}>
<AuthorView author={props.author} authorArticles={props.authorArticles} authorSlug={slug()} /> <AuthorView author={props.author} shouts={props.shouts} authorSlug={slug()} />
</Show> </Show>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,8 +1,8 @@
import { MainLayout } from '../Layouts/MainLayout' import { PageWrap } from '../_shared/PageWrap'
export const ConnectPage = () => { export const ConnectPage = () => {
return ( return (
<MainLayout> <PageWrap>
<article class="container container--static-page"> <article class="container container--static-page">
<div class="row"> <div class="row">
<div class="col-sm-10 col-md-8 col-lg-7 col-xl-6 shift-content"> <div class="col-sm-10 col-md-8 col-lg-7 col-xl-6 shift-content">
@ -39,7 +39,7 @@ export const ConnectPage = () => {
</div> </div>
</div> </div>
</article> </article>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,16 +1,16 @@
import { lazy, Suspense } from 'solid-js' import { lazy, Suspense } from 'solid-js'
import { MainLayout } from '../Layouts/MainLayout' import { PageWrap } from '../_shared/PageWrap'
import { Loading } from '../Loading' import { Loading } from '../Loading'
const CreateView = lazy(() => import('../Views/Create')) const CreateView = lazy(() => import('../Views/Create'))
export const CreatePage = () => { export const CreatePage = () => {
return ( return (
<MainLayout> <PageWrap>
<Suspense fallback={<Loading />}> <Suspense fallback={<Loading />}>
<CreateView /> <CreateView />
</Suspense> </Suspense>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,4 +1,4 @@
import { MainLayout } from '../Layouts/MainLayout' import { PageWrap } from '../_shared/PageWrap'
import { FeedView } from '../Views/Feed' import { FeedView } from '../Views/Feed'
import { onCleanup } from 'solid-js' import { onCleanup } from 'solid-js'
import { resetSortedArticles } from '../../stores/zine/articles' import { resetSortedArticles } from '../../stores/zine/articles'
@ -7,9 +7,9 @@ export const FeedPage = () => {
onCleanup(() => resetSortedArticles()) onCleanup(() => resetSortedArticles())
return ( return (
<MainLayout> <PageWrap>
<FeedView /> <FeedView />
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,11 +1,11 @@
import { FourOuFourView } from '../Views/FourOuFour' import { FourOuFourView } from '../Views/FourOuFour'
import { MainLayout } from '../Layouts/MainLayout' import { PageWrap } from '../_shared/PageWrap'
export const FourOuFourPage = () => { export const FourOuFourPage = () => {
return ( return (
<MainLayout isHeaderFixed={false} hideFooter={true}> <PageWrap isHeaderFixed={false} hideFooter={true}>
<FourOuFourView /> <FourOuFourView />
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,21 +1,21 @@
import { HomeView, PRERENDERED_ARTICLES_COUNT } from '../Views/Home' import { HomeView, PRERENDERED_ARTICLES_COUNT } from '../Views/Home'
import { MainLayout } from '../Layouts/MainLayout' import { PageWrap } from '../_shared/PageWrap'
import type { PageProps } from '../types' import type { PageProps } from '../types'
import { createSignal, onCleanup, onMount, Show } from 'solid-js' import { createSignal, onCleanup, onMount, Show } from 'solid-js'
import { loadPublishedArticles, resetSortedArticles } from '../../stores/zine/articles' import { loadShoutsBy, resetSortedArticles } from '../../stores/zine/articles'
import { loadRandomTopics } from '../../stores/zine/topics' import { loadRandomTopics } from '../../stores/zine/topics'
import { Loading } from '../Loading' import { Loading } from '../Loading'
import styles from './HomePage.module.scss' import styles from './HomePage.module.scss'
export const HomePage = (props: PageProps) => { export const HomePage = (props: PageProps) => {
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.homeArticles) && Boolean(props.randomTopics)) const [isLoaded, setIsLoaded] = createSignal(Boolean(props.shouts) && Boolean(props.randomTopics))
onMount(async () => { onMount(async () => {
if (isLoaded()) { if (isLoaded()) {
return return
} }
await loadPublishedArticles({ limit: PRERENDERED_ARTICLES_COUNT, offset: 0 }) await loadShoutsBy({ by: { visibility: 'public' }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
await loadRandomTopics() await loadRandomTopics()
setIsLoaded(true) setIsLoaded(true)
@ -24,11 +24,11 @@ export const HomePage = (props: PageProps) => {
onCleanup(() => resetSortedArticles()) onCleanup(() => resetSortedArticles())
return ( return (
<MainLayout class={styles.mainContent}> <PageWrap class={styles.mainContent}>
<Show when={isLoaded()} fallback={<Loading />}> <Show when={isLoaded()} fallback={<Loading />}>
<HomeView randomTopics={props.randomTopics} recentPublishedArticles={props.homeArticles || []} /> <HomeView randomTopics={props.randomTopics} shouts={props.shouts || []} />
</Show> </Show>
</MainLayout> </PageWrap>
) )
} }

View File

@ -0,0 +1,14 @@
import { PageWrap } from '../_shared/PageWrap'
import { InboxView } from '../Views/Inbox'
import type { PageProps } from '../types'
export const InboxPage = (props: PageProps) => {
return (
<PageWrap>
<InboxView />
</PageWrap>
)
}
// for lazy loading
export default InboxPage

View File

@ -0,0 +1,148 @@
import { PageWrap } from '../_shared/PageWrap'
import type { PageProps } from '../types'
import { createMemo, createSignal, For, onCleanup, onMount, Show } from 'solid-js'
import { loadShoutsBy, resetSortedArticles } from '../../stores/zine/articles'
import { useRouter } from '../../stores/router'
import { LayoutType, useLayoutsStore } from '../../stores/zine/layouts'
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'
export const PRERENDERED_ARTICLES_COUNT = 21
const LOAD_MORE_PAGE_SIZE = 9 // Row3 + Row3 + Row3
export const LayoutShoutsPage = (props: PageProps) => {
const layout = createMemo<LayoutType>(() => {
const { page: getPage } = useRouter()
const page = getPage()
if (page.route !== 'expo') throw new Error('ts guard')
return page.params.layout as LayoutType
})
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const { sortedLayoutShouts, loadLayoutShoutsBy } = useLayoutsStore(layout(), props.shouts)
const sortedArticles = createMemo<Shout[]>(() => sortedLayoutShouts().get(layout()) || [])
const loadMoreLayout = async (kind: LayoutType) => {
saveScrollPosition()
const { hasMore } = await loadLayoutShoutsBy({
by: { layout: kind },
limit: 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 isLoaded = createMemo(() => Boolean(sortedArticles()))
onMount(async () => {
if (!isLoaded()) {
await loadShoutsBy({ by: { layout: layout() }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
}
})
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 (
<PageWrap>
<Show when={isLoaded()} fallback={<Loading />}>
<div class={styles.topicPage}>
<Show when={layout() && Boolean(sortedArticles())}>
<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>
</PageWrap>
)
}
// for lazy loading
export default LayoutShoutsPage

View File

@ -1,8 +1,8 @@
import { MainLayout } from '../Layouts/MainLayout' import { PageWrap } from '../_shared/PageWrap'
import { SearchView } from '../Views/Search' import { SearchView } from '../Views/Search'
import type { PageProps } from '../types' import type { PageProps } from '../types'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js' import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { loadSearchResults, resetSortedArticles } from '../../stores/zine/articles' import { loadShoutsBy, resetSortedArticles } from '../../stores/zine/articles'
import { useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
import { Loading } from '../Loading' import { Loading } from '../Loading'
@ -26,18 +26,18 @@ export const SearchPage = (props: PageProps) => {
return return
} }
await loadSearchResults({ query: q(), limit: 50, offset: 0 }) await loadShoutsBy({ by: { title: q(), body: q() }, limit: 50, offset: 0 })
setIsLoaded(true) setIsLoaded(true)
}) })
onCleanup(() => resetSortedArticles()) onCleanup(() => resetSortedArticles())
return ( return (
<MainLayout> <PageWrap>
<Show when={isLoaded()} fallback={<Loading />}> <Show when={isLoaded()} fallback={<Loading />}>
<SearchView results={props.searchResults || []} query={props.searchQuery} /> <SearchView results={props.searchResults || []} query={props.searchQuery} />
</Show> </Show>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,14 +1,14 @@
import { MainLayout } from '../Layouts/MainLayout' import { PageWrap } from '../_shared/PageWrap'
import { PRERENDERED_ARTICLES_COUNT, TopicView } from '../Views/Topic' import { PRERENDERED_ARTICLES_COUNT, TopicView } from '../Views/Topic'
import type { PageProps } from '../types' import type { PageProps } from '../types'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js' import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { loadTopicArticles, resetSortedArticles } from '../../stores/zine/articles' import { loadShoutsBy, resetSortedArticles } from '../../stores/zine/articles'
import { useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
import { loadTopic } from '../../stores/zine/topics' import { loadTopic } from '../../stores/zine/topics'
import { Loading } from '../Loading' import { Loading } from '../Loading'
export const TopicPage = (props: PageProps) => { export const TopicPage = (props: PageProps) => {
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.topicArticles) && Boolean(props.topic)) const [isLoaded, setIsLoaded] = createSignal(Boolean(props.shouts) && Boolean(props.topic))
const slug = createMemo(() => { const slug = createMemo(() => {
const { page: getPage } = useRouter() const { page: getPage } = useRouter()
@ -27,7 +27,7 @@ export const TopicPage = (props: PageProps) => {
return return
} }
await loadTopicArticles({ topicSlug: slug(), limit: PRERENDERED_ARTICLES_COUNT, offset: 0 }) await loadShoutsBy({ by: { topics: [slug()] }, limit: PRERENDERED_ARTICLES_COUNT, offset: 0 })
await loadTopic({ slug: slug() }) await loadTopic({ slug: slug() })
setIsLoaded(true) setIsLoaded(true)
@ -36,11 +36,11 @@ export const TopicPage = (props: PageProps) => {
onCleanup(() => resetSortedArticles()) onCleanup(() => resetSortedArticles())
return ( return (
<MainLayout> <PageWrap>
<Show when={isLoaded()} fallback={<Loading />}> <Show when={isLoaded()} fallback={<Loading />}>
<TopicView topic={props.topic} topicArticles={props.topicArticles} topicSlug={slug()} /> <TopicView topic={props.topic} shouts={props.shouts} topicSlug={slug()} />
</Show> </Show>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,10 +1,10 @@
import { MainLayout } from '../../Layouts/MainLayout' import { PageWrap } from '../../_shared/PageWrap'
import { t } from '../../../utils/intl' import { t } from '../../../utils/intl'
export const DiscussionRulesPage = () => { export const DiscussionRulesPage = () => {
const title = t('Discussion rules') const title = t('Discussion rules')
return ( return (
<MainLayout> <PageWrap>
<article class="container container--static-page"> <article class="container container--static-page">
<div class="row"> <div class="row">
<div class="col-md-6 col-xl-7 shift-content order-md-first"> <div class="col-md-6 col-xl-7 shift-content order-md-first">
@ -114,7 +114,7 @@ export const DiscussionRulesPage = () => {
</div> </div>
</div> </div>
</article> </article>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,10 +1,10 @@
import { MainLayout } from '../../Layouts/MainLayout' import { PageWrap } from '../../_shared/PageWrap'
// const title = t('Dogma') // const title = t('Dogma')
export const DogmaPage = () => { export const DogmaPage = () => {
return ( return (
<MainLayout> <PageWrap>
<article class="container container--static-page"> <article class="container container--static-page">
<div class="row"> <div class="row">
<div class="col-md-6 col-xl-7 shift-content order-md-first"> <div class="col-md-6 col-xl-7 shift-content order-md-first">
@ -53,7 +53,7 @@ export const DogmaPage = () => {
</div> </div>
</div> </div>
</article> </article>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,7 +1,7 @@
import { createSignal, Show } from 'solid-js' import { createSignal, Show } from 'solid-js'
import { MainLayout } from '../../Layouts/MainLayout' import { PageWrap } from '../../_shared/PageWrap'
import { t } from '../../../utils/intl' import { t } from '../../../utils/intl'
import { Icon } from '../../Nav/Icon' import { Icon } from '../../_shared/Icon'
export const GuidePage = () => { export const GuidePage = () => {
const title = t('How it works') const title = t('How it works')
@ -11,7 +11,7 @@ export const GuidePage = () => {
const toggleIndexExpanded = () => setIndexExpanded((oldExpanded) => !oldExpanded) const toggleIndexExpanded = () => setIndexExpanded((oldExpanded) => !oldExpanded)
return ( return (
<MainLayout> <PageWrap>
{/*<Meta name="description" content={title} />*/} {/*<Meta name="description" content={title} />*/}
{/*<Meta name="keywords" content={t('Discours') + ',' + title} />*/} {/*<Meta name="keywords" content={t('Discours') + ',' + title} />*/}
{/*<Meta property="og:title" content={title} />*/} {/*<Meta property="og:title" content={title} />*/}
@ -283,7 +283,7 @@ export const GuidePage = () => {
</div> </div>
</div> </div>
</article> </article>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,7 +1,7 @@
import { createSignal, Show } from 'solid-js' import { createSignal, Show } from 'solid-js'
import { MainLayout } from '../../Layouts/MainLayout' import { PageWrap } from '../../_shared/PageWrap'
import { Donate } from '../../Discours/Donate' import { Donate } from '../../Discours/Donate'
import { Icon } from '../../Nav/Icon' import { Icon } from '../../_shared/Icon'
// const title = t('Support us') // const title = t('Support us')
@ -11,7 +11,7 @@ export const HelpPage = () => {
const toggleIndexExpanded = () => setIndexExpanded((oldExpanded) => !oldExpanded) const toggleIndexExpanded = () => setIndexExpanded((oldExpanded) => !oldExpanded)
return ( return (
<MainLayout> <PageWrap>
{/*<Meta name="description">Здесь можно поддержать Дискурс материально.</Meta>*/} {/*<Meta name="description">Здесь можно поддержать Дискурс материально.</Meta>*/}
{/*<Meta name="keywords">Discours.io, помощь, благотворительность</Meta>*/} {/*<Meta name="keywords">Discours.io, помощь, благотворительность</Meta>*/}
@ -161,7 +161,7 @@ export const HelpPage = () => {
</div> </div>
</div> </div>
</article> </article>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,10 +1,10 @@
import { createSignal, Show } from 'solid-js' import { createSignal, Show } from 'solid-js'
import { MainLayout } from '../../Layouts/MainLayout' import { PageWrap } from '../../_shared/PageWrap'
import { Modal } from '../../Nav/Modal' import { Modal } from '../../Nav/Modal'
import { Feedback } from '../../Discours/Feedback' import { Feedback } from '../../Discours/Feedback'
import Subscribe from '../../Discours/Subscribe' import Subscribe from '../../Discours/Subscribe'
import Opener from '../../Nav/Opener' import Opener from '../../Nav/Opener'
import { Icon } from '../../Nav/Icon' import { Icon } from '../../_shared/Icon'
// title={t('Manifest')} // title={t('Manifest')}
@ -14,7 +14,7 @@ export const ManifestPage = () => {
const toggleIndexExpanded = () => setIndexExpanded((oldExpanded) => !oldExpanded) const toggleIndexExpanded = () => setIndexExpanded((oldExpanded) => !oldExpanded)
return ( return (
<MainLayout> <PageWrap>
<Modal name="feedback"> <Modal name="feedback">
<Feedback /> <Feedback />
</Modal> </Modal>
@ -191,7 +191,7 @@ export const ManifestPage = () => {
</div> </div>
</div> </div>
</article> </article>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,11 +1,11 @@
import { MainLayout } from '../../Layouts/MainLayout' import { PageWrap } from '../../_shared/PageWrap'
import { t } from '../../../utils/intl' import { t } from '../../../utils/intl'
// const title = t('Partners') // const title = t('Partners')
export const PartnersPage = () => { export const PartnersPage = () => {
return ( return (
<MainLayout> <PageWrap>
<article class="container container--static-page"> <article class="container container--static-page">
<div class="row"> <div class="row">
<div class="col-md-6 col-xl-7 shift-content order-md-first"> <div class="col-md-6 col-xl-7 shift-content order-md-first">
@ -13,7 +13,7 @@ export const PartnersPage = () => {
</div> </div>
</div> </div>
</article> </article>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,10 +1,10 @@
import { MainLayout } from '../../Layouts/MainLayout' import { PageWrap } from '../../_shared/PageWrap'
import { t } from '../../../utils/intl' import { t } from '../../../utils/intl'
export const PrinciplesPage = () => { export const PrinciplesPage = () => {
const title = t('Principles') const title = t('Principles')
return ( return (
<MainLayout> <PageWrap>
<article class="container container--static-page"> <article class="container container--static-page">
<div class="row"> <div class="row">
<div class="col-md-6 col-xl-7 shift-content order-md-first"> <div class="col-md-6 col-xl-7 shift-content order-md-first">
@ -172,7 +172,7 @@ export const PrinciplesPage = () => {
</div> </div>
</div> </div>
</article> </article>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,11 +1,11 @@
import { MainLayout } from '../../Layouts/MainLayout' import { PageWrap } from '../../_shared/PageWrap'
import { t } from '../../../utils/intl' import { t } from '../../../utils/intl'
// title={t('Projects')}> // title={t('Projects')}>
export const ProjectsPage = () => { export const ProjectsPage = () => {
return ( return (
<MainLayout> <PageWrap>
<article class="container container--static-page"> <article class="container container--static-page">
<div class="row"> <div class="row">
<div class="col-md-6 col-xl-7 shift-content order-md-first"> <div class="col-md-6 col-xl-7 shift-content order-md-first">
@ -13,7 +13,7 @@ export const ProjectsPage = () => {
</div> </div>
</div> </div>
</article> </article>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,6 +1,6 @@
import { createSignal, Show } from 'solid-js' import { createSignal, Show } from 'solid-js'
import { MainLayout } from '../../Layouts/MainLayout' import { PageWrap } from '../../_shared/PageWrap'
import { Icon } from '../../Nav/Icon' import { Icon } from '../../_shared/Icon'
// const title = t('Terms of use') // const title = t('Terms of use')
@ -10,7 +10,7 @@ export const TermsOfUsePage = () => {
const toggleIndexExpanded = () => setIndexExpanded((oldExpanded) => !oldExpanded) const toggleIndexExpanded = () => setIndexExpanded((oldExpanded) => !oldExpanded)
return ( return (
<MainLayout> <PageWrap>
{/*<Meta name="description" content={title} />*/} {/*<Meta name="description" content={title} />*/}
{/*<Meta name="keywords" content={`Discours.io, ${t('Terms of use')}, ${t('Terms of use', 'en')}`} />*/} {/*<Meta name="keywords" content={`Discours.io, ${t('Terms of use')}, ${t('Terms of use', 'en')}`} />*/}
{/*<Meta property="og:title" content={title} />*/} {/*<Meta property="og:title" content={title} />*/}
@ -274,7 +274,7 @@ export const TermsOfUsePage = () => {
</div> </div>
</div> </div>
</article> </article>
</MainLayout> </PageWrap>
) )
} }

View File

@ -1,10 +1,10 @@
import { MainLayout } from '../../Layouts/MainLayout' import { PageWrap } from '../../_shared/PageWrap'
import { t } from '../../../utils/intl' import { t } from '../../../utils/intl'
export const ThanksPage = () => { export const ThanksPage = () => {
const title = t('Thank you') const title = t('Thank you')
return ( return (
<MainLayout> <PageWrap>
{/*<Meta name="description" content={title} />*/} {/*<Meta name="description" content={title} />*/}
{/*<Meta name="keywords" content={`Discours.io, ${title}, ${t('Thank you', 'en')}`} />*/} {/*<Meta name="keywords" content={`Discours.io, ${title}, ${t('Thank you', 'en')}`} />*/}
{/*<Meta property="og:title" content={title} />*/} {/*<Meta property="og:title" content={title} />*/}
@ -85,7 +85,7 @@ export const ThanksPage = () => {
</div> </div>
</div> </div>
</article> </article>
</MainLayout> </PageWrap>
) )
} }

View File

@ -29,29 +29,16 @@ import { TermsOfUsePage } from './Pages/about/TermsOfUsePage'
import { ThanksPage } from './Pages/about/ThanksPage' 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 { InboxPage } from './Pages/InboxPage'
import { LayoutShoutsPage } from './Pages/LayoutShoutsPage'
import { SessionProvider } from '../context/session' import { SessionProvider } from '../context/session'
// 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 pagesMap: Record<keyof Routes, Component<PageProps>> = { const pagesMap: Record<keyof Routes, Component<PageProps>> = {
inbox: InboxPage,
expo: LayoutShoutsPage,
connect: ConnectPage, connect: ConnectPage,
create: CreatePage, create: CreatePage,
home: HomePage, home: HomePage,

View File

@ -1,5 +1,5 @@
import type { Topic } from '../../graphql/types.gen' import type { Topic } from '../../graphql/types.gen'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import './FloorHeader.scss' import './FloorHeader.scss'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'

View File

@ -1,7 +1,7 @@
import { createEffect, createMemo, createSignal, For, Show } from 'solid-js' import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'
import type { Author } from '../../graphql/types.gen' import type { Author } from '../../graphql/types.gen'
import { AuthorCard } from '../Author/Card' import { AuthorCard } from '../Author/Card'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { useAuthorsStore, setAuthorsSort } from '../../stores/zine/authors' import { useAuthorsStore, setAuthorsSort } from '../../stores/zine/authors'
import { handleClientRouteLinkClick, useRouter } from '../../stores/router' import { handleClientRouteLinkClick, useRouter } from '../../stores/router'

View File

@ -1,6 +1,6 @@
import { createEffect, createMemo, createSignal, For, Show } from 'solid-js' import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'
import type { Topic } from '../../graphql/types.gen' import type { Topic } from '../../graphql/types.gen'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { setTopicsSort, useTopicsStore } from '../../stores/zine/topics' import { setTopicsSort, useTopicsStore } from '../../stores/zine/topics'
import { handleClientRouteLinkClick, useRouter } from '../../stores/router' import { handleClientRouteLinkClick, useRouter } from '../../stores/router'

View File

@ -1,13 +1,14 @@
import { createEffect, createSignal, Show, Suspense } from 'solid-js' import { createEffect, createSignal, Show, Suspense } from 'solid-js'
import { FullArticle } from '../Article/FullArticle' import { FullArticle } from '../Article/FullArticle'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import type { Shout } from '../../graphql/types.gen' import type { Shout, Reaction } from '../../graphql/types.gen'
import { loadArticleReactions, useReactionsStore } from '../../stores/zine/reactions' import { useReactionsStore } from '../../stores/zine/reactions'
import '../../styles/Article.scss' import '../../styles/Article.scss'
interface ArticlePageProps { interface ArticlePageProps {
article: Shout article: Shout
reactions?: Reaction[]
} }
const ARTICLE_COMMENTS_PAGE_SIZE = 50 const ARTICLE_COMMENTS_PAGE_SIZE = 50
@ -15,13 +16,20 @@ const ARTICLE_COMMENTS_PAGE_SIZE = 50
export const ArticleView = (props: ArticlePageProps) => { export const ArticleView = (props: ArticlePageProps) => {
const [getCommentsPage] = createSignal(1) const [getCommentsPage] = createSignal(1)
const [getIsCommentsLoading, setIsCommentsLoading] = createSignal(false) const [getIsCommentsLoading, setIsCommentsLoading] = createSignal(false)
const reactionslist = useReactionsStore() const {
reactionsByShout,
sortedReactions,
createReaction,
updateReaction,
deleteReaction,
loadReactionsBy
} = useReactionsStore({ reactions: props.reactions })
createEffect(async () => { createEffect(async () => {
try { try {
setIsCommentsLoading(true) setIsCommentsLoading(true)
await loadArticleReactions({ await loadReactionsBy({
articleSlug: props.article.slug, by: { shout: props.article.slug },
limit: ARTICLE_COMMENTS_PAGE_SIZE, limit: ARTICLE_COMMENTS_PAGE_SIZE,
offset: getCommentsPage() * ARTICLE_COMMENTS_PAGE_SIZE offset: getCommentsPage() * ARTICLE_COMMENTS_PAGE_SIZE
}) })
@ -36,7 +44,7 @@ export const ArticleView = (props: ArticlePageProps) => {
<Suspense> <Suspense>
<FullArticle <FullArticle
article={props.article} article={props.article}
reactions={reactionslist().filter((r) => r.shout.slug === props.article.slug)} reactions={reactionsByShout()[props.article.slug]}
isCommentsLoading={getIsCommentsLoading()} isCommentsLoading={getIsCommentsLoading()}
/> />
</Suspense> </Suspense>

View File

@ -5,7 +5,7 @@ import { Row3 } from '../Feed/Row3'
import { AuthorFull } from '../Author/Full' import { AuthorFull } from '../Author/Full'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { useAuthorsStore } from '../../stores/zine/authors' import { useAuthorsStore } from '../../stores/zine/authors'
import { loadAuthorArticles, useArticlesStore } from '../../stores/zine/articles' import { loadShoutsBy, useArticlesStore } from '../../stores/zine/articles'
import { useTopicsStore } from '../../stores/zine/topics' import { useTopicsStore } from '../../stores/zine/topics'
import { useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
@ -15,7 +15,7 @@ import { splitToPages } from '../../utils/splitToPages'
// TODO: load reactions on client // TODO: load reactions on client
type AuthorProps = { type AuthorProps = {
authorArticles: Shout[] shouts: Shout[]
author: Author author: Author
authorSlug: string authorSlug: string
// FIXME author topics fro server // FIXME author topics fro server
@ -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
@ -31,7 +31,7 @@ const LOAD_MORE_PAGE_SIZE = 9 // Row3 + Row3 + Row3
export const AuthorView = (props: AuthorProps) => { export const AuthorView = (props: AuthorProps) => {
const { sortedArticles } = useArticlesStore({ const { sortedArticles } = useArticlesStore({
sortedArticles: props.authorArticles shouts: props.shouts
}) })
const { authorEntities } = useAuthorsStore({ authors: [props.author] }) const { authorEntities } = useAuthorsStore({ authors: [props.author] })
const { topicsByAuthor } = useTopicsStore() const { topicsByAuthor } = useTopicsStore()
@ -42,8 +42,8 @@ export const AuthorView = (props: AuthorProps) => {
const loadMore = async () => { const loadMore = async () => {
saveScrollPosition() saveScrollPosition()
const { hasMore } = await loadAuthorArticles({ const { hasMore } = await loadShoutsBy({
authorSlug: author().slug, by: { author: author().slug },
limit: LOAD_MORE_PAGE_SIZE, limit: LOAD_MORE_PAGE_SIZE,
offset: sortedArticles().length offset: sortedArticles().length
}) })
@ -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,63 +1,71 @@
import { createMemo, createSignal, For, onMount, Show } from 'solid-js' import { createEffect, createSignal, For, onMount, Show } from 'solid-js'
import '../../styles/Feed.scss' import '../../styles/Feed.scss'
import stylesBeside from '../../components/Feed/Beside.module.scss' import stylesBeside from '../../components/Feed/Beside.module.scss'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import { byCreated, sortBy } from '../../utils/sortby'
import { TopicCard } from '../Topic/Card' import { TopicCard } from '../Topic/Card'
import { ArticleCard } from '../Feed/Card' import { ArticleCard } from '../Feed/Card'
import { AuthorCard } from '../Author/Card' import { AuthorCard } from '../Author/Card'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { FeedSidebar } from '../Feed/Sidebar' import { FeedSidebar } from '../Feed/Sidebar'
import CommentCard from '../Article/Comment' import CommentCard from '../Article/Comment'
import { loadRecentArticles, useArticlesStore } from '../../stores/zine/articles' import { useArticlesStore } from '../../stores/zine/articles'
import { useReactionsStore } from '../../stores/zine/reactions' import { useReactionsStore } from '../../stores/zine/reactions'
import { useAuthorsStore } from '../../stores/zine/authors' import { useAuthorsStore } from '../../stores/zine/authors'
import { useTopicsStore } from '../../stores/zine/topics' import { useTopicsStore } from '../../stores/zine/topics'
import { useTopAuthorsStore } from '../../stores/zine/topAuthors' import { useTopAuthorsStore } from '../../stores/zine/topAuthors'
import { useSession } from '../../context/session' import { useSession } from '../../context/session'
import { Collab, ReactionKind, Shout } from '../../graphql/types.gen'
import { collabs, setCollabs } from '../../stores/editor'
// const AUTHORSHIP_REACTIONS = [ const AUTHORSHIP_REACTIONS = [
// ReactionKind.Accept, ReactionKind.Accept,
// ReactionKind.Reject, ReactionKind.Reject,
// ReactionKind.Propose, ReactionKind.Propose,
// ReactionKind.Ask ReactionKind.Ask
// ] ]
export const FEED_PAGE_SIZE = 20 export const FEED_PAGE_SIZE = 20
export const FeedView = () => { export const FeedView = () => {
// state // state
const { sortedArticles } = useArticlesStore() const { sortedArticles, loadShoutsBy } = useArticlesStore()
const reactions = useReactionsStore() const { sortedReactions: topComments, loadReactionsBy } = useReactionsStore({})
const { sortedAuthors } = useAuthorsStore() const { sortedAuthors } = useAuthorsStore()
const { topTopics } = useTopicsStore() const { topTopics } = useTopicsStore()
const { topAuthors } = useTopAuthorsStore() const { topAuthors } = useTopAuthorsStore()
const { session } = useSession() const { session } = useSession()
const topReactions = createMemo(() => sortBy(reactions(), byCreated))
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
// const expectingFocus = createMemo<Shout[]>(() => {
// // 1 co-author notifications needs
// // TODO: list of articles where you are co-author
// // TODO: preload proposals
// // TODO: (maybe?) and changes history
// console.debug(reactions().filter((r) => r.kind in AUTHORSHIP_REACTIONS))
//
// // 2 community self-regulating mechanics
// // TODO: query all new posts to be rated for publishing
// // TODO: query all reactions where user is in authors list
// return []
// })
const loadMore = async () => { const loadMore = async () => {
const { hasMore } = await loadRecentArticles({ limit: FEED_PAGE_SIZE, offset: sortedArticles().length }) const { hasMore } = await loadShoutsBy({
by: { visibility: 'community' },
limit: FEED_PAGE_SIZE,
offset: sortedArticles().length
})
setIsLoadMoreButtonVisible(hasMore) setIsLoadMoreButtonVisible(hasMore)
} }
onMount(() => { onMount(async () => {
loadMore() // load 5 recent comments overall
await loadReactionsBy({ by: {}, limit: 5, offset: 0 })
// load recent shouts not only published ( visibility = community )
await loadMore()
// TODO: load collabs
// await loadCollabs()
// load recent editing shouts ( visibility = authors )
const userslug = session().user.slug
await loadShoutsBy({ by: { author: userslug, visibility: 'authors' }, limit: 15, offset: 0 })
const collaborativeShouts = sortedArticles().filter((s: Shout, n: number, arr: Shout[]) => {
if (s.visibility !== 'authors') {
arr.splice(n, 1)
return arr
}
})
// load recent reactions on collabs
await loadReactionsBy({ by: { shouts: [...collaborativeShouts], body: true }, limit: 5, offset: 0 })
}) })
return ( return (
@ -118,7 +126,7 @@ export const FeedView = () => {
<aside class="col-md-3"> <aside class="col-md-3">
<section class="feed-comments"> <section class="feed-comments">
<h4>{t('Comments')}</h4> <h4>{t('Comments')}</h4>
<For each={topReactions()}> <For each={topComments()}>
{(comment) => <CommentCard comment={comment} compact={true} />} {(comment) => <CommentCard comment={comment} compact={true} />}
</For> </For>
</section> </section>

View File

@ -6,7 +6,7 @@ import { handleClientRouteLinkClick } from '../../stores/router'
// by: '' | 'topics' | 'authors' | 'reacted' // by: '' | 'topics' | 'authors' | 'reacted'
// } // }
export const FeedSettingsView = () => { export const FeedSettingsView = (_props) => {
return ( return (
<div class="container"> <div class="container">
<h1>{t('Feed settings')}</h1> <h1>{t('Feed settings')}</h1>

View File

@ -1,5 +1,5 @@
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import styles from '../../styles/FourOuFour.module.scss' import styles from '../../styles/FourOuFour.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'

View File

@ -11,15 +11,10 @@ import RowShort from '../Feed/RowShort'
import Slider from '../Feed/Slider' import Slider from '../Feed/Slider'
import Group from '../Feed/Group' import Group from '../Feed/Group'
import type { Shout, Topic } from '../../graphql/types.gen' import type { Shout, Topic } from '../../graphql/types.gen'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { useTopicsStore } from '../../stores/zine/topics' import { useTopicsStore } from '../../stores/zine/topics'
import { import { loadShoutsBy, useArticlesStore } from '../../stores/zine/articles'
loadPublishedArticles,
loadTopArticles,
loadTopMonthArticles,
useArticlesStore
} from '../../stores/zine/articles'
import { useTopAuthorsStore } from '../../stores/zine/topAuthors' import { useTopAuthorsStore } from '../../stores/zine/topAuthors'
import { locale } from '../../stores/ui' import { locale } from '../../stores/ui'
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll' import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
@ -27,7 +22,7 @@ import { splitToPages } from '../../utils/splitToPages'
type HomeProps = { type HomeProps = {
randomTopics: Topic[] randomTopics: Topic[]
recentPublishedArticles: Shout[] shouts: Shout[]
} }
export const PRERENDERED_ARTICLES_COUNT = 5 export const PRERENDERED_ARTICLES_COUNT = 5
@ -37,26 +32,24 @@ const LOAD_MORE_PAGE_SIZE = 16 // Row1 + Row3 + Row2 + Beside (3 + 1) + Row1 + R
export const HomeView = (props: HomeProps) => { export const HomeView = (props: HomeProps) => {
const { const {
sortedArticles, sortedArticles,
articlesByLayout,
topArticles, topArticles,
topMonthArticles,
topViewedArticles,
topCommentedArticles, topCommentedArticles,
articlesByLayout topMonthArticles,
topViewedArticles
} = useArticlesStore({ } = useArticlesStore({
sortedArticles: props.recentPublishedArticles shouts: props.shouts
}) })
const { randomTopics, topTopics } = useTopicsStore({ const { randomTopics, topTopics } = useTopicsStore({
randomTopics: props.randomTopics randomTopics: props.randomTopics
}) })
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const { topAuthors } = useTopAuthorsStore() const { topAuthors } = useTopAuthorsStore()
onMount(async () => { onMount(async () => {
loadTopArticles()
loadTopMonthArticles()
if (sortedArticles().length < PRERENDERED_ARTICLES_COUNT + CLIENT_LOAD_ARTICLES_COUNT) { if (sortedArticles().length < PRERENDERED_ARTICLES_COUNT + CLIENT_LOAD_ARTICLES_COUNT) {
const { hasMore } = await loadPublishedArticles({ const { hasMore } = await loadShoutsBy({
by: {},
limit: CLIENT_LOAD_ARTICLES_COUNT, limit: CLIENT_LOAD_ARTICLES_COUNT,
offset: sortedArticles().length offset: sortedArticles().length
}) })
@ -84,7 +77,8 @@ export const HomeView = (props: HomeProps) => {
const loadMore = async () => { const loadMore = async () => {
saveScrollPosition() saveScrollPosition()
const { hasMore } = await loadPublishedArticles({ const { hasMore } = await loadShoutsBy({
by: { visibility: 'public' },
limit: LOAD_MORE_PAGE_SIZE, limit: LOAD_MORE_PAGE_SIZE,
offset: sortedArticles().length offset: sortedArticles().length
}) })

View File

@ -1,59 +1,158 @@
import { For, createSignal, Show, onMount, createEffect } from 'solid-js'
import type { Author } from '../../graphql/types.gen' import type { Author } from '../../graphql/types.gen'
import { AuthorCard } from '../Author/Card' import { AuthorCard } from '../Author/Card'
import { Icon } from '../Nav/Icon' import { Icon } from '../_shared/Icon'
import '../../styles/Inbox.scss' import { Loading } from '../Loading'
import DialogCard from '../Inbox/DialogCard'
import Search from '../Inbox/Search'
import { useAuthorsStore } from '../../stores/zine/authors'
import MarkdownIt from 'markdown-it'
// const { session } = useAuthStore()
import '../../styles/Inbox.scss'
// Для моков
import { createClient } from '@urql/core'
import { findAndLoadGraphQLConfig } from '@graphql-codegen/cli'
// import { useAuthStore } from '../../stores/auth'
import { useSession } from '../../context/session'
const md = new MarkdownIt({
linkify: true
})
const OWNER_ID = '501'
const client = createClient({
url: 'https://graphqlzero.almansi.me/api'
})
// console.log('!!! session:', session)
// interface InboxProps { // interface InboxProps {
// chats?: Chat[] // chats?: Chat[]
// messages?: Message[] // messages?: Message[]
// } // }
const messageQuery = `
query Comments ($options: PageQueryOptions) {
comments(options: $options) {
data {
id
body
email
}
}
}
`
const newMessageQuery = `
mutation postComment($messageBody: String!) {
createComment(
input: { body: $messageBody, email: "test@test.com", name: "User" }
) {
id
body
name
email
}
}
`
const userSearch = (array: Author[], keyword: string) => {
const searchTerm = keyword.toLowerCase()
return array.filter((value) => {
return value.name.toLowerCase().match(new RegExp(searchTerm, 'g'))
})
}
const postMessage = async (msg: string) => {
const response = await client.mutation(newMessageQuery, { messageBody: msg }).toPromise()
return response.data.createComment
}
export const InboxView = () => { export const InboxView = () => {
// TODO: get user session const [messages, setMessages] = createSignal([])
const [authors, setAuthors] = createSignal<Author[]>([])
const [postMessageText, setPostMessageText] = createSignal('')
const [loading, setLoading] = createSignal<boolean>(false)
const [currentSlug, setCurrentSlug] = createSignal<Author['slug'] | null>()
const { session } = useSession()
const { sortedAuthors } = useAuthorsStore()
createEffect(() => {
setAuthors(sortedAuthors())
setCurrentSlug(session()?.user?.slug)
})
// Поиск по диалогам
const getQuery = (query) => {
if (query().length >= 2) {
const match = userSearch(authors(), query())
console.log('!!! match:', match)
setAuthors(match)
} else {
setAuthors(sortedAuthors())
}
}
const fetchMessages = async (query) => {
const response = await client
.query(query, {
options: { slice: { start: 0, end: 3 } }
})
.toPromise()
if (response.error) console.debug('getMessages', response.error)
setMessages(response.data.comments.data)
}
let chatWindow
onMount(async () => {
setLoading(true)
try {
await fetchMessages(messageQuery)
} catch (error) {
setLoading(false)
console.error([fetchMessages], error)
} finally {
setLoading(false)
chatWindow.scrollTop = chatWindow.scrollHeight
}
})
const handleSubmit = async () => {
try {
const post = await postMessage(postMessageText())
setMessages((prev) => [...prev, post])
setPostMessageText('')
chatWindow.scrollTop = chatWindow.scrollHeight
} catch (error) {
console.error('[post message error]:', error)
}
}
let formParent // autoresize ghost element
const handleChangeMessage = (event) => {
setPostMessageText(event.target.value)
}
createEffect(() => {
formParent.dataset.replicatedValue = postMessageText()
})
return ( return (
<div class="messages container"> <div class="messages container">
<div class="row"> <div class="row">
<div class="chat-list col-md-4"> <div class="chat-list col-md-4">
<form class="chat-list__search"> <Search placeholder="Поиск" onChange={getQuery} />
<input type="search" placeholder="Поиск" />
<button class="button">+</button>
</form>
<div class="chat-list__types"> <div class="chat-list__types">
<ul> <ul>
<li> <li>
<strong> <strong>Все</strong>
<a href="/">Все</a>
</strong>
</li>
<li>
<a href="/">Переписки</a>
</li>
<li>
<a href="/">Группы</a>
</li> </li>
<li>Переписки</li>
<li>Группы</li>
</ul> </ul>
</div> </div>
<div class="holder">
<div class="chat-list__users"> <div class="dialogs">
<ul> <For each={authors()}>{(author) => <DialogCard author={author} online={true} />}</For>
<li>
<AuthorCard author={{} as Author} hideFollow={true} />
<div class="last-message-date">12:15</div>
<div class="last-message-text">
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
</div> </div>
</li>
<li class="user--online chat-list__user--current">
<AuthorCard author={{} as Author} hideFollow={true} />
<div class="last-message-date">19:48</div>
<div class="last-message-text">
Assumenda delectus deleniti dolores doloribus ducimus, et expedita facere iste laborum,
nihil similique suscipit, ut voluptatem. Accusantium consequuntur doloremque ex molestiae
nemo.
</div>
</li>
</ul>
</div> </div>
</div> </div>
@ -64,59 +163,55 @@ export const InboxView = () => {
</div> </div>
<div class="conversation__messages"> <div class="conversation__messages">
<div class="conversation__messages-container"> <div class="conversation__messages-container" ref={chatWindow}>
<div class="conversation__message-container conversation__message-container--other"> <Show when={loading()}>
<Loading />
</Show>
<For each={messages()}>
{(comment: { body: string; id: string; email: string }) => (
<div
class={`conversation__message-container
${
OWNER_ID === comment.id
? 'conversation__message-container--own'
: 'conversation__message-container--other'
}`}
>
<div class="conversation__message"> <div class="conversation__message">
Круто, беру в оборот! <div innerHTML={md.render(comment.body)} />
<div class="conversation__message-details"> <div class="conversation__message-details">
<time>14:26</time> <time>14:26</time>
{comment.email} id: {comment.id}
</div> </div>
<button class="conversation__context-popup-control"> <button class="conversation__context-popup-control">
<Icon name="ellipsis" /> <Icon name="ellipsis" />
</button> </button>
</div> </div>
</div> </div>
)}
</For>
<div class="conversation__message-container conversation__message-container--own"> {/*<div class="conversation__date">*/}
<div class="conversation__message"> {/* <time>12 сентября</time>*/}
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut beatae earum iste itaque {/*</div>*/}
libero perspiciatis possimus quod! Accusamus, aliquam amet consequuntur debitis dolorum
esse laudantium magni omnis rerum voluptatem voluptates!
<div class="conversation__message-details">
<time>14:31</time>
Отредактировано
</div>
<button class="conversation__context-popup-control">
<Icon name="ellipsis" />
</button>
</div> </div>
</div> </div>
<div class="conversation__date"> <div class="message-form">
<time>12 сентября</time> <div class="wrapper">
<div class="grow-wrap" ref={formParent}>
<textarea
value={postMessageText()}
rows={1}
onInput={(event) => handleChangeMessage(event)}
placeholder="Написать сообщение"
/>
</div> </div>
<button type="submit" disabled={postMessageText().length === 0} onClick={handleSubmit}>
<div class="conversation__message-container conversation__message-container--other">
<div class="conversation__message">
Нужна грамотная инфраструктура для сообщений, если ожидается нагрузка - надо опираться на
это. Но в целом это несложно сделать.
<div class="conversation__message-details">
<time>10:47</time>
</div>
<button class="conversation__context-popup-control">
<Icon name="ellipsis" />
</button>
</div>
</div>
</div>
</div>
<form class="conversation__message-form">
<input type="text" placeholder="Написать сообщение" />
<button type="submit">
<Icon name="send-message" /> <Icon name="send-message" />
</button> </button>
</form> </div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,7 +3,7 @@ import '../../styles/Search.scss'
import type { Shout } from '../../graphql/types.gen' import type { Shout } from '../../graphql/types.gen'
import { ArticleCard } from '../Feed/Card' import { ArticleCard } from '../Feed/Card'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { useArticlesStore, loadSearchResults } from '../../stores/zine/articles' import { useArticlesStore, loadShoutsBy } from '../../stores/zine/articles'
import { handleClientRouteLinkClick, useRouter } from '../../stores/router' import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
type SearchPageSearchParams = { type SearchPageSearchParams = {
@ -16,7 +16,7 @@ type Props = {
} }
export const SearchView = (props: Props) => { export const SearchView = (props: Props) => {
const { sortedArticles } = useArticlesStore({ sortedArticles: props.results }) const { sortedArticles } = useArticlesStore({ shouts: props.results })
const [getQuery, setQuery] = createSignal(props.query) const [getQuery, setQuery] = createSignal(props.query)
const { searchParams } = useRouter<SearchPageSearchParams>() const { searchParams } = useRouter<SearchPageSearchParams>()
@ -28,7 +28,7 @@ export const SearchView = (props: Props) => {
const handleSubmit = (_ev) => { const handleSubmit = (_ev) => {
// TODO page // TODO page
// TODO sort // TODO sort
loadSearchResults({ query: getQuery() }) loadShoutsBy({ by: { title: getQuery(), body: getQuery() }, limit: 50 })
} }
return ( return (

View File

@ -8,7 +8,7 @@ import { FullTopic } from '../Topic/Full'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
import { useTopicsStore } from '../../stores/zine/topics' import { useTopicsStore } from '../../stores/zine/topics'
import { loadTopicArticles, useArticlesStore } from '../../stores/zine/articles' import { loadShoutsBy, useArticlesStore } from '../../stores/zine/articles'
import { useAuthorsStore } from '../../stores/zine/authors' import { useAuthorsStore } from '../../stores/zine/authors'
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll' import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
import { splitToPages } from '../../utils/splitToPages' import { splitToPages } from '../../utils/splitToPages'
@ -22,7 +22,7 @@ type TopicsPageSearchParams = {
interface TopicProps { interface TopicProps {
topic: Topic topic: Topic
topicArticles: Shout[] shouts: Shout[]
topicSlug: string topicSlug: string
} }
@ -34,7 +34,7 @@ export const TopicView = (props: TopicProps) => {
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const { sortedArticles } = useArticlesStore({ sortedArticles: props.topicArticles }) const { sortedArticles } = useArticlesStore({ shouts: props.shouts })
const { topicEntities } = useTopicsStore({ topics: [props.topic] }) const { topicEntities } = useTopicsStore({ topics: [props.topic] })
const { authorsByTopic } = useAuthorsStore() const { authorsByTopic } = useAuthorsStore()
@ -44,8 +44,8 @@ export const TopicView = (props: TopicProps) => {
const loadMore = async () => { const loadMore = async () => {
saveScrollPosition() saveScrollPosition()
const { hasMore } = await loadTopicArticles({ const { hasMore } = await loadShoutsBy({
topicSlug: topic().slug, by: { topic: topic().slug },
limit: LOAD_MORE_PAGE_SIZE, limit: LOAD_MORE_PAGE_SIZE,
offset: sortedArticles().length offset: sortedArticles().length
}) })

View File

@ -6,7 +6,7 @@ import '../../styles/app.scss'
import { Show } from 'solid-js' import { Show } from 'solid-js'
import { clsx } from 'clsx' import { clsx } from 'clsx'
type MainLayoutProps = { type PageWrapProps = {
headerTitle?: string headerTitle?: string
children: JSX.Element children: JSX.Element
isHeaderFixed?: boolean isHeaderFixed?: boolean
@ -14,7 +14,7 @@ type MainLayoutProps = {
class?: string class?: string
} }
export const MainLayout = (props: MainLayoutProps) => { export const PageWrap = (props: PageWrapProps) => {
const isHeaderFixed = props.isHeaderFixed !== undefined ? props.isHeaderFixed : true const isHeaderFixed = props.isHeaderFixed !== undefined ? props.isHeaderFixed : true
return ( return (

View File

@ -1,18 +1,18 @@
// 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 = {
randomTopics?: Topic[] randomTopics?: Topic[]
article?: Shout article?: Shout
authorArticles?: Shout[] shouts?: Shout[]
topicArticles?: Shout[]
homeArticles?: Shout[]
author?: Author author?: Author
allAuthors?: Author[] allAuthors?: Author[]
topic?: Topic topic?: Topic
allTopics?: Topic[] allTopics?: Topic[]
searchQuery?: string searchQuery?: string
layout?: LayoutType
// other types? // other types?
searchResults?: Shout[] searchResults?: Shout[]
chats?: Chat[] chats?: Chat[]

View File

@ -0,0 +1,12 @@
import { gql } from '@urql/core'
export default gql`
mutation CreateChat($title: String, $members: [String]!) {
createChat(title: $title, members: $members) {
error
chat {
id
}
}
}
`

View File

@ -1,9 +0,0 @@
import { gql } from '@urql/core'
export default gql`
mutation IncrementViewMutation($shout: String!) {
incrementView(shout: $shout) {
error
}
}
`

View File

@ -1,45 +0,0 @@
import { gql } from '@urql/core'
export default gql`
query GetShoutBySlugQuery($slug: String!) {
getShoutBySlug(slug: $slug) {
_id: slug
slug
title
subtitle
layout
cover
# community
body
authors {
_id: slug
name
slug
userpic
caption
}
mainTopic
topics {
title
body
slug
stat {
_id: shouts
shouts
authors
followers
}
}
createdAt
updatedAt
publishedAt
stat {
_id: viewed
viewed
reacted
rating
commented
}
}
}
`

View File

@ -1,42 +0,0 @@
import { gql } from '@urql/core'
export default gql`
query ShoutsForAuthorsQuery($slugs: [String]!, $limit: Int!, $offset: Int!) {
shoutsByAuthors(slugs: $slugs, limit: $limit, offset: $offset) {
_id: slug
title
subtitle
layout
slug
cover
# community
mainTopic
topics {
title
body
slug
stat {
_id: shouts
shouts
authors
followers
}
}
authors {
_id: slug
name
slug
bio
links
userpic
}
createdAt
publishedAt
stat {
_id: viewed
viewed
reacted
}
}
}
`

View File

@ -1,40 +0,0 @@
import { gql } from '@urql/core'
export default gql`
query ShoutsForCommunitiesQuery($slugs: [String]!, $limit: Int!, $offset: Int!) {
shoutsByCommunities(slugs: $slugs, limit: $limit, offset: $offset) {
_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
}
}
}
`

View File

@ -1,42 +0,0 @@
import { gql } from '@urql/core'
export default gql`
query ShoutsBySessionQuery($limit: Int!, $offset: Int!) {
shoutsForFeed(limit: $limit, offset: $offset) {
_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
rating
commented
}
}
}
`

View File

@ -1,40 +0,0 @@
import { gql } from '@urql/core'
export default gql`
query ShoutsForTopicsQuery($slugs: [String]!, $limit: Int!, $offset: Int!) {
shoutsByTopics(slugs: $slugs, limit: $limit, offset: $offset) {
_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
}
}
}
`

View File

@ -1,14 +1,16 @@
import { gql } from '@urql/core' import { gql } from '@urql/core'
export default gql` export default gql`
query RecentPublishedQuery($limit: Int!, $offset: Int!) { query LoadShoutsByQuery($by: ShoutsBy, $limit: Int!, $offset: Int!) {
recentPublished(limit: $limit, offset: $offset) { loadShoutsBy(by: $by, limit: $limit, offset: $offset) {
_id: slug _id: slug
title title
subtitle subtitle
slug slug
layout layout
cover cover
# community
mainTopic
topics { topics {
title title
body body
@ -26,10 +28,8 @@ export default gql`
slug slug
userpic userpic
} }
# community
mainTopic
publishedAt
createdAt createdAt
publishedAt
stat { stat {
_id: viewed _id: viewed
viewed viewed

View File

@ -1,40 +0,0 @@
import { gql } from '@urql/core'
export default gql`
query RecentAllQuery($limit: Int!, $offset: Int!) {
recentAll(limit: $limit, offset: $offset) {
_id: slug
title
subtitle
slug
layout
cover
topics {
title
body
slug
stat {
_id: shouts
shouts
authors
followers
}
}
authors {
_id: slug
name
slug
userpic
}
# community
mainTopic
createdAt
stat {
_id: viewed
viewed
reacted
rating
}
}
}
`

View File

@ -1,40 +0,0 @@
import { gql } from '@urql/core'
export default gql`
query TopMonthShoutsQuery($limit: Int!, $offset: Int!) {
topMonth(limit: $limit, offset: $offset) {
_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
}
}
}
`

View File

@ -1,41 +0,0 @@
import { gql } from '@urql/core'
export default gql`
query TopOverallShoutsQuery($limit: Int!, $offset: Int!) {
topOverall(limit: $limit, offset: $offset) {
_id: slug
title
subtitle
slug
layout
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
rating
}
}
}
`

View File

@ -1,41 +0,0 @@
import { gql } from '@urql/core'
export default gql`
query TopViewedShoutsQuery($limit: Int!, $offset: Int!) {
topViewed(limit: $limit, offset: $offset) {
_id: slug
title
subtitle
slug
layout
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
rating
}
}
}
`

View File

@ -1,8 +1,8 @@
import { gql } from '@urql/core' import { gql } from '@urql/core'
export default gql` export default gql`
query GetAuthorsBySlugsQuery($slugs: [String]!) { query AuthorLoadByQuery($by: AuthorsBy, $limit: Int, $offset: Int) {
getUsersBySlugs(slugs: $slugs) { loadAuthorsBy(by: $by, limit: $limit, offset: $offset) {
_id: slug _id: slug
slug slug
name name

View File

@ -0,0 +1,16 @@
import { gql } from '@urql/core'
export default gql`
query LoadMessagesQuery($by: MessagesBy!, $limit: Int, $offset: Int) {
loadMessagesBy(by: $by, limit: $limit, offset: $offset) {
error
messages {
author
body
createdAt
updatedAt
seen
}
}
}
`

View File

@ -0,0 +1,26 @@
import { gql } from '@urql/core'
export default gql`
query GetChatsQuery($limit: Int, $offset: Int) {
loadChats(limit: $limit, offset: $offset) {
error
chats {
title
description
updatedAt
messages {
id
author
body
replyTo
createdAt
}
users {
slug
name
userpic
}
}
}
}
`

View File

@ -1,13 +0,0 @@
import { gql } from '@urql/core'
export default gql`
query {
getMyCollections {
id
title
desc
slug
amount
}
}
`

View File

@ -1,13 +0,0 @@
import { gql } from '@urql/core'
export default gql`
query CollectionsUserQuery($slug: String!) {
getUserCollections(user: $slug) {
id
title
desc
slug
amount
}
}
`

View File

@ -1,23 +0,0 @@
import { gql } from '@urql/core'
export default gql`
query GetChatsQuery {
myChats {
messages {
chatId
id
author
body
replyTo
createdAt
}
users {
slug
name
pic
}
title
createdAt
}
}
`

View File

@ -1,8 +1,8 @@
import { gql } from '@urql/core' import { gql } from '@urql/core'
export default gql` export default gql`
query ReactionsForShoutsQuery($shouts: [String]!, $limit: Int!, $offset: Int!) { query LoadReactionsByQuery($by: ReactionsBy, $limit: Int!, $offset: Int!) {
reactionsForShouts(shouts: $shouts, limit: $limit, offset: $offset) { loadReactionsBy(by: $by, limit: $limit, offset: $offset) {
id id
createdBy { createdBy {
slug slug

View File

@ -1,42 +0,0 @@
import { gql } from '@urql/core'
export default gql`
query SearchResultsQuery($q: String!, $limit: Int!, $offset: Int!) {
searchQuery(q: $q, limit: $limit, offset: $offset) {
_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
rating
commented
}
}
}
`

View File

@ -41,6 +41,17 @@ export type AuthorStat = {
rating?: Maybe<Scalars['Int']> rating?: Maybe<Scalars['Int']>
} }
export type AuthorsBy = {
createdAt?: InputMaybe<Scalars['DateTime']>
days?: InputMaybe<Scalars['Int']>
lastSeen?: InputMaybe<Scalars['DateTime']>
name?: InputMaybe<Scalars['String']>
order?: InputMaybe<Scalars['String']>
slug?: InputMaybe<Scalars['String']>
stat?: InputMaybe<Scalars['String']>
topic?: InputMaybe<Scalars['String']>
}
export type Chat = { export type Chat = {
admins?: Maybe<Array<Maybe<User>>> admins?: Maybe<Array<Maybe<User>>>
createdAt: Scalars['Int'] createdAt: Scalars['Int']
@ -88,12 +99,6 @@ export type Collection = {
title: Scalars['String'] title: Scalars['String']
} }
export type CollectionInput = {
desc?: InputMaybe<Scalars['String']>
pic?: InputMaybe<Scalars['String']>
title: Scalars['String']
}
export type Community = { export type Community = {
createdAt: Scalars['DateTime'] createdAt: Scalars['DateTime']
createdBy: User createdBy: User
@ -104,12 +109,6 @@ export type Community = {
slug: Scalars['String'] slug: Scalars['String']
} }
export type CommunityInput = {
desc?: InputMaybe<Scalars['String']>
pic?: InputMaybe<Scalars['String']>
title: Scalars['String']
}
export enum FollowingEntity { export enum FollowingEntity {
Author = 'AUTHOR', Author = 'AUTHOR',
Community = 'COMMUNITY', Community = 'COMMUNITY',
@ -133,25 +132,28 @@ export enum MessageStatus {
Updated = 'UPDATED' Updated = 'UPDATED'
} }
export type MessagesBy = {
author?: InputMaybe<Scalars['String']>
body?: InputMaybe<Scalars['String']>
chat?: InputMaybe<Scalars['String']>
days?: InputMaybe<Scalars['Int']>
order?: InputMaybe<Scalars['String']>
stat?: InputMaybe<Scalars['String']>
}
export type Mutation = { export type Mutation = {
confirmEmail: AuthResult confirmEmail: AuthResult
createChat: Result createChat: Result
createCollection: Result
createCommunity: Result
createMessage: Result createMessage: Result
createReaction: Result createReaction: Result
createShout: Result createShout: Result
createTopic: Result createTopic: Result
deleteChat: Result deleteChat: Result
deleteCollection: Result
deleteCommunity: Result
deleteMessage: Result deleteMessage: Result
deleteReaction: Result deleteReaction: Result
deleteShout: Result deleteShout: Result
destroyTopic: Result destroyTopic: Result
enterChat: Result
follow: Result follow: Result
incrementView: Result
inviteAuthor: Result inviteAuthor: Result
inviteChat: Result inviteChat: Result
markAsRead: Result markAsRead: Result
@ -162,9 +164,8 @@ export type Mutation = {
sendLink: Result sendLink: Result
unfollow: Result unfollow: Result
updateChat: Result updateChat: Result
updateCollection: Result
updateCommunity: Result
updateMessage: Result updateMessage: Result
updateOnlineStatus: Result
updateProfile: Result updateProfile: Result
updateReaction: Result updateReaction: Result
updateShout: Result updateShout: Result
@ -180,14 +181,6 @@ export type MutationCreateChatArgs = {
title?: InputMaybe<Scalars['String']> title?: InputMaybe<Scalars['String']>
} }
export type MutationCreateCollectionArgs = {
collection: CollectionInput
}
export type MutationCreateCommunityArgs = {
community: CommunityInput
}
export type MutationCreateMessageArgs = { export type MutationCreateMessageArgs = {
body: Scalars['String'] body: Scalars['String']
chatId: Scalars['String'] chatId: Scalars['String']
@ -210,14 +203,6 @@ export type MutationDeleteChatArgs = {
chatId: Scalars['String'] chatId: Scalars['String']
} }
export type MutationDeleteCollectionArgs = {
slug: Scalars['String']
}
export type MutationDeleteCommunityArgs = {
slug: Scalars['String']
}
export type MutationDeleteMessageArgs = { export type MutationDeleteMessageArgs = {
chatId: Scalars['String'] chatId: Scalars['String']
id: Scalars['Int'] id: Scalars['Int']
@ -235,19 +220,11 @@ export type MutationDestroyTopicArgs = {
slug: Scalars['String'] slug: Scalars['String']
} }
export type MutationEnterChatArgs = {
chatId: Scalars['String']
}
export type MutationFollowArgs = { export type MutationFollowArgs = {
slug: Scalars['String'] slug: Scalars['String']
what: FollowingEntity what: FollowingEntity
} }
export type MutationIncrementViewArgs = {
shout: Scalars['String']
}
export type MutationInviteAuthorArgs = { export type MutationInviteAuthorArgs = {
author: Scalars['String'] author: Scalars['String']
shout: Scalars['String'] shout: Scalars['String']
@ -293,14 +270,6 @@ export type MutationUpdateChatArgs = {
chat: ChatInput chat: ChatInput
} }
export type MutationUpdateCollectionArgs = {
collection: CollectionInput
}
export type MutationUpdateCommunityArgs = {
community: CommunityInput
}
export type MutationUpdateMessageArgs = { export type MutationUpdateMessageArgs = {
body: Scalars['String'] body: Scalars['String']
chatId: Scalars['String'] chatId: Scalars['String']
@ -348,98 +317,66 @@ export type ProfileInput = {
export type Query = { export type Query = {
authorsAll: Array<Maybe<Author>> authorsAll: Array<Maybe<Author>>
collectionsAll: Array<Maybe<Collection>>
getAuthor: User getAuthor: User
getCollabs: Array<Maybe<Collab>> getCollabs: Array<Maybe<Collab>>
getCommunities: Array<Maybe<Community>>
getCommunity: Community
getShoutBySlug: Shout
getTopic: Topic getTopic: Topic
getUserCollections: Array<Maybe<Collection>>
getUserRoles: Array<Maybe<Role>>
getUsersBySlugs: Array<Maybe<Author>>
isEmailUsed: Scalars['Boolean'] isEmailUsed: Scalars['Boolean']
loadAuthorsBy: Array<Maybe<Author>>
loadChats: Result loadChats: Result
loadMessages: Result loadMessagesBy: Result
loadReactionsBy: Array<Maybe<Reaction>>
loadShoutsBy: Array<Maybe<Shout>>
markdownBody: Scalars['String'] markdownBody: Scalars['String']
reactionsByAuthor: Array<Maybe<Reaction>>
reactionsForShouts: Array<Maybe<Reaction>>
recentAll: Array<Maybe<Shout>>
recentCandidates: Array<Maybe<Shout>>
recentCommented: Array<Maybe<Shout>>
recentLayoutShouts: Array<Maybe<Shout>>
recentPublished: Array<Maybe<Shout>>
recentReacted: Array<Maybe<Shout>>
searchChats: Result
searchMessages: Result
searchQuery?: Maybe<Array<Maybe<Shout>>>
searchUsers: Result searchUsers: Result
shoutsByAuthors: Array<Maybe<Shout>>
shoutsByCollection: Array<Maybe<Shout>>
shoutsByCommunities: Array<Maybe<Shout>>
shoutsByLayout: Array<Maybe<Shout>>
shoutsByTopics: Array<Maybe<Shout>>
shoutsForFeed: Array<Maybe<Shout>>
signIn: AuthResult signIn: AuthResult
signOut: AuthResult signOut: AuthResult
topAuthors: Array<Maybe<Author>>
topCommented: Array<Maybe<Shout>>
topLayoutShouts: Array<Maybe<Shout>>
topMonth: Array<Maybe<Shout>>
topMonthLayoutShouts: Array<Maybe<Shout>>
topOverall: 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<Author>> userFollowedAuthors: Array<Maybe<Author>>
userFollowedCommunities: Array<Maybe<Community>>
userFollowedTopics: Array<Maybe<Topic>> userFollowedTopics: Array<Maybe<Topic>>
userFollowers: Array<Maybe<Author>> userFollowers: Array<Maybe<Author>>
userReactedShouts: Array<Maybe<Shout>>
} }
export type QueryGetAuthorArgs = { export type QueryGetAuthorArgs = {
slug: Scalars['String'] slug: Scalars['String']
} }
export type QueryGetCommunityArgs = {
slug?: InputMaybe<Scalars['String']>
}
export type QueryGetShoutBySlugArgs = {
slug: Scalars['String']
}
export type QueryGetTopicArgs = { export type QueryGetTopicArgs = {
slug: Scalars['String'] slug: Scalars['String']
} }
export type QueryGetUserCollectionsArgs = {
author: Scalars['String']
}
export type QueryGetUserRolesArgs = {
slug: Scalars['String']
}
export type QueryGetUsersBySlugsArgs = {
slugs: Array<InputMaybe<Scalars['String']>>
}
export type QueryIsEmailUsedArgs = { export type QueryIsEmailUsedArgs = {
email: Scalars['String'] email: Scalars['String']
} }
export type QueryLoadChatsArgs = { export type QueryLoadAuthorsByArgs = {
amount?: InputMaybe<Scalars['Int']> by?: InputMaybe<AuthorsBy>
limit?: InputMaybe<Scalars['Int']>
offset?: InputMaybe<Scalars['Int']> offset?: InputMaybe<Scalars['Int']>
} }
export type QueryLoadMessagesArgs = { export type QueryLoadChatsArgs = {
amount?: InputMaybe<Scalars['Int']> limit?: InputMaybe<Scalars['Int']>
chatId: Scalars['String'] offset?: InputMaybe<Scalars['Int']>
}
export type QueryLoadMessagesByArgs = {
by: MessagesBy
limit?: InputMaybe<Scalars['Int']>
offset?: InputMaybe<Scalars['Int']>
}
export type QueryLoadReactionsByArgs = {
by: ReactionBy
limit?: InputMaybe<Scalars['Int']>
offset?: InputMaybe<Scalars['Int']>
}
export type QueryLoadShoutsByArgs = {
by?: InputMaybe<ShoutsBy>
limit?: InputMaybe<Scalars['Int']>
offset?: InputMaybe<Scalars['Int']> offset?: InputMaybe<Scalars['Int']>
} }
@ -447,106 +384,10 @@ export type QueryMarkdownBodyArgs = {
body: Scalars['String'] body: Scalars['String']
} }
export type QueryReactionsByAuthorArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
slug: Scalars['String']
}
export type QueryReactionsForShoutsArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
shouts: Array<InputMaybe<Scalars['String']>>
}
export type QueryRecentAllArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryRecentCandidatesArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryRecentCommentedArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryRecentLayoutShoutsArgs = {
amount?: InputMaybe<Scalars['Int']>
layout: Scalars['String']
offset?: InputMaybe<Scalars['Int']>
}
export type QueryRecentPublishedArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryRecentReactedArgs = {
limit: 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 = {
limit: Scalars['Int']
offset: Scalars['Int']
q?: InputMaybe<Scalars['String']>
}
export type QuerySearchUsersArgs = { export type QuerySearchUsersArgs = {
amount?: InputMaybe<Scalars['Int']> limit?: InputMaybe<Scalars['Int']>
offset?: InputMaybe<Scalars['Int']> offset?: InputMaybe<Scalars['Int']>
q: Scalars['String'] query: Scalars['String']
}
export type QueryShoutsByAuthorsArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
slugs: Array<InputMaybe<Scalars['String']>>
}
export type QueryShoutsByCollectionArgs = {
collection: Scalars['String']
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryShoutsByCommunitiesArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
slugs: Array<InputMaybe<Scalars['String']>>
}
export type QueryShoutsByLayoutArgs = {
amount: Scalars['Int']
layout?: InputMaybe<Scalars['String']>
offset: Scalars['Int']
}
export type QueryShoutsByTopicsArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
slugs: Array<InputMaybe<Scalars['String']>>
}
export type QueryShoutsForFeedArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
} }
export type QuerySignInArgs = { export type QuerySignInArgs = {
@ -555,44 +396,6 @@ export type QuerySignInArgs = {
password?: InputMaybe<Scalars['String']> password?: InputMaybe<Scalars['String']>
} }
export type QueryTopAuthorsArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryTopCommentedArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryTopLayoutShoutsArgs = {
amount?: InputMaybe<Scalars['Int']>
layout: Scalars['String']
offset?: InputMaybe<Scalars['Int']>
}
export type QueryTopMonthArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryTopMonthLayoutShoutsArgs = {
amount?: InputMaybe<Scalars['Int']>
layout: Scalars['String']
offset?: InputMaybe<Scalars['Int']>
}
export type QueryTopOverallArgs = {
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryTopPublishedArgs = {
daysago: Scalars['Int']
limit: Scalars['Int']
offset: Scalars['Int']
}
export type QueryTopicsByAuthorArgs = { export type QueryTopicsByAuthorArgs = {
author: Scalars['String'] author: Scalars['String']
} }
@ -609,10 +412,6 @@ export type QueryUserFollowedAuthorsArgs = {
slug: Scalars['String'] slug: Scalars['String']
} }
export type QueryUserFollowedCommunitiesArgs = {
slug: Scalars['String']
}
export type QueryUserFollowedTopicsArgs = { export type QueryUserFollowedTopicsArgs = {
slug: Scalars['String'] slug: Scalars['String']
} }
@ -621,10 +420,6 @@ export type QueryUserFollowersArgs = {
slug: Scalars['String'] slug: Scalars['String']
} }
export type QueryUserReactedShoutsArgs = {
slug: Scalars['String']
}
export type Rating = { export type Rating = {
rater: Scalars['String'] rater: Scalars['String']
value: Scalars['Int'] value: Scalars['Int']
@ -647,6 +442,17 @@ export type Reaction = {
updatedAt?: Maybe<Scalars['DateTime']> updatedAt?: Maybe<Scalars['DateTime']>
} }
export type ReactionBy = {
author?: InputMaybe<Scalars['String']>
body?: InputMaybe<Scalars['String']>
days?: InputMaybe<Scalars['Int']>
order?: InputMaybe<Scalars['String']>
shout?: InputMaybe<Scalars['String']>
shouts?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
stat?: InputMaybe<Scalars['String']>
topic?: InputMaybe<Scalars['String']>
}
export type ReactionInput = { export type ReactionInput = {
body?: InputMaybe<Scalars['String']> body?: InputMaybe<Scalars['String']>
kind: Scalars['Int'] kind: Scalars['Int']
@ -757,6 +563,20 @@ export type ShoutInput = {
visibleForUsers?: InputMaybe<Array<InputMaybe<Scalars['String']>>> visibleForUsers?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
} }
export type ShoutsBy = {
author?: InputMaybe<Scalars['String']>
body?: InputMaybe<Scalars['String']>
days?: InputMaybe<Scalars['Int']>
layout?: InputMaybe<Scalars['String']>
order?: InputMaybe<Scalars['String']>
slug?: InputMaybe<Scalars['String']>
stat?: InputMaybe<Scalars['String']>
title?: InputMaybe<Scalars['String']>
topic?: InputMaybe<Scalars['String']>
topics?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
visibility?: InputMaybe<Scalars['String']>
}
export type Stat = { export type Stat = {
commented?: Maybe<Scalars['Int']> commented?: Maybe<Scalars['Int']>
ranking?: Maybe<Scalars['Int']> ranking?: Maybe<Scalars['Int']>

View File

@ -1,23 +0,0 @@
---
import { setLocale } from '../stores/ui';
import '../styles/app.scss'
import { t } from '../utils/intl'
const lang = Astro.url.searchParams.get('lang') || 'ru'
console.log('[layout] server locale is', lang)
setLocale(lang)
---
<html lang={lang || 'ru'}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/png" href="/favicon.png" />
<title>{t('Discours')}</title>
</head>
<body>
<slot />
</body>
</html>

View File

@ -171,6 +171,10 @@
"Link sent, check your email": "Ссылка отправлена, проверьте почту", "Link sent, check your email": "Ссылка отправлена, проверьте почту",
"Create post": "Создать публикацию", "Create post": "Создать публикацию",
"Just start typing...": "Просто начните печатать...", "Just start typing...": "Просто начните печатать...",
"Artworks": "Артворки",
"Audio": "Аудио",
"Video": "Видео",
"Literature": "Литература",
"We can't find you, check email or": "Не можем вас найти, проверьте адрес электронной почты или", "We can't find you, check email or": "Не можем вас найти, проверьте адрес электронной почты или",
"register": "зарегистрируйтесь" "register": "зарегистрируйтесь"
} }

30
src/main.astro Normal file
View File

@ -0,0 +1,30 @@
---
import { setLocale } from './stores/ui'
import './styles/app.scss'
import { t } from './utils/intl'
// Setting locale for prerendered content here
const lang = Astro.url.searchParams.get('lang') || 'ru'
console.log('[main.astro] astro runtime locale is', lang)
setLocale(lang)
---
<html lang={lang || 'ru'}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/png" href="/favicon.png" />
<title>{t('Discours')}</title>
<script async
src="https://ackee.discours.io/increment.js"
data-ackee-server="https://ackee.discours.io"
data-ackee-domain-id="1004abeb-89b2-4e85-ad97-74f8d2c8ed2d"
></script>
</head>
<body>
<slot />
</body>
</html>

View File

@ -1,5 +1,5 @@
--- ---
import Zine from '../layouts/zine.astro' import Prerendered from '../main.astro'
import { Root } from '../components/Root' import { Root } from '../components/Root'
import { initRouter } from '../stores/router' import { initRouter } from '../stores/router'
@ -9,6 +9,6 @@ initRouter(pathname, search)
Astro.response.headers.set('Cache-Control', 's-maxage=1, stale-while-revalidate') Astro.response.headers.set('Cache-Control', 's-maxage=1, stale-while-revalidate')
--- ---
<Zine> <Prerendered>
<Root client:load /> <Root client:load />
</Zine> </Prerendered>

View File

@ -1,6 +1,6 @@
--- ---
import { Root } from '../components/Root' import { Root } from '../components/Root'
import Zine from '../layouts/zine.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'
@ -9,7 +9,7 @@ if (slug.endsWith('.map')) {
return Astro.redirect('/404') return Astro.redirect('/404')
} }
const article = await apiClient.getArticle({ slug }) const article = await apiClient.loadShoutsBy({ by: { slug }, limit: 1})
if (!article) { if (!article) {
return Astro.redirect('/404') return Astro.redirect('/404')
} }
@ -20,6 +20,6 @@ initRouter(pathname, search)
Astro.response.headers.set('Cache-Control', 's-maxage=1, stale-while-revalidate') Astro.response.headers.set('Cache-Control', 's-maxage=1, stale-while-revalidate')
--- ---
<Zine> <Prerendered>
<Root article={article} client:load /> <Root article={article.at(0)} client:load />
</Zine> </Prerendered>

Some files were not shown because too many files have changed in this diff Show More