Merge remote-tracking branch 'origin/dev' into client-routing-minor-fixes
This commit is contained in:
commit
e65b075dca
|
@ -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/
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
100
package.json
100
package.json
|
@ -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
10965
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
4
public/icons/audio.svg
Normal file
4
public/icons/audio.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="13" height="14" viewBox="0 0 13 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M11.1853 7.68061L11.1746 2.64865L5.05266 3.68025C5.04861 8.28981 5.04659 10.9099 5.04659 11.5405L5.04077 11.5326C4.95203 12.9109 3.85883 14 2.52329 14C1.12972 14 0 12.8142 0 11.3514C0 9.88854 1.12972 8.7027 2.52329 8.7027C2.77381 8.7027 3.0158 8.74102 3.24423 8.81239V1.7027L12.9769 0C13.0077 6.85087 13.0077 10.3193 12.9769 10.4054L12.9711 10.398C12.8821 11.776 11.789 12.8649 10.4536 12.8649C9.06007 12.8649 7.93035 11.679 7.93035 10.2162C7.93035 8.75341 9.06007 7.56757 10.4536 7.56757C10.7081 7.56757 10.9537 7.60709 11.1853 7.68061Z" fill="currentColor"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 675 B |
|
@ -1,3 +1,3 @@
|
||||||
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="17" height="18" viewBox="0 0 17 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M7.71176 2.49041L8.40551 2.26722V1.55186C8.4056 1.55036 8.40572 1.54846 8.40589 1.54616C8.40697 1.53163 8.40964 1.50407 8.41583 1.46825C8.42899 1.39201 8.4535 1.30486 8.49268 1.22923C8.52946 1.15823 8.57081 1.11241 8.61909 1.08135C8.6628 1.05322 8.7689 1 8.99954 1C9.23018 1 9.33628 1.05322 9.38 1.08135C9.42827 1.11241 9.46962 1.15823 9.5064 1.22923C9.54558 1.30486 9.57009 1.39201 9.58325 1.46825C9.58944 1.50407 9.59211 1.53163 9.59319 1.54616C9.59336 1.54846 9.59348 1.55036 9.59358 1.55186V2.26722L10.2873 2.49041C12.7482 3.28211 14.3757 5.49002 14.3757 7.94197V12.8672C14.3757 13.986 14.82 14.759 15.3994 15.377C15.6695 15.665 15.9739 15.925 16.2499 16.1586C16.2731 16.1783 16.2962 16.1978 16.3192 16.2172C16.5518 16.4139 16.7728 16.6007 16.991 16.8057C16.9922 16.8139 16.9934 16.8231 16.9946 16.8335C17.0069 16.9472 16.999 17.1091 16.962 17.2772C16.9459 17.3502 16.9271 17.4118 16.9087 17.4615H10.5936H9.59358V18.4481C9.59348 18.4496 9.59336 18.4515 9.59319 18.4538C9.59211 18.4684 9.58944 18.4959 9.58325 18.5318C9.57009 18.608 9.54558 18.6951 9.5064 18.7708C9.46962 18.8418 9.42827 18.8876 9.38 18.9187C9.33628 18.9468 9.23018 19 8.99954 19C8.7689 19 8.6628 18.9468 8.61909 18.9187C8.57081 18.8876 8.52946 18.8418 8.49268 18.7708C8.4535 18.6951 8.42899 18.608 8.41583 18.5318C8.40964 18.4959 8.40697 18.4684 8.40589 18.4538C8.40572 18.4515 8.4056 18.4496 8.40551 18.4481V17.4615H7.40551H1.09134C1.07295 17.4118 1.05414 17.3502 1.03802 17.2772C1.00095 17.1091 0.993067 16.9472 1.00542 16.8335C1.00655 16.8231 1.00778 16.8139 1.00901 16.8057C1.22708 16.6008 1.44781 16.4142 1.6802 16.2176C1.70337 16.198 1.72666 16.1783 1.75007 16.1585C2.02607 15.9249 2.33033 15.6649 2.60028 15.3768C3.17948 14.7587 3.62341 13.9857 3.62341 12.8672V7.94197C3.62341 5.49001 5.25088 3.28211 7.71176 2.49041ZM16.8354 17.6091C16.8354 17.609 16.8361 17.608 16.8377 17.6061C16.8362 17.6082 16.8354 17.6091 16.8354 17.6091ZM1.16462 17.6091C1.16462 17.6091 1.16384 17.6082 1.16228 17.6061C1.16385 17.608 1.16463 17.609 1.16462 17.6091Z" stroke="black" stroke-width="2"/>
|
<path d="M6.97121 2.33657L7.66496 2.11338V1.39995C7.66498 1.39963 7.665 1.39929 7.66503 1.39893C7.66579 1.3886 7.66785 1.36706 7.67279 1.33844C7.68346 1.27662 7.70299 1.20871 7.73221 1.1523C7.75902 1.10054 7.78543 1.07394 7.81129 1.05731C7.83259 1.0436 7.9088 1 8.09959 1C8.29037 1 8.36659 1.0436 8.38789 1.05731C8.41374 1.07394 8.44015 1.10054 8.46697 1.1523C8.49619 1.20871 8.51571 1.27662 8.52639 1.33844C8.53133 1.36706 8.53338 1.3886 8.53414 1.39893C8.53417 1.39929 8.53419 1.39963 8.53422 1.39995V2.11338L9.22796 2.33657C11.4033 3.03639 12.8381 4.98635 12.8381 7.14777V11.5805C12.8381 12.6186 13.2522 13.3378 13.7865 13.9077C14.0341 14.1717 14.3122 14.4092 14.5603 14.6191C14.5811 14.6367 14.6017 14.6541 14.6223 14.6715C14.8226 14.8408 15.0112 15.0003 15.1969 15.1732C15.2049 15.2629 15.1981 15.3919 15.1681 15.5279C15.1613 15.5589 15.1537 15.5882 15.1456 15.6154H9.53422H8.53422V16.6C8.53419 16.6004 8.53417 16.6007 8.53414 16.6011C8.53338 16.6114 8.53133 16.6329 8.52639 16.6616C8.51571 16.7234 8.49619 16.7913 8.46697 16.8477C8.44015 16.8995 8.41374 16.9261 8.38789 16.9427C8.36659 16.9564 8.29037 17 8.09959 17C7.9088 17 7.83259 16.9564 7.81129 16.9427C7.78543 16.9261 7.75902 16.8995 7.73221 16.8477C7.70299 16.7913 7.68346 16.7234 7.67279 16.6616C7.66785 16.6329 7.66579 16.6114 7.66503 16.6011C7.665 16.6007 7.66498 16.6004 7.66496 16.6V15.6154H6.66496H1.05437C1.04632 15.5882 1.03872 15.5589 1.03187 15.5279C1.00187 15.3919 0.995103 15.2629 1.00309 15.1732C1.18866 15.0004 1.37707 14.8411 1.57711 14.6719C1.59784 14.6544 1.61869 14.6368 1.63967 14.619C1.88772 14.409 2.16578 14.1715 2.41322 13.9075C2.9474 13.3375 3.36106 12.6184 3.36106 11.5805V7.14777C3.36106 4.98635 4.79591 3.03639 6.97121 2.33657ZM1.12345 15.7819C1.12341 15.7818 1.12338 15.7818 1.12334 15.7817L1.12345 15.7819Z" stroke="black" stroke-width="2"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.9 KiB |
1
public/icons/literature.svg
Normal file
1
public/icons/literature.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg width="12" height="18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.969 1.688l6.181 2.18v12.88H12V2.722L4.2 0 0 1.47v13.668L7.436 18H8.55V4.532L2.147 2.265l1.822-.577z" fill="currentColor"/></svg>
|
After Width: | Height: | Size: 216 B |
66
src/components/Article/AudioPlayer.tsx
Normal file
66
src/components/Article/AudioPlayer.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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'
|
||||||
|
|
|
@ -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 = () =>
|
||||||
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -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'
|
||||||
|
|
109
src/components/Article/Soundwave.tsx
Normal file
109
src/components/Article/Soundwave.tsx
Normal 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} />
|
||||||
|
}
|
|
@ -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'
|
||||||
|
@ -20,6 +20,7 @@ interface AuthorCardProps {
|
||||||
isAuthorPage?: boolean
|
isAuthorPage?: boolean
|
||||||
noSocialButtons?: boolean
|
noSocialButtons?: boolean
|
||||||
isAuthorsList?: boolean
|
isAuthorsList?: boolean
|
||||||
|
truncateBio?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuthorCard = (props: AuthorCardProps) => {
|
export const AuthorCard = (props: AuthorCardProps) => {
|
||||||
|
@ -63,7 +64,8 @@ export const AuthorCard = (props: AuthorCardProps) => {
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={!props.hideDescription}>
|
<Show when={!props.hideDescription}>
|
||||||
<div class={styles.authorAbout} classList={{ 'text-truncate': props.isAuthorsList }}>
|
{props.isAuthorsList}
|
||||||
|
<div class={styles.authorAbout} classList={{ 'text-truncate': props.truncateBio }}>
|
||||||
{bio()}
|
{bio()}
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
@ -86,7 +88,6 @@ export const AuthorCard = (props: AuthorCardProps) => {
|
||||||
>
|
>
|
||||||
<Show when={!props.isAuthorsList}>
|
<Show when={!props.isAuthorsList}>
|
||||||
<Icon name="author-subscribe" class={styles.icon} />
|
<Icon name="author-subscribe" class={styles.icon} />
|
||||||
|
|
||||||
</Show>
|
</Show>
|
||||||
<span class={styles.buttonLabel}>{t('Follow')}</span>
|
<span class={styles.buttonLabel}>{t('Follow')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -103,7 +104,6 @@ export const AuthorCard = (props: AuthorCardProps) => {
|
||||||
>
|
>
|
||||||
<Show when={!props.isAuthorsList}>
|
<Show when={!props.isAuthorsList}>
|
||||||
<Icon name="author-unsubscribe" class={styles.icon} />
|
<Icon name="author-unsubscribe" class={styles.icon} />
|
||||||
|
|
||||||
</Show>
|
</Show>
|
||||||
<span class={styles.buttonLabel}>{t('Unfollow')}</span>
|
<span class={styles.buttonLabel}>{t('Unfollow')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -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,70 +113,68 @@ 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" />
|
<input value="0" name="customerNumber" type="hidden" />
|
||||||
<input value="0" name="customerNumber" type="hidden" />
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="donate-buttons-container" ref={amountSwitchElement}>
|
<div class="donate-buttons-container" ref={amountSwitchElement}>
|
||||||
<input type="radio" name="amount" id="fix250" value="250" />
|
<input type="radio" name="amount" id="fix250" value="250" />
|
||||||
<label for="fix250" class="btn donate-value-radio">
|
<label for="fix250" class="btn donate-value-radio">
|
||||||
250 ₽
|
250 ₽
|
||||||
</label>
|
</label>
|
||||||
<input type="radio" name="amount" id="fix500" value="500" checked />
|
<input type="radio" name="amount" id="fix500" value="500" checked />
|
||||||
<label for="fix500" class="btn donate-value-radio">
|
<label for="fix500" class="btn donate-value-radio">
|
||||||
500 ₽
|
500 ₽
|
||||||
</label>
|
</label>
|
||||||
<input type="radio" name="amount" id="fix1000" value="1000" />
|
<input type="radio" name="amount" id="fix1000" value="1000" />
|
||||||
<label for="fix1000" class="btn donate-value-radio">
|
<label for="fix1000" class="btn donate-value-radio">
|
||||||
1000 ₽
|
1000 ₽
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
class="form-control donate-input"
|
class="form-control donate-input"
|
||||||
required
|
required
|
||||||
ref={customAmountElement}
|
ref={customAmountElement}
|
||||||
type="number"
|
type="number"
|
||||||
name="sum"
|
name="sum"
|
||||||
placeholder={t('Another amount')}
|
placeholder={t('Another amount')}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group" id="payment-type" classList={{ showing: showingPayment() }}>
|
<div class="form-group" id="payment-type" classList={{ showing: showingPayment() }}>
|
||||||
<div class="btn-group payment-choose" data-toggle="buttons">
|
<div class="btn-group payment-choose" data-toggle="buttons">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
id="once"
|
id="once"
|
||||||
name="once"
|
name="once"
|
||||||
onClick={() => setPeriod(once)}
|
onClick={() => setPeriod(once)}
|
||||||
checked={period() === once}
|
checked={period() === once}
|
||||||
/>
|
/>
|
||||||
<label for="once" class="btn payment-type" classList={{ active: period() === once }}>
|
<label for="once" class="btn payment-type" classList={{ active: period() === once }}>
|
||||||
{t('One time')}
|
{t('One time')}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
id="monthly"
|
id="monthly"
|
||||||
name="monthly"
|
name="monthly"
|
||||||
onClick={() => setPeriod(monthly)}
|
onClick={() => setPeriod(monthly)}
|
||||||
checked={period() === monthly}
|
checked={period() === monthly}
|
||||||
/>
|
/>
|
||||||
<label for="monthly" class="btn payment-type" classList={{ active: period() === monthly }}>
|
<label for="monthly" class="btn payment-type" classList={{ active: period() === monthly }}>
|
||||||
{t('Every month')}
|
{t('Every month')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<a href={''} class="btn send-btn donate" onClick={show}>
|
<a href={''} class="btn send-btn donate" onClick={show}>
|
||||||
{t('Help discours to grow')}
|
{t('Help discours to grow')}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
@include font-size(1.5rem);
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
padding-right: 0.3em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
@ -108,3 +109,7 @@ button.follow {
|
||||||
max-width: 2em;
|
max-width: 2em;
|
||||||
max-height: 2em;
|
max-height: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shoutCardContainer {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
|
@ -4,10 +4,11 @@ import { For, Show } from 'solid-js'
|
||||||
import { ArticleCard } from './Card'
|
import { ArticleCard } from './Card'
|
||||||
import { AuthorCard } from '../Author/Card'
|
import { AuthorCard } from '../Author/Card'
|
||||||
import { TopicCard } from '../Topic/Card'
|
import { TopicCard } from '../Topic/Card'
|
||||||
import style 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'
|
||||||
|
|
||||||
interface BesideProps {
|
interface BesideProps {
|
||||||
title?: string
|
title?: string
|
||||||
|
@ -29,21 +30,28 @@ export const Beside = (props: BesideProps) => {
|
||||||
<Show when={!!props.values}>
|
<Show when={!!props.values}>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<Show when={!!props.title}>
|
<Show when={!!props.title}>
|
||||||
<div class={style.besideColumnTitle}>
|
<div class={styles.besideColumnTitle}>
|
||||||
<h4>{props.title}</h4>
|
<h4>{props.title}</h4>
|
||||||
|
|
||||||
<Show when={props.wrapper === 'author'}>
|
<Show when={props.wrapper === 'author'}>
|
||||||
<a href="/authors">
|
<a href="/authors">
|
||||||
{t('All authors')}
|
{t('All authors')}
|
||||||
<Icon name="arrow-right" />
|
<Icon name="arrow-right" class={styles.icon} />
|
||||||
|
</a>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={props.wrapper === 'topic'}>
|
||||||
|
<a href="/topics">
|
||||||
|
{t('All topics')}
|
||||||
|
<Icon name="arrow-right" class={styles.icon} />
|
||||||
</a>
|
</a>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<ul class={style.besideColumn}>
|
<ul class={styles.besideColumn}>
|
||||||
<For each={[...props.values]}>
|
<For each={[...props.values]}>
|
||||||
{(value: Partial<Shout | User | Topic>) => (
|
{(value: Partial<Shout | User | Topic>) => (
|
||||||
<li classList={{ [style.top]: props.wrapper.startsWith('top-') }}>
|
<li classList={{ [styles.top]: props.wrapper.startsWith('top-') }}>
|
||||||
<Show when={props.wrapper === 'topic'}>
|
<Show when={props.wrapper === 'topic'}>
|
||||||
<TopicCard
|
<TopicCard
|
||||||
topic={value as Topic}
|
topic={value as Topic}
|
||||||
|
@ -51,10 +59,16 @@ export const Beside = (props: BesideProps) => {
|
||||||
shortDescription={props.topicShortDescription}
|
shortDescription={props.topicShortDescription}
|
||||||
isTopicInRow={props.isTopicInRow}
|
isTopicInRow={props.isTopicInRow}
|
||||||
iconButton={props.iconButton}
|
iconButton={props.iconButton}
|
||||||
|
showPublications={true}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.wrapper === 'author'}>
|
<Show when={props.wrapper === 'author'}>
|
||||||
<AuthorCard author={value as Author} compact={true} hasLink={true} />
|
<AuthorCard
|
||||||
|
author={value as Author}
|
||||||
|
compact={true}
|
||||||
|
hasLink={true}
|
||||||
|
truncateBio={true}
|
||||||
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={props.wrapper === 'article' && value?.slug}>
|
<Show when={props.wrapper === 'article' && value?.slug}>
|
||||||
<ArticleCard article={value as Shout} settings={{ noimage: true }} />
|
<ArticleCard article={value as Shout} settings={{ noimage: true }} />
|
||||||
|
@ -71,8 +85,8 @@ export const Beside = (props: BesideProps) => {
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
<div class="col-md-8">
|
<div class={clsx('col-md-8', styles.shoutCardContainer)}>
|
||||||
<ArticleCard article={props.beside} settings={{ isBigTitle: true }} />
|
<ArticleCard article={props.beside} settings={{ isBigTitle: true, isBeside: true }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -675,3 +675,19 @@
|
||||||
@include font-size(2.4rem);
|
@include font-size(2.4rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shoutCardBeside {
|
||||||
|
&,
|
||||||
|
.shoutCardCoverContainer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutCardCover {
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shoutCardContent {
|
||||||
|
padding-top: 1.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
@ -28,6 +28,7 @@ interface ArticleCardProps {
|
||||||
withBorder?: boolean
|
withBorder?: boolean
|
||||||
isCompact?: boolean
|
isCompact?: boolean
|
||||||
isSingle?: boolean
|
isSingle?: boolean
|
||||||
|
isBeside?: boolean
|
||||||
}
|
}
|
||||||
article: Shout
|
article: Shout
|
||||||
}
|
}
|
||||||
|
@ -81,7 +82,8 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
[styles.shoutCardVertical]: props.settings?.isVertical,
|
[styles.shoutCardVertical]: props.settings?.isVertical,
|
||||||
[styles.shoutCardWithBorder]: props.settings?.withBorder,
|
[styles.shoutCardWithBorder]: props.settings?.withBorder,
|
||||||
[styles.shoutCardCompact]: props.settings?.isCompact,
|
[styles.shoutCardCompact]: props.settings?.isCompact,
|
||||||
[styles.shoutCardSingle]: props.settings?.isSingle
|
[styles.shoutCardSingle]: props.settings?.isSingle,
|
||||||
|
[styles.shoutCardBeside]: props.settings?.isBeside
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={!props.settings?.noimage && cover}>
|
<Show when={!props.settings?.noimage && cover}>
|
||||||
|
@ -95,7 +97,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>
|
||||||
|
|
|
@ -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() }}>
|
||||||
|
|
|
@ -23,7 +23,7 @@ export const Row2 = (props: { articles: Shout[]; isEqual?: boolean }) => {
|
||||||
<div class={`col-md-${props.isEqual ? '6' : x[y()][i()]}`}>
|
<div class={`col-md-${props.isEqual ? '6' : x[y()][i()]}`}>
|
||||||
<ArticleCard
|
<ArticleCard
|
||||||
article={a}
|
article={a}
|
||||||
settings={{ isWithCover: props.isEqual || x[y()][i()] === '8' }}
|
settings={{ isWithCover: props.isEqual || x[y()][i()] === '8', nodate: props.isEqual }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
35
src/components/Inbox/DialogAvatar.module.scss
Normal file
35
src/components/Inbox/DialogAvatar.module.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
48
src/components/Inbox/DialogAvatar.tsx
Normal file
48
src/components/Inbox/DialogAvatar.tsx
Normal 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
|
70
src/components/Inbox/DialogCard.module.scss
Normal file
70
src/components/Inbox/DialogCard.module.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
src/components/Inbox/DialogCard.tsx
Normal file
56
src/components/Inbox/DialogCard.tsx
Normal 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
|
7
src/components/Inbox/Message.module.scss
Normal file
7
src/components/Inbox/Message.module.scss
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.Message {
|
||||||
|
.own {
|
||||||
|
}
|
||||||
|
.body {
|
||||||
|
// message text
|
||||||
|
}
|
||||||
|
}
|
17
src/components/Inbox/Message.tsx
Normal file
17
src/components/Inbox/Message.tsx
Normal 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
|
46
src/components/Inbox/Search.module.scss
Normal file
46
src/components/Inbox/Search.module.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
src/components/Inbox/Search.tsx
Normal file
27
src/components/Inbox/Search.tsx
Normal 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
|
|
@ -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'
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -2,8 +2,8 @@ import styles from './Header.module.scss'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { useRouter } from '../../stores/router'
|
import { 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'
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
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.shouts) && Boolean(props.author))
|
||||||
|
|
||||||
const slug = createMemo(() => {
|
const slug = createMemo(() => {
|
||||||
const { page: getPage } = useRouter()
|
const { page: getPage } = useRouter()
|
||||||
|
|
||||||
|
@ -29,7 +31,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)
|
||||||
|
@ -38,11 +40,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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
3
src/components/Pages/HomePage.module.scss
Normal file
3
src/components/Pages/HomePage.module.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.mainContent {
|
||||||
|
padding-top: 100px;
|
||||||
|
}
|
|
@ -1,20 +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'
|
||||||
|
|
||||||
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)
|
||||||
|
@ -23,11 +24,11 @@ export const HomePage = (props: PageProps) => {
|
||||||
onCleanup(() => resetSortedArticles())
|
onCleanup(() => resetSortedArticles())
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
src/components/Pages/InboxPage.tsx
Normal file
14
src/components/Pages/InboxPage.tsx
Normal 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
|
148
src/components/Pages/LayoutShoutsPage.tsx
Normal file
148
src/components/Pages/LayoutShoutsPage.tsx
Normal 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
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
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.shouts) && Boolean(props.topic))
|
||||||
|
|
||||||
const slug = createMemo(() => {
|
const slug = createMemo(() => {
|
||||||
const { page: getPage } = useRouter()
|
const { page: getPage } = useRouter()
|
||||||
|
|
||||||
|
@ -20,14 +22,12 @@ export const TopicPage = (props: PageProps) => {
|
||||||
return page.params.slug
|
return page.params.slug
|
||||||
})
|
})
|
||||||
|
|
||||||
const [isLoaded, setIsLoaded] = createSignal(Boolean(props.topicArticles) && props?.topic.slug === slug())
|
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (isLoaded()) {
|
if (isLoaded()) {
|
||||||
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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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">
|
||||||
|
@ -50,7 +50,7 @@ export const DogmaPage = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</MainLayout>
|
</PageWrap>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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} />*/}
|
||||||
|
@ -285,7 +285,7 @@ export const GuidePage = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</MainLayout>
|
</PageWrap>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -192,7 +192,7 @@ export const ManifestPage = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</MainLayout>
|
</PageWrap>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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} />*/}
|
||||||
|
@ -81,7 +81,7 @@ export const ThanksPage = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</MainLayout>
|
</PageWrap>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
@include font-size(2.2rem);
|
@include font-size(2.2rem);
|
||||||
|
|
||||||
margin-bottom: 1.2rem;
|
margin-bottom: 1.2rem;
|
||||||
|
margin-top: 0.5rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topicAvatar {
|
.topicAvatar {
|
||||||
|
@ -104,3 +105,13 @@
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.topicCompact {
|
||||||
|
.topicTitle {
|
||||||
|
@include font-size(1.7rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonCompact {
|
||||||
|
margin-top: 0.6rem;
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ interface TopicProps {
|
||||||
additionalClass?: string
|
additionalClass?: string
|
||||||
isTopicInRow?: boolean
|
isTopicInRow?: boolean
|
||||||
iconButton?: boolean
|
iconButton?: boolean
|
||||||
|
showPublications?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TopicCard = (props: TopicProps) => {
|
export const TopicCard = (props: TopicProps) => {
|
||||||
|
@ -47,6 +48,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
class={styles.topic}
|
class={styles.topic}
|
||||||
classList={{
|
classList={{
|
||||||
row: !props.compact && !props.subscribeButtonBottom,
|
row: !props.compact && !props.subscribeButtonBottom,
|
||||||
|
[styles.topicCompact]: props.compact,
|
||||||
[styles.topicInRow]: props.isTopicInRow
|
[styles.topicInRow]: props.isTopicInRow
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -75,7 +77,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
|
|
||||||
<Show when={props.topic?.stat}>
|
<Show when={props.topic?.stat}>
|
||||||
<div class={styles.topicDetails}>
|
<div class={styles.topicDetails}>
|
||||||
<Show when={!props.compact}>
|
<Show when={props.showPublications}>
|
||||||
<span class={styles.topicDetailsItem} classList={{ compact: props.compact }}>
|
<span class={styles.topicDetailsItem} classList={{ compact: props.compact }}>
|
||||||
{props.topic.stat?.shouts +
|
{props.topic.stat?.shouts +
|
||||||
' ' +
|
' ' +
|
||||||
|
@ -85,6 +87,8 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
locale() === 'ru' ? ['ов', '', 'а'] : ['s', '', 's']
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
</Show>
|
||||||
|
<Show when={!props.compact}>
|
||||||
<span class={styles.topicDetailsItem} classList={{ compact: props.compact }}>
|
<span class={styles.topicDetailsItem} classList={{ compact: props.compact }}>
|
||||||
{props.topic.stat?.authors +
|
{props.topic.stat?.authors +
|
||||||
' ' +
|
' ' +
|
||||||
|
@ -132,19 +136,30 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
class={styles.controlContainer}
|
class={styles.controlContainer}
|
||||||
classList={{ 'col-md-3': !props.compact && !props.subscribeButtonBottom }}
|
classList={{ 'col-md-3': !props.compact && !props.subscribeButtonBottom }}
|
||||||
>
|
>
|
||||||
|
{}
|
||||||
<Show
|
<Show
|
||||||
when={subscribed()}
|
when={subscribed()}
|
||||||
fallback={
|
fallback={
|
||||||
<button onClick={() => subscribe(true)} class="button--light button--subscribe-topic">
|
<button
|
||||||
|
onClick={() => subscribe(true)}
|
||||||
|
class="button--light button--subscribe-topic"
|
||||||
|
classList={{
|
||||||
|
[styles.buttonCompact]: props.compact
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Show when={props.iconButton}>+</Show>
|
<Show when={props.iconButton}>+</Show>
|
||||||
|
|
||||||
<Show when={!props.iconButton}>{t('Follow')}</Show>
|
<Show when={!props.iconButton}>{t('Follow')}</Show>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<button onClick={() => subscribe(false)} class="button--light button--subscribe-topic">
|
<button
|
||||||
|
onClick={() => subscribe(false)}
|
||||||
|
class="button--light button--subscribe-topic"
|
||||||
|
classList={{
|
||||||
|
[styles.buttonCompact]: props.compact
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Show when={props.iconButton}>-</Show>
|
<Show when={props.iconButton}>-</Show>
|
||||||
|
|
||||||
<Show when={!props.iconButton}>{t('Unfollow')}</Show>
|
<Show when={!props.iconButton}>{t('Unfollow')}</Show>
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.topicHeader {
|
.topicHeader {
|
||||||
@include font-size(1.7rem);
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
padding-top: 5.8rem;
|
padding-top: 2.8rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
|
|
@ -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 { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
|
@ -103,6 +103,7 @@ export const AllAuthorsView = (props: Props) => {
|
||||||
subscribed={subscribed(author.slug)}
|
subscribed={subscribed(author.slug)}
|
||||||
noSocialButtons={true}
|
noSocialButtons={true}
|
||||||
isAuthorsList={true}
|
isAuthorsList={true}
|
||||||
|
truncateBio={true}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
@ -112,7 +113,7 @@ export const AllAuthorsView = (props: Props) => {
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class={clsx(styles.loadMoreContainer, 'col-12 col-md-10')}>
|
<div class={clsx(styles.loadMoreContainer, 'col-12 col-md-10')}>
|
||||||
<button class={clsx('button', styles.loadMoreButton)} onClick={showMore}>
|
<button class={clsx('button', styles.loadMoreButton)} onClick={showMore}>
|
||||||
{t('More')}
|
{t('Load more')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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 { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
|
@ -109,7 +109,7 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class={clsx(styles.loadMoreContainer, 'col-12 col-md-10')}>
|
<div class={clsx(styles.loadMoreContainer, 'col-12 col-md-10')}>
|
||||||
<button class={clsx('button', styles.loadMoreButton)} onClick={showMore}>
|
<button class={clsx('button', styles.loadMoreButton)} onClick={showMore}>
|
||||||
{t('More')}
|
{t('Load more')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 from server
|
// FIXME author topics from 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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { t } from '../../utils/intl'
|
||||||
// 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>
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
@ -76,14 +69,7 @@ export const HomeView = (props: HomeProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={Boolean(selectedRandomLayout)}>
|
<Show when={Boolean(selectedRandomLayout)}>
|
||||||
<Group
|
<Group articles={articlesByLayout()[selectedRandomLayout]} header={''} />
|
||||||
articles={articlesByLayout()[selectedRandomLayout]}
|
|
||||||
header={
|
|
||||||
<div class="layout-icon">
|
|
||||||
<Icon name={selectedRandomLayout} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -91,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
|
||||||
})
|
})
|
||||||
|
|
|
@ -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>
|
</div>
|
||||||
<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>
|
|
||||||
</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()}>
|
||||||
<div class="conversation__message">
|
<Loading />
|
||||||
Круто, беру в оборот!
|
</Show>
|
||||||
<div class="conversation__message-details">
|
<For each={messages()}>
|
||||||
<time>14:26</time>
|
{(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 innerHTML={md.render(comment.body)} />
|
||||||
|
<div class="conversation__message-details">
|
||||||
|
<time>14:26</time>
|
||||||
|
{comment.email} id: {comment.id}
|
||||||
|
</div>
|
||||||
|
<button class="conversation__context-popup-control">
|
||||||
|
<Icon name="ellipsis" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="conversation__context-popup-control">
|
)}
|
||||||
<Icon name="ellipsis" />
|
</For>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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 class="conversation__date">
|
|
||||||
<time>12 сентября</time>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form class="conversation__message-form">
|
<div class="message-form">
|
||||||
<input type="text" placeholder="Написать сообщение" />
|
<div class="wrapper">
|
||||||
<button type="submit">
|
<div class="grow-wrap" ref={formParent}>
|
||||||
<Icon name="send-message" />
|
<textarea
|
||||||
</button>
|
value={postMessageText()}
|
||||||
</form>
|
rows={1}
|
||||||
|
onInput={(event) => handleChangeMessage(event)}
|
||||||
|
placeholder="Написать сообщение"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button type="submit" disabled={postMessageText().length === 0} onClick={handleSubmit}>
|
||||||
|
<Icon name="send-message" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,8 +3,8 @@ 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 { useRouter } from '../../stores/router'
|
import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
|
||||||
|
|
||||||
type SearchPageSearchParams = {
|
type SearchPageSearchParams = {
|
||||||
by: '' | 'relevance' | 'rating'
|
by: '' | 'relevance' | 'rating'
|
||||||
|
@ -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 (
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,21 +4,26 @@ import { Footer } from '../Discours/Footer'
|
||||||
|
|
||||||
import '../../styles/app.scss'
|
import '../../styles/app.scss'
|
||||||
import { Show } from 'solid-js'
|
import { Show } from 'solid-js'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
type MainLayoutProps = {
|
type PageWrapProps = {
|
||||||
headerTitle?: string
|
headerTitle?: string
|
||||||
children: JSX.Element
|
children: JSX.Element
|
||||||
isHeaderFixed?: boolean
|
isHeaderFixed?: boolean
|
||||||
hideFooter?: boolean
|
hideFooter?: boolean
|
||||||
|
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 (
|
||||||
<>
|
<>
|
||||||
<Header title={props.headerTitle} isHeaderFixed={isHeaderFixed} />
|
<Header title={props.headerTitle} isHeaderFixed={isHeaderFixed} />
|
||||||
<main class="main-content" classList={{ 'main-content--no-padding': !isHeaderFixed }}>
|
<main
|
||||||
|
class={clsx('main-content', props.class)}
|
||||||
|
classList={{ 'main-content--no-padding': !isHeaderFixed }}
|
||||||
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
</main>
|
</main>
|
||||||
<Show when={props.hideFooter !== true}>
|
<Show when={props.hideFooter !== true}>
|
|
@ -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[]
|
||||||
|
|
12
src/graphql/mutation/create-chat.ts
Normal file
12
src/graphql/mutation/create-chat.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
|
@ -1,9 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
mutation IncrementViewMutation($shout: String!) {
|
|
||||||
incrementView(shout: $shout) {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -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
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -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
|
16
src/graphql/query/chat-messages-load-by.ts
Normal file
16
src/graphql/query/chat-messages-load-by.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
26
src/graphql/query/chats-load.ts
Normal file
26
src/graphql/query/chats-load.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
|
@ -1,13 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
query {
|
|
||||||
getMyCollections {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
desc
|
|
||||||
slug
|
|
||||||
amount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user