commit
7c2f8370b5
|
@ -1,7 +0,0 @@
|
||||||
node_modules
|
|
||||||
public
|
|
||||||
*.cjs
|
|
||||||
dist/
|
|
||||||
.vercel/
|
|
||||||
src/graphql/client/*
|
|
||||||
src/graphql/schema/*
|
|
109
.eslintrc.cjs
109
.eslintrc.cjs
|
@ -1,109 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
plugins: ['@typescript-eslint', 'import', 'sonarjs', 'unicorn', 'promise', 'solid', 'jest'],
|
|
||||||
extends: [
|
|
||||||
'eslint:recommended',
|
|
||||||
'plugin:import/recommended',
|
|
||||||
'plugin:import/typescript',
|
|
||||||
'prettier',
|
|
||||||
'plugin:sonarjs/recommended',
|
|
||||||
'plugin:unicorn/recommended',
|
|
||||||
'plugin:promise/recommended',
|
|
||||||
'plugin:solid/recommended',
|
|
||||||
'plugin:jest/recommended',
|
|
||||||
],
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ['**/*.ts', '**/*.tsx'],
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 2021,
|
|
||||||
ecmaFeatures: { jsx: true },
|
|
||||||
sourceType: 'module',
|
|
||||||
project: './tsconfig.json',
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
// 'plugin:@typescript-eslint/recommended-requiring-type-checking', // 30-01-2024 699 problems
|
|
||||||
],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/no-unused-vars': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
argsIgnorePattern: '^_',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'warn',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
node: true,
|
|
||||||
// mocha: true,
|
|
||||||
},
|
|
||||||
globals: {},
|
|
||||||
rules: {
|
|
||||||
// Solid
|
|
||||||
'solid/reactivity': 'off',
|
|
||||||
'solid/no-innerhtml': 'off',
|
|
||||||
|
|
||||||
/** Unicorn **/
|
|
||||||
'unicorn/no-null': 'off',
|
|
||||||
'unicorn/filename-case': 'off',
|
|
||||||
'unicorn/no-array-for-each': 'off',
|
|
||||||
'unicorn/no-array-reduce': 'off',
|
|
||||||
'unicorn/prefer-string-replace-all': 'warn',
|
|
||||||
'unicorn/prevent-abbreviations': 'off',
|
|
||||||
'unicorn/prefer-module': 'off',
|
|
||||||
'unicorn/import-style': 'off',
|
|
||||||
'unicorn/numeric-separators-style': 'off',
|
|
||||||
'unicorn/prefer-node-protocol': 'off',
|
|
||||||
'unicorn/prefer-dom-node-append': 'warn',
|
|
||||||
'unicorn/prefer-top-level-await': 'warn',
|
|
||||||
'unicorn/consistent-function-scoping': 'warn',
|
|
||||||
'unicorn/no-array-callback-reference': 'warn',
|
|
||||||
'unicorn/no-array-method-this-argument': 'warn',
|
|
||||||
'unicorn/no-for-loop': 'off',
|
|
||||||
'unicorn/prefer-switch': 'warn',
|
|
||||||
|
|
||||||
'sonarjs/no-duplicate-string': ['warn', { threshold: 5 }],
|
|
||||||
'sonarjs/prefer-immediate-return': 'warn',
|
|
||||||
|
|
||||||
// Promise
|
|
||||||
'promise/catch-or-return': 'off',
|
|
||||||
'promise/always-return': 'off',
|
|
||||||
|
|
||||||
eqeqeq: 'error',
|
|
||||||
'no-param-reassign': 'error',
|
|
||||||
'no-nested-ternary': 'error',
|
|
||||||
'no-shadow': 'error',
|
|
||||||
|
|
||||||
'import/order': [
|
|
||||||
'warn',
|
|
||||||
{
|
|
||||||
groups: ['type', 'builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
|
|
||||||
distinctGroup: false,
|
|
||||||
pathGroups: [
|
|
||||||
{
|
|
||||||
pattern: '*.scss',
|
|
||||||
patternOptions: { matchBase: true },
|
|
||||||
group: 'index',
|
|
||||||
position: 'after',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'newlines-between': 'always',
|
|
||||||
alphabetize: {
|
|
||||||
order: 'asc',
|
|
||||||
caseInsensitive: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
'import/resolver': {
|
|
||||||
typescript: true,
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,31 +1,35 @@
|
||||||
name: 'deploy'
|
name: "deploy"
|
||||||
|
|
||||||
on:
|
on: [push]
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- dev
|
|
||||||
- feature/email-templates
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- uses: actions/checkout@v3
|
||||||
uses: actions/checkout@v2
|
- uses: actions/setup-node@v3
|
||||||
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '18'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm ci
|
||||||
|
|
||||||
- name: Run tests
|
- name: Install Biome
|
||||||
run: npm run check
|
run: npm install --global --save-exact @biomejs/biome
|
||||||
|
|
||||||
update_mailgun_template:
|
- name: Lint with Biome
|
||||||
|
run: npx biome ci .
|
||||||
|
|
||||||
|
- name: Lint styles
|
||||||
|
run: npm run lint:styles
|
||||||
|
|
||||||
|
- name: Check types
|
||||||
|
run: npm run typecheck
|
||||||
|
|
||||||
|
- name: Test production build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
email-templates:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Update templates on Mailgun
|
name: Update templates on Mailgun
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/feature/email-templates'
|
if: github.event_name == 'push' && github.ref == 'refs/heads/feature/email-templates'
|
||||||
|
|
42
.github/workflows/node-ci.yml
vendored
42
.github/workflows/node-ci.yml
vendored
|
@ -1,18 +1,42 @@
|
||||||
name: CI
|
name: "deploy"
|
||||||
|
|
||||||
on: [push]
|
on: [push]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v4
|
||||||
with:
|
|
||||||
node-version: '18'
|
|
||||||
|
|
||||||
- name: Install deps
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Check
|
- name: Check types
|
||||||
run: npm run check
|
run: npm run typecheck
|
||||||
|
|
||||||
|
- name: Lint with Biome
|
||||||
|
run: npx biome ci .
|
||||||
|
|
||||||
|
- name: Lint styles
|
||||||
|
run: npm run lint:styles
|
||||||
|
|
||||||
|
- name: Test production build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
e2e:
|
||||||
|
timeout-minutes: 60
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event.deployment_status.state == 'success'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: Install Playwright
|
||||||
|
run: npx playwright install --with-deps
|
||||||
|
- name: Run Playwright tests
|
||||||
|
run: npx playwright test
|
||||||
|
env:
|
||||||
|
BASE_URL: ${{ github.event.deployment_status.target_url }}
|
||||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -17,3 +17,8 @@ stats.html
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
bun.lockb
|
bun.lockb
|
||||||
.jj
|
.jj
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
/plawright-report/
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
---
|
|
||||||
stages:
|
|
||||||
- deploy
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
image:
|
|
||||||
name: alpine/git
|
|
||||||
entrypoint: [""]
|
|
||||||
stage: deploy
|
|
||||||
environment:
|
|
||||||
name: production
|
|
||||||
url: https://new.discours.io
|
|
||||||
only:
|
|
||||||
- main
|
|
||||||
script:
|
|
||||||
- mkdir ~/.ssh
|
|
||||||
- echo "${HOST_KEY}" > ~/.ssh/known_hosts
|
|
||||||
- echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa
|
|
||||||
- chmod 0400 ~/.ssh/id_rsa
|
|
||||||
- git remote add github git@github.com:Discours/discoursio-webapp.git
|
|
||||||
- git push github HEAD:main
|
|
|
@ -1,4 +0,0 @@
|
||||||
#!/usr/bin/env sh
|
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
npm run pre-push
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"*.{js,ts,tsx,json,scss,css,html}": "prettier --write",
|
"*.{js,ts,cjs,mjs,d.mts,jsx,tsx,json,jsonc,scss,css}": [
|
||||||
"package.json": "sort-package-json",
|
"npm run check"
|
||||||
"public/locales/**/*.json": "sort-json"
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"*.{js,mjs,ts,tsx,json,scss,css,html}": "prettier --write",
|
|
||||||
"package.json": "sort-package-json",
|
|
||||||
"*.{scss,css}": "stylelint",
|
|
||||||
"*.{ts,tsx,js,mjs}": "eslint --fix"
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
"htmlWhitespaceSensitivity": "ignore",
|
|
||||||
"semi": false,
|
|
||||||
"singleQuote": true,
|
|
||||||
"proseWrap": "always",
|
|
||||||
"printWidth": 108,
|
|
||||||
"plugins": [],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": "*.ts",
|
|
||||||
"options": {
|
|
||||||
"parser": "typescript"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
{
|
|
||||||
"extends": [
|
|
||||||
"stylelint-config-standard-scss"
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"stylelint-order",
|
|
||||||
"stylelint-scss"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"selector-class-pattern": null,
|
|
||||||
"no-descending-specificity": null,
|
|
||||||
"scss/function-no-unknown": null,
|
|
||||||
"scss/no-global-function-names": null,
|
|
||||||
"function-url-quotes": null,
|
|
||||||
"font-family-no-missing-generic-family-keyword": null,
|
|
||||||
"order/order": [
|
|
||||||
"custom-properties",
|
|
||||||
"declarations"
|
|
||||||
],
|
|
||||||
"scss/dollar-variable-pattern": ["^[a-z][a-zA-Z]+$", {
|
|
||||||
"ignore": "global"
|
|
||||||
}],
|
|
||||||
"selector-pseudo-class-no-unknown": [
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
"ignorePseudoClasses": ["global", "export"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"defaultSeverity": "warning"
|
|
||||||
}
|
|
|
@ -1,11 +1,6 @@
|
||||||
{
|
{
|
||||||
"extends": [
|
"extends": ["stylelint-config-standard-scss"],
|
||||||
"stylelint-config-standard-scss"
|
"plugins": ["stylelint-order", "stylelint-scss"],
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"stylelint-order",
|
|
||||||
"stylelint-scss"
|
|
||||||
],
|
|
||||||
"rules": {
|
"rules": {
|
||||||
"keyframes-name-pattern": null,
|
"keyframes-name-pattern": null,
|
||||||
"declaration-block-no-redundant-longhand-properties": null,
|
"declaration-block-no-redundant-longhand-properties": null,
|
||||||
|
@ -15,13 +10,13 @@
|
||||||
"scss/no-global-function-names": null,
|
"scss/no-global-function-names": null,
|
||||||
"function-url-quotes": null,
|
"function-url-quotes": null,
|
||||||
"font-family-no-missing-generic-family-keyword": null,
|
"font-family-no-missing-generic-family-keyword": null,
|
||||||
"order/order": [
|
"order/order": ["custom-properties", "declarations"],
|
||||||
"custom-properties",
|
"scss/dollar-variable-pattern": [
|
||||||
"declarations"
|
"^[a-z][a-zA-Z]+$",
|
||||||
|
{
|
||||||
|
"ignore": "global"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"scss/dollar-variable-pattern": ["^[a-z][a-zA-Z]+$", {
|
|
||||||
"ignore": "global"
|
|
||||||
}],
|
|
||||||
"selector-pseudo-class-no-unknown": [
|
"selector-pseudo-class-no-unknown": [
|
||||||
true,
|
true,
|
||||||
{
|
{
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2021-2023 Discours
|
Copyright (c) 2021-2024 Discours
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
14
README.md
14
README.md
|
@ -3,12 +3,6 @@
|
||||||
npm install
|
npm install
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
with different backends
|
|
||||||
```
|
|
||||||
npm run start:local
|
|
||||||
npm run start:production
|
|
||||||
npm run start:staging
|
|
||||||
```
|
|
||||||
|
|
||||||
## Useful commands
|
## Useful commands
|
||||||
run checks
|
run checks
|
||||||
|
@ -19,14 +13,18 @@ type checking with watch
|
||||||
```
|
```
|
||||||
npm run typecheck:watch
|
npm run typecheck:watch
|
||||||
```
|
```
|
||||||
|
fix styles, imports, formatting and autofixable linting errors:
|
||||||
|
```
|
||||||
|
npm run fix
|
||||||
|
```
|
||||||
## Code generation
|
## Code generation
|
||||||
|
|
||||||
generate new SolidJS component:
|
generate new SolidJS component:
|
||||||
```
|
```
|
||||||
npx hygen component new NewComponentName
|
npm run hygen component new NewComponentName
|
||||||
```
|
```
|
||||||
|
|
||||||
generate new SolidJS context:
|
generate new SolidJS context:
|
||||||
```
|
```
|
||||||
npx hygen context new NewContextName
|
npm run hygen context new NewContextName
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
const { chromium } = require('playwright')
|
|
||||||
|
|
||||||
const checkUrl = async (page, targetUrl, pageName) => {
|
|
||||||
const response = await page.goto(targetUrl)
|
|
||||||
if (response.status() > 399) {
|
|
||||||
throw new Error(`Failed with response code ${response.status()}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.screenshot({ path: `${pageName}.jpg` })
|
|
||||||
}
|
|
||||||
|
|
||||||
const browser = await chromium.launch()
|
|
||||||
const page = await browser.newPage()
|
|
||||||
|
|
||||||
const targetUrl = process.env.ENVIRONMENT_URL || 'https://testing.discours.io'
|
|
||||||
|
|
||||||
await checkUrl(page, targetUrl, 'main')
|
|
||||||
await checkUrl(page, `${targetUrl}/authors`, 'authors')
|
|
||||||
await checkUrl(page, `${targetUrl}/topics`, 'topics')
|
|
||||||
await page.close()
|
|
||||||
await browser.close()
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
73
biome.json
Normal file
73
biome.json
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/1.5.3/schema.json",
|
||||||
|
"files": {
|
||||||
|
"include": ["*.tsx", "*.ts", "*.js", "*.json"],
|
||||||
|
"ignore": ["./dist", "./node_modules", ".husky", "docs", "gen"]
|
||||||
|
},
|
||||||
|
"vcs": {
|
||||||
|
"defaultBranch": "dev",
|
||||||
|
"useIgnoreFile": true
|
||||||
|
},
|
||||||
|
"organizeImports": {
|
||||||
|
"enabled": true,
|
||||||
|
"ignore": ["./api", "./gen"]
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineWidth": 108,
|
||||||
|
"ignore": ["./src/graphql/schema", "./gen"]
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"semicolons": "asNeeded",
|
||||||
|
"quoteStyle": "single",
|
||||||
|
"trailingComma": "all",
|
||||||
|
"enabled": true,
|
||||||
|
"jsxQuoteStyle": "double",
|
||||||
|
"arrowParentheses": "always"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"ignore": ["*.scss", "*.md", ".DS_Store", "*.svg"],
|
||||||
|
"enabled": true,
|
||||||
|
"rules": {
|
||||||
|
"all": true,
|
||||||
|
"complexity": {
|
||||||
|
"noForEach": "off",
|
||||||
|
"useOptionalChain": "warn",
|
||||||
|
"useLiteralKeys": "off"
|
||||||
|
},
|
||||||
|
"correctness": {
|
||||||
|
"useHookAtTopLevel": "off"
|
||||||
|
},
|
||||||
|
"a11y": {
|
||||||
|
"useHeadingContent": "off",
|
||||||
|
"useKeyWithClickEvents": "off",
|
||||||
|
"useKeyWithMouseEvents": "off",
|
||||||
|
"useAnchorContent": "off",
|
||||||
|
"useValidAnchor": "off",
|
||||||
|
"useMediaCaption": "off",
|
||||||
|
"useAltText": "off",
|
||||||
|
"useButtonType": "off",
|
||||||
|
"noRedundantAlt": "off",
|
||||||
|
"noSvgWithoutTitle": "off"
|
||||||
|
},
|
||||||
|
"nursery": {
|
||||||
|
"useImportRestrictions": "off",
|
||||||
|
"useImportType": "off",
|
||||||
|
"useFilenamingConvention": "off"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"useBlockStatements": "off",
|
||||||
|
"noImplicitBoolean": "off",
|
||||||
|
"useNamingConvention": "off",
|
||||||
|
"noDefaultExport": "off"
|
||||||
|
},
|
||||||
|
"suspicious": {
|
||||||
|
"noConsoleLog": "off",
|
||||||
|
"noAssignInExpressions": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,8 +5,7 @@ import type { Accessor, JSX } from 'solid-js'
|
||||||
import { createContext, createSignal, useContext } from 'solid-js'
|
import { createContext, createSignal, useContext } from 'solid-js'
|
||||||
|
|
||||||
type <%= h.changeCase.pascal(name) %>ContextType = {
|
type <%= h.changeCase.pascal(name) %>ContextType = {
|
||||||
actions: {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const <%= h.changeCase.pascal(name) %>Context = createContext<<%= h.changeCase.pascal(name) %>ContextType>()
|
const <%= h.changeCase.pascal(name) %>Context = createContext<<%= h.changeCase.pascal(name) %>ContextType>()
|
||||||
|
@ -19,9 +18,7 @@ export const <%= h.changeCase.pascal(name) %>Provider = (props: { children: JSX.
|
||||||
const actions = {
|
const actions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const value: <%= h.changeCase.pascal(name) %>ContextType = { actions }
|
const value: <%= h.changeCase.pascal(name) %>ContextType = { ...actions }
|
||||||
|
|
||||||
return <<%= h.changeCase.pascal(name) %>Context.Provider value={value}>{props.children}</<%= h.changeCase.pascal(name) %>Context.Provider>
|
return <<%= h.changeCase.pascal(name) %>Context.Provider value={value}>{props.children}</<%= h.changeCase.pascal(name) %>Context.Provider>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
to: _templates/<%= name %>/<%= action || 'new' %>/hello.ejs.t
|
to: gen/<%= name %>/<%= action || 'new' %>/hello.ejs.t
|
||||||
---
|
---
|
||||||
---
|
---
|
||||||
to: app/hello.js
|
to: app/hello.js
|
||||||
|
@ -14,5 +14,3 @@ https://github.com/jondot/hygen
|
||||||
```
|
```
|
||||||
|
|
||||||
console.log(hello)
|
console.log(hello)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
to: _templates/<%= name %>/<%= action || 'new' %>/hello.ejs.t
|
to: gen/<%= name %>/<%= action || 'new' %>/hello.ejs.t
|
||||||
---
|
---
|
||||||
---
|
---
|
||||||
to: app/hello.js
|
to: app/hello.js
|
||||||
|
@ -14,5 +14,3 @@ https://github.com/jondot/hygen
|
||||||
```
|
```
|
||||||
|
|
||||||
console.log(hello)
|
console.log(hello)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
to: _templates/<%= name %>/<%= action || 'new' %>/prompt.js
|
to: gen/<%= name %>/<%= action || 'new' %>/prompt.js
|
||||||
---
|
---
|
||||||
|
|
||||||
// see types of prompts:
|
// see types of prompts:
|
4539
package-lock.json
generated
4539
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "discoursio-webapp",
|
"name": "discoursio-webapp",
|
||||||
"version": "0.9.1",
|
"version": "0.9.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -10,22 +10,22 @@
|
||||||
"codegen": "graphql-codegen",
|
"codegen": "graphql-codegen",
|
||||||
"deploy": "graphql-codegen && npm run typecheck && vite build && vercel",
|
"deploy": "graphql-codegen && npm run typecheck && vite build && vercel",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"fix": "npm run lint:code:fix && npm run lint:styles:fix",
|
"e2e": "npx playwright test --project=chromium",
|
||||||
"format": "npx prettier \"{,!(node_modules)/**/}*.{js,ts,tsx,json,scss,css}\" --write --ignore-path .gitignore",
|
"fix": "npm run check:code:fix && stylelint **/*.{scss,css} --fix",
|
||||||
|
"format": "npx @biomejs/biome format src/. --write",
|
||||||
|
"hygen": "HYGEN_TMPLS=gen hygen",
|
||||||
"postinstall": "npm run codegen",
|
"postinstall": "npm run codegen",
|
||||||
"lint": "npm run lint:code && npm run lint:styles",
|
"check:code": "npx @biomejs/biome check src --log-kind=compact --verbose",
|
||||||
"lint:code": "eslint .",
|
"check:code:fix": "npx @biomejs/biome check src --log-kind=compact --verbose --apply-unsafe",
|
||||||
"lint:code:fix": "eslint . --fix",
|
"lint": "npm run lint:code && stylelint **/*.{scss,css}",
|
||||||
|
"lint:code": "npx @biomejs/biome lint src --log-kind=compact --verbose",
|
||||||
|
"lint:code:fix": "npx @biomejs/biome lint src --apply-unsafe --log-kind=compact --verbose",
|
||||||
"lint:styles": "stylelint **/*.{scss,css}",
|
"lint:styles": "stylelint **/*.{scss,css}",
|
||||||
"lint:styles:fix": "stylelint **/*.{scss,css} --fix",
|
"lint:styles:fix": "stylelint **/*.{scss,css} --fix",
|
||||||
"pre-commit": "lint-staged",
|
"pre-commit": "lint-staged",
|
||||||
"pre-push": "npm run typecheck",
|
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"start:local": "cross-env PUBLIC_API_URL=http://127.0.0.1:8080 vite",
|
|
||||||
"start:production": "cross-env PUBLIC_API_URL=https://v2.discours.io vite",
|
|
||||||
"start:staging": "cross-env PUBLIC_API_URL=https://testapi.discours.io vite",
|
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"typecheck:watch": "tsc --noEmit --watch"
|
"typecheck:watch": "tsc --noEmit --watch"
|
||||||
},
|
},
|
||||||
|
@ -44,19 +44,21 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.23.3",
|
"@babel/core": "7.23.3",
|
||||||
|
"@biomejs/biome": "^1.5.3",
|
||||||
"@graphql-codegen/cli": "5.0.0",
|
"@graphql-codegen/cli": "5.0.0",
|
||||||
"@graphql-codegen/typescript": "4.0.1",
|
"@graphql-codegen/typescript": "4.0.1",
|
||||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||||
"@graphql-codegen/typescript-urql": "4.0.0",
|
"@graphql-codegen/typescript-urql": "4.0.0",
|
||||||
"@graphql-codegen/urql-introspection": "3.0.0",
|
"@graphql-codegen/urql-introspection": "3.0.0",
|
||||||
"@graphql-tools/url-loader": "7.17.18",
|
"@graphql-tools/url-loader": "8.0.1",
|
||||||
"@graphql-typed-document-node/core": "3.2.0",
|
"@graphql-typed-document-node/core": "3.2.0",
|
||||||
"@hocuspocus/provider": "2.0.6",
|
"@hocuspocus/provider": "2.0.6",
|
||||||
"@microsoft/fetch-event-source": "^2.0.1",
|
"@microsoft/fetch-event-source": "^2.0.1",
|
||||||
"@nanostores/router": "0.11.0",
|
"@nanostores/router": "0.13.0",
|
||||||
"@nanostores/solid": "0.4.2",
|
"@nanostores/solid": "0.4.2",
|
||||||
|
"@playwright/test": "1.41.2",
|
||||||
"@popperjs/core": "2.11.8",
|
"@popperjs/core": "2.11.8",
|
||||||
"@sentry/browser": "5.30.0",
|
"@sentry/browser": "7.99.0",
|
||||||
"@solid-primitives/media": "2.2.3",
|
"@solid-primitives/media": "2.2.3",
|
||||||
"@solid-primitives/memo": "1.2.4",
|
"@solid-primitives/memo": "1.2.4",
|
||||||
"@solid-primitives/share": "2.0.4",
|
"@solid-primitives/share": "2.0.4",
|
||||||
|
@ -95,25 +97,13 @@
|
||||||
"@tiptap/extension-youtube": "2.0.3",
|
"@tiptap/extension-youtube": "2.0.3",
|
||||||
"@types/js-cookie": "3.0.6",
|
"@types/js-cookie": "3.0.6",
|
||||||
"@types/node": "20.9.0",
|
"@types/node": "20.9.0",
|
||||||
"@typescript-eslint/eslint-plugin": "6.10.0",
|
"@urql/core": "4.2.3",
|
||||||
"@typescript-eslint/parser": "6.10.0",
|
|
||||||
"@urql/core": "3.2.2",
|
|
||||||
"@urql/devtools": "2.0.3",
|
"@urql/devtools": "2.0.3",
|
||||||
"babel-preset-solid": "1.8.4",
|
"babel-preset-solid": "1.8.4",
|
||||||
"bootstrap": "5.3.2",
|
"bootstrap": "5.3.2",
|
||||||
"change-case": "5.2.0",
|
"change-case": "5.2.0",
|
||||||
"clsx": "2.0.0",
|
"clsx": "2.0.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "8.53.0",
|
|
||||||
"eslint-config-stylelint": "20.0.0",
|
|
||||||
"eslint-import-resolver-typescript": "3.6.1",
|
|
||||||
"eslint-plugin-import": "2.29.0",
|
|
||||||
"eslint-plugin-jest": "27.6.0",
|
|
||||||
"eslint-plugin-jsx-a11y": "6.8.0",
|
|
||||||
"eslint-plugin-promise": "6.1.1",
|
|
||||||
"eslint-plugin-solid": "0.13.0",
|
|
||||||
"eslint-plugin-sonarjs": "0.23.0",
|
|
||||||
"eslint-plugin-unicorn": "49.0.0",
|
|
||||||
"fast-deep-equal": "3.1.3",
|
"fast-deep-equal": "3.1.3",
|
||||||
"graphql": "16.8.1",
|
"graphql": "16.8.1",
|
||||||
"graphql-tag": "2.12.6",
|
"graphql-tag": "2.12.6",
|
||||||
|
@ -127,8 +117,6 @@
|
||||||
"loglevel": "1.8.1",
|
"loglevel": "1.8.1",
|
||||||
"loglevel-plugin-prefix": "0.8.4",
|
"loglevel-plugin-prefix": "0.8.4",
|
||||||
"nanostores": "0.9.5",
|
"nanostores": "0.9.5",
|
||||||
"prettier": "3.1.0",
|
|
||||||
"prettier-eslint": "16.1.2",
|
|
||||||
"prosemirror-history": "1.3.0",
|
"prosemirror-history": "1.3.0",
|
||||||
"prosemirror-trailing-node": "2.0.3",
|
"prosemirror-trailing-node": "2.0.3",
|
||||||
"prosemirror-view": "1.30.2",
|
"prosemirror-view": "1.30.2",
|
||||||
|
|
77
playwright.config.ts
Normal file
77
playwright.config.ts
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import { defineConfig, devices } from '@playwright/test'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
// require('dotenv').config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './tests',
|
||||||
|
/* Run tests in files in parallel */
|
||||||
|
fullyParallel: true,
|
||||||
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
/* Retry on CI only */
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
/* Opt out of parallel tests on CI. */
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
|
reporter: 'html',
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
|
// baseURL: 'http://127.0.0.1:3000',
|
||||||
|
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Configure projects for major browsers */
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'firefox',
|
||||||
|
use: { ...devices['Desktop Firefox'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'webkit',
|
||||||
|
use: { ...devices['Desktop Safari'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Test against mobile viewports. */
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Chrome',
|
||||||
|
// use: { ...devices['Pixel 5'] },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Safari',
|
||||||
|
// use: { ...devices['iPhone 12'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against branded browsers. */
|
||||||
|
// {
|
||||||
|
// name: 'Microsoft Edge',
|
||||||
|
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Google Chrome',
|
||||||
|
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
// webServer: {
|
||||||
|
// command: 'npm run start',
|
||||||
|
// url: 'http://127.0.0.1:3000',
|
||||||
|
// reuseExistingServer: !process.env.CI,
|
||||||
|
// },
|
||||||
|
})
|
|
@ -43,7 +43,7 @@ import { ProfileSubscriptionsPage } from '../pages/profile/profileSubscriptions.
|
||||||
import { SearchPage } from '../pages/search.page'
|
import { SearchPage } from '../pages/search.page'
|
||||||
import { TopicPage } from '../pages/topic.page'
|
import { TopicPage } from '../pages/topic.page'
|
||||||
import { ROUTES, useRouter } from '../stores/router'
|
import { ROUTES, useRouter } from '../stores/router'
|
||||||
import { hideModal, MODALS, showModal } from '../stores/ui'
|
import { MODALS, hideModal, showModal } from '../stores/ui'
|
||||||
|
|
||||||
// TODO: lazy load
|
// TODO: lazy load
|
||||||
// const SomePage = lazy(() => import('./Pages/SomePage'))
|
// const SomePage = lazy(() => import('./Pages/SomePage'))
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, Show } from 'solid-js'
|
import { Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { Topic } from '../../../graphql/schema/core.gen'
|
import { Topic } from '../../../graphql/schema/core.gen'
|
||||||
import { MediaItem } from '../../../pages/types'
|
import { MediaItem } from '../../../pages/types'
|
||||||
|
import { CardTopic } from '../../Feed/CardTopic'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { Image } from '../../_shared/Image'
|
import { Image } from '../../_shared/Image'
|
||||||
import { CardTopic } from '../../Feed/CardTopic'
|
|
||||||
|
|
||||||
import styles from './AudioHeader.module.scss'
|
import styles from './AudioHeader.module.scss'
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { createEffect, createMemo, createSignal, on, onMount, Show } from 'solid-js'
|
import { Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
|
||||||
|
|
||||||
import { MediaItem } from '../../../pages/types'
|
import { MediaItem } from '../../../pages/types'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, Show } from 'solid-js'
|
import { Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { MediaItem } from '../../../pages/types'
|
import { MediaItem } from '../../../pages/types'
|
||||||
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
|
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { gtag } from 'ga-gtag'
|
import { gtag } from 'ga-gtag'
|
||||||
import { createSignal, For, lazy, Show } from 'solid-js'
|
import { For, Show, createSignal, lazy } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { MediaItem } from '../../../pages/types'
|
import { MediaItem } from '../../../pages/types'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { getPagePath } from '@nanostores/router'
|
import { getPagePath } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { Show, createMemo, createSignal, For, lazy, Suspense } from 'solid-js'
|
import { For, Show, Suspense, createMemo, createSignal, lazy } from 'solid-js'
|
||||||
|
|
||||||
import { useConfirm } from '../../../context/confirm'
|
import { useConfirm } from '../../../context/confirm'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
@ -9,10 +9,10 @@ import { useSession } from '../../../context/session'
|
||||||
import { useSnackbar } from '../../../context/snackbar'
|
import { useSnackbar } from '../../../context/snackbar'
|
||||||
import { Author, Reaction, ReactionKind } from '../../../graphql/schema/core.gen'
|
import { Author, Reaction, ReactionKind } from '../../../graphql/schema/core.gen'
|
||||||
import { router } from '../../../stores/router'
|
import { router } from '../../../stores/router'
|
||||||
import { Icon } from '../../_shared/Icon'
|
|
||||||
import { ShowIfAuthenticated } from '../../_shared/ShowIfAuthenticated'
|
|
||||||
import { AuthorLink } from '../../Author/AuthorLink'
|
import { AuthorLink } from '../../Author/AuthorLink'
|
||||||
import { Userpic } from '../../Author/Userpic'
|
import { Userpic } from '../../Author/Userpic'
|
||||||
|
import { Icon } from '../../_shared/Icon'
|
||||||
|
import { ShowIfAuthenticated } from '../../_shared/ShowIfAuthenticated'
|
||||||
import { CommentDate } from '../CommentDate'
|
import { CommentDate } from '../CommentDate'
|
||||||
import { CommentRatingControl } from '../CommentRatingControl'
|
import { CommentRatingControl } from '../CommentRatingControl'
|
||||||
|
|
||||||
|
@ -39,23 +39,14 @@ export const Comment = (props: Props) => {
|
||||||
const [editMode, setEditMode] = createSignal(false)
|
const [editMode, setEditMode] = createSignal(false)
|
||||||
const [clearEditor, setClearEditor] = createSignal(false)
|
const [clearEditor, setClearEditor] = createSignal(false)
|
||||||
const { author } = useSession()
|
const { author } = useSession()
|
||||||
|
const { createReaction, deleteReaction, updateReaction } = useReactions()
|
||||||
const {
|
const { showConfirm } = useConfirm()
|
||||||
actions: { createReaction, deleteReaction, updateReaction },
|
const { showSnackbar } = useSnackbar()
|
||||||
} = useReactions()
|
|
||||||
|
|
||||||
const {
|
|
||||||
actions: { showConfirm },
|
|
||||||
} = useConfirm()
|
|
||||||
|
|
||||||
const {
|
|
||||||
actions: { showSnackbar },
|
|
||||||
} = useSnackbar()
|
|
||||||
|
|
||||||
const isCommentAuthor = createMemo(() => props.comment.created_by?.slug === author()?.slug)
|
const isCommentAuthor = createMemo(() => props.comment.created_by?.slug === author()?.slug)
|
||||||
|
|
||||||
const comment = createMemo(() => props.comment)
|
const comment = createMemo(() => props.comment)
|
||||||
const body = createMemo(() => (comment().body || '').trim())
|
const body = createMemo(() => (comment().body || '').trim())
|
||||||
|
|
||||||
const remove = async () => {
|
const remove = async () => {
|
||||||
if (comment()?.id) {
|
if (comment()?.id) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -19,13 +19,8 @@ type Props = {
|
||||||
export const CommentRatingControl = (props: Props) => {
|
export const CommentRatingControl = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { author } = useSession()
|
const { author } = useSession()
|
||||||
const {
|
const { showSnackbar } = useSnackbar()
|
||||||
actions: { showSnackbar },
|
const { reactionEntities, createReaction, deleteReaction, loadReactionsBy } = useReactions()
|
||||||
} = useSnackbar()
|
|
||||||
const {
|
|
||||||
reactionEntities,
|
|
||||||
actions: { createReaction, deleteReaction, loadReactionsBy },
|
|
||||||
} = useReactions()
|
|
||||||
|
|
||||||
const checkReaction = (reactionKind: ReactionKind) =>
|
const checkReaction = (reactionKind: ReactionKind) =>
|
||||||
Object.values(reactionEntities).some(
|
Object.values(reactionEntities).some(
|
||||||
|
@ -86,7 +81,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
<div class={styles.commentRating}>
|
<div class={styles.commentRating}>
|
||||||
<button
|
<button
|
||||||
role="button"
|
role="button"
|
||||||
disabled={!canVote() || !author()}
|
disabled={!(canVote() && author())}
|
||||||
onClick={() => handleRatingChange(true)}
|
onClick={() => handleRatingChange(true)}
|
||||||
class={clsx(styles.commentRatingControl, styles.commentRatingControlUp, {
|
class={clsx(styles.commentRatingControl, styles.commentRatingControlUp, {
|
||||||
[styles.voted]: isUpvoted(),
|
[styles.voted]: isUpvoted(),
|
||||||
|
@ -112,7 +107,7 @@ export const CommentRatingControl = (props: Props) => {
|
||||||
</Popup>
|
</Popup>
|
||||||
<button
|
<button
|
||||||
role="button"
|
role="button"
|
||||||
disabled={!canVote() || !author()}
|
disabled={!(canVote() && author())}
|
||||||
onClick={() => handleRatingChange(false)}
|
onClick={() => handleRatingChange(false)}
|
||||||
class={clsx(styles.commentRatingControl, styles.commentRatingControlDown, {
|
class={clsx(styles.commentRatingControl, styles.commentRatingControlDown, {
|
||||||
[styles.voted]: isDownvoted(),
|
[styles.voted]: isDownvoted(),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { Show, createMemo, createSignal, onMount, For, lazy } from 'solid-js'
|
import { For, Show, createMemo, createSignal, lazy, onMount } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { useReactions } from '../../context/reactions'
|
import { useReactions } from '../../context/reactions'
|
||||||
|
@ -22,8 +22,8 @@ const sortCommentsByRating = (a: Reaction, b: Reaction): -1 | 0 | 1 => {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const x = (a?.stat && a.stat.rating) || 0
|
const x = a.stat?.rating || 0
|
||||||
const y = (b?.stat && b.stat.rating) || 0
|
const y = b.stat?.rating || 0
|
||||||
|
|
||||||
if (x > y) {
|
if (x > y) {
|
||||||
return 1
|
return 1
|
||||||
|
@ -49,11 +49,7 @@ export const CommentsTree = (props: Props) => {
|
||||||
const [newReactions, setNewReactions] = createSignal<Reaction[]>([])
|
const [newReactions, setNewReactions] = createSignal<Reaction[]>([])
|
||||||
const [clearEditor, setClearEditor] = createSignal(false)
|
const [clearEditor, setClearEditor] = createSignal(false)
|
||||||
const [clickedReplyId, setClickedReplyId] = createSignal<number>()
|
const [clickedReplyId, setClickedReplyId] = createSignal<number>()
|
||||||
|
const { reactionEntities, createReaction } = useReactions()
|
||||||
const {
|
|
||||||
reactionEntities,
|
|
||||||
actions: { createReaction },
|
|
||||||
} = useReactions()
|
|
||||||
|
|
||||||
const comments = createMemo(() =>
|
const comments = createMemo(() =>
|
||||||
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT'),
|
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT'),
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import type { CoverImageProps } from './types'
|
import type { CoverImageProps } from './types'
|
||||||
|
|
||||||
import { CoverImage1 } from './images/CoverImage1'
|
import { CoverImage1 } from './images/CoverImage1'
|
||||||
import { CoverImage10 } from './images/CoverImage10'
|
|
||||||
import { CoverImage11 } from './images/CoverImage11'
|
|
||||||
import { CoverImage12 } from './images/CoverImage12'
|
|
||||||
import { CoverImage2 } from './images/CoverImage2'
|
import { CoverImage2 } from './images/CoverImage2'
|
||||||
import { CoverImage3 } from './images/CoverImage3'
|
import { CoverImage3 } from './images/CoverImage3'
|
||||||
import { CoverImage4 } from './images/CoverImage4'
|
import { CoverImage4 } from './images/CoverImage4'
|
||||||
|
@ -12,6 +9,9 @@ import { CoverImage6 } from './images/CoverImage6'
|
||||||
import { CoverImage7 } from './images/CoverImage7'
|
import { CoverImage7 } from './images/CoverImage7'
|
||||||
import { CoverImage8 } from './images/CoverImage8'
|
import { CoverImage8 } from './images/CoverImage8'
|
||||||
import { CoverImage9 } from './images/CoverImage9'
|
import { CoverImage9 } from './images/CoverImage9'
|
||||||
|
import { CoverImage10 } from './images/CoverImage10'
|
||||||
|
import { CoverImage11 } from './images/CoverImage11'
|
||||||
|
import { CoverImage12 } from './images/CoverImage12'
|
||||||
|
|
||||||
// not pretty, but I don't want to use dynamic imports
|
// not pretty, but I don't want to use dynamic imports
|
||||||
const coverImages = [
|
const coverImages = [
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { createPopper } from '@popperjs/core'
|
||||||
import { Link, Meta } from '@solidjs/meta'
|
import { Link, Meta } from '@solidjs/meta'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { install } from 'ga-gtag'
|
import { install } from 'ga-gtag'
|
||||||
import { createEffect, For, createMemo, onMount, Show, createSignal, onCleanup, on } from 'solid-js'
|
import { For, Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js'
|
||||||
import { isServer } from 'solid-js/web'
|
import { isServer } from 'solid-js/web'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
@ -15,9 +15,14 @@ import { MediaItem } from '../../pages/types'
|
||||||
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
|
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
|
||||||
import { showModal } from '../../stores/ui'
|
import { showModal } from '../../stores/ui'
|
||||||
import { capitalize } from '../../utils/capitalize'
|
import { capitalize } from '../../utils/capitalize'
|
||||||
import { isCyrillic } from '../../utils/cyrillic'
|
|
||||||
import { getImageUrl, getOpenGraphImageUrl } from '../../utils/getImageUrl'
|
import { getImageUrl, getOpenGraphImageUrl } from '../../utils/getImageUrl'
|
||||||
import { getDescription, getKeywords } from '../../utils/meta'
|
import { getDescription, getKeywords } from '../../utils/meta'
|
||||||
|
import { isCyrillic } from '../../utils/translate'
|
||||||
|
import { AuthorBadge } from '../Author/AuthorBadge'
|
||||||
|
import { CardTopic } from '../Feed/CardTopic'
|
||||||
|
import { FeedArticlePopup } from '../Feed/FeedArticlePopup'
|
||||||
|
import { Modal } from '../Nav/Modal'
|
||||||
|
import { TableOfContents } from '../TableOfContents'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { Image } from '../_shared/Image'
|
import { Image } from '../_shared/Image'
|
||||||
import { InviteMembers } from '../_shared/InviteMembers'
|
import { InviteMembers } from '../_shared/InviteMembers'
|
||||||
|
@ -26,20 +31,15 @@ import { Popover } from '../_shared/Popover'
|
||||||
import { ShareModal } from '../_shared/ShareModal'
|
import { ShareModal } from '../_shared/ShareModal'
|
||||||
import { ImageSwiper } from '../_shared/SolidSwiper'
|
import { ImageSwiper } from '../_shared/SolidSwiper'
|
||||||
import { VideoPlayer } from '../_shared/VideoPlayer'
|
import { VideoPlayer } from '../_shared/VideoPlayer'
|
||||||
import { AuthorBadge } from '../Author/AuthorBadge'
|
|
||||||
import { CardTopic } from '../Feed/CardTopic'
|
|
||||||
import { FeedArticlePopup } from '../Feed/FeedArticlePopup'
|
|
||||||
import { Modal } from '../Nav/Modal'
|
|
||||||
import { TableOfContents } from '../TableOfContents'
|
|
||||||
|
|
||||||
import { AudioHeader } from './AudioHeader'
|
import { AudioHeader } from './AudioHeader'
|
||||||
import { AudioPlayer } from './AudioPlayer'
|
import { AudioPlayer } from './AudioPlayer'
|
||||||
import { CommentsTree } from './CommentsTree'
|
import { CommentsTree } from './CommentsTree'
|
||||||
import { getShareUrl, SharePopup } from './SharePopup'
|
import { SharePopup, getShareUrl } from './SharePopup'
|
||||||
import { ShoutRatingControl } from './ShoutRatingControl'
|
import { ShoutRatingControl } from './ShoutRatingControl'
|
||||||
|
|
||||||
import styles from './Article.module.scss'
|
|
||||||
import stylesHeader from '../Nav/Header/Header.module.scss'
|
import stylesHeader from '../Nav/Header/Header.module.scss'
|
||||||
|
import styles from './Article.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
article: Shout
|
article: Shout
|
||||||
|
@ -69,32 +69,27 @@ const scrollTo = (el: HTMLElement) => {
|
||||||
const imgSrcRegExp = /<img[^>]+src\s*=\s*["']([^"']+)["']/gi
|
const imgSrcRegExp = /<img[^>]+src\s*=\s*["']([^"']+)["']/gi
|
||||||
|
|
||||||
export const FullArticle = (props: Props) => {
|
export const FullArticle = (props: Props) => {
|
||||||
|
const { searchParams, changeSearchParams } = useRouter<ArticlePageSearchParams>()
|
||||||
|
const { loadReactionsBy } = useReactions()
|
||||||
const [selectedImage, setSelectedImage] = createSignal('')
|
const [selectedImage, setSelectedImage] = createSignal('')
|
||||||
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
|
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
|
||||||
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
|
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
|
||||||
|
|
||||||
const { t, formatDate, lang } = useLocalize()
|
const { t, formatDate, lang } = useLocalize()
|
||||||
const {
|
const { author, isAuthenticated, requireAuthentication } = useSession()
|
||||||
author,
|
|
||||||
isAuthenticated,
|
|
||||||
actions: { requireAuthentication },
|
|
||||||
} = useSession()
|
|
||||||
|
|
||||||
const formattedDate = createMemo(() => formatDate(new Date(props.article.published_at * 1000)))
|
const formattedDate = createMemo(() => formatDate(new Date(props.article.published_at * 1000)))
|
||||||
|
const canEdit = () => props.article.authors?.some((a) => Boolean(a) && a?.slug === author()?.slug)
|
||||||
|
|
||||||
const mainTopic = createMemo(() => {
|
const mainTopic = createMemo(() => {
|
||||||
const main_topic_slug = props.article.topics.length > 0 ? props.article.main_topic : null
|
const mainTopicSlug = props.article.topics.length > 0 ? props.article.main_topic : null
|
||||||
const mt = props.article.topics.find((tpc: Topic) => tpc.slug === main_topic_slug)
|
const mt = props.article.topics.find((tpc: Topic) => tpc.slug === mainTopicSlug)
|
||||||
if (mt) {
|
if (mt) {
|
||||||
mt.title = lang() === 'en' ? capitalize(mt.slug.replace(/-/, ' ')) : mt.title
|
mt.title = lang() === 'en' ? capitalize(mt.slug.replace(/-/, ' ')) : mt.title
|
||||||
return mt
|
return mt
|
||||||
} else {
|
|
||||||
return props.article.topics[0]
|
|
||||||
}
|
}
|
||||||
|
return props.article.topics[0]
|
||||||
})
|
})
|
||||||
|
|
||||||
const canEdit = () => props.article.authors?.some((a) => Boolean(a) && a?.slug === author()?.slug)
|
|
||||||
|
|
||||||
const handleBookmarkButtonClick = (ev) => {
|
const handleBookmarkButtonClick = (ev) => {
|
||||||
requireAuthentication(() => {
|
requireAuthentication(() => {
|
||||||
// TODO: implement bookmark clicked
|
// TODO: implement bookmark clicked
|
||||||
|
@ -154,8 +149,6 @@ export const FullArticle = (props: Props) => {
|
||||||
scrollTo(commentsRef.current)
|
scrollTo(commentsRef.current)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { searchParams, changeSearchParams } = useRouter<ArticlePageSearchParams>()
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (props.scrollToComments) {
|
if (props.scrollToComments) {
|
||||||
scrollToComments()
|
scrollToComments()
|
||||||
|
@ -185,10 +178,6 @@ export const FullArticle = (props: Props) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
|
||||||
actions: { loadReactionsBy },
|
|
||||||
} = useReactions()
|
|
||||||
|
|
||||||
const clickHandlers = []
|
const clickHandlers = []
|
||||||
const documentClickHandlers = []
|
const documentClickHandlers = []
|
||||||
|
|
||||||
|
@ -284,7 +273,7 @@ export const FullArticle = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleArticleBodyClick = (event) => {
|
const handleArticleBodyClick = (event) => {
|
||||||
if (event.target.tagName === 'IMG' && !event.target.dataset['disableLightbox']) {
|
if (event.target.tagName === 'IMG' && !event.target.dataset.disableLightbox) {
|
||||||
const src = event.target.src
|
const src = event.target.src
|
||||||
openLightbox(getImageUrl(src))
|
openLightbox(getImageUrl(src))
|
||||||
}
|
}
|
||||||
|
@ -293,7 +282,7 @@ export const FullArticle = (props: Props) => {
|
||||||
// Check iframes size
|
// Check iframes size
|
||||||
const articleContainer: { current: HTMLElement } = { current: null }
|
const articleContainer: { current: HTMLElement } = { current: null }
|
||||||
const updateIframeSizes = () => {
|
const updateIframeSizes = () => {
|
||||||
if (!articleContainer?.current || !props.article.body) return
|
if (!(articleContainer?.current && props.article.body)) return
|
||||||
const iframes = articleContainer?.current?.querySelectorAll('iframe')
|
const iframes = articleContainer?.current?.querySelectorAll('iframe')
|
||||||
if (!iframes) return
|
if (!iframes) return
|
||||||
const containerWidth = articleContainer.current?.offsetWidth
|
const containerWidth = articleContainer.current?.offsetWidth
|
||||||
|
@ -337,8 +326,8 @@ export const FullArticle = (props: Props) => {
|
||||||
const cover = props.article.cover ?? 'production/image/logo_image.png'
|
const cover = props.article.cover ?? 'production/image/logo_image.png'
|
||||||
const ogImage = getOpenGraphImageUrl(cover, {
|
const ogImage = getOpenGraphImageUrl(cover, {
|
||||||
title: props.article.title,
|
title: props.article.title,
|
||||||
topic: mainTopic().title,
|
topic: mainTopic()?.title || '',
|
||||||
author: props.article.authors[0].name,
|
author: props.article?.authors[0]?.name || '',
|
||||||
width: 1200,
|
width: 1200,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createMemo, createSignal, Show } from 'solid-js'
|
import { Show, createMemo, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { useReactions } from '../../context/reactions'
|
import { useReactions } from '../../context/reactions'
|
||||||
|
@ -19,16 +19,8 @@ interface ShoutRatingControlProps {
|
||||||
|
|
||||||
export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const {
|
const { author, requireAuthentication } = useSession()
|
||||||
author,
|
const { reactionEntities, createReaction, deleteReaction, loadReactionsBy } = useReactions()
|
||||||
actions: { requireAuthentication },
|
|
||||||
} = useSession()
|
|
||||||
|
|
||||||
const {
|
|
||||||
reactionEntities,
|
|
||||||
actions: { createReaction, deleteReaction, loadReactionsBy },
|
|
||||||
} = useReactions()
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = createSignal(false)
|
const [isLoading, setIsLoading] = createSignal(false)
|
||||||
|
|
||||||
const checkReaction = (reactionKind: ReactionKind) =>
|
const checkReaction = (reactionKind: ReactionKind) =>
|
||||||
|
@ -60,7 +52,7 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||||
return deleteReaction(reactionToDelete.id)
|
return deleteReaction(reactionToDelete.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRatingChange = async (isUpvote: boolean) => {
|
const handleRatingChange = (isUpvote: boolean) => {
|
||||||
requireAuthentication(async () => {
|
requireAuthentication(async () => {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
if (isUpvoted()) {
|
if (isUpvoted()) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { JSX, createSignal } from 'solid-js'
|
||||||
import './Tooltip.scss'
|
import './Tooltip.scss'
|
||||||
import { createSignal, JSX } from 'solid-js'
|
|
||||||
|
|
||||||
interface TooltipProps {
|
interface TooltipProps {
|
||||||
children?: JSX.Element
|
children?: JSX.Element
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { createEffect, JSX, Show } from 'solid-js'
|
import { JSX, Show, createEffect } from 'solid-js'
|
||||||
|
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
import { RootSearchParams } from '../../pages/types'
|
import { RootSearchParams } from '../../pages/types'
|
||||||
|
@ -15,7 +15,7 @@ export const AuthGuard = (props: Props) => {
|
||||||
const { isAuthenticated, isSessionLoaded } = useSession()
|
const { isAuthenticated, isSessionLoaded } = useSession()
|
||||||
const { changeSearchParams } = useRouter<RootSearchParams & AuthModalSearchParams>()
|
const { changeSearchParams } = useRouter<RootSearchParams & AuthModalSearchParams>()
|
||||||
|
|
||||||
createEffect(async () => {
|
createEffect(() => {
|
||||||
if (props.disabled) {
|
if (props.disabled) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { openPage } from '@nanostores/router'
|
import { openPage } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createEffect, createMemo, createSignal, Match, on, Show, Switch } from 'solid-js'
|
import { Match, Show, Switch, createEffect, createMemo, createSignal, on } from 'solid-js'
|
||||||
|
|
||||||
import { useFollowing } from '../../../context/following'
|
import { useFollowing } from '../../../context/following'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
@ -8,16 +8,16 @@ import { useMediaQuery } from '../../../context/mediaQuery'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
import { Author, FollowingEntity } from '../../../graphql/schema/core.gen'
|
import { Author, FollowingEntity } from '../../../graphql/schema/core.gen'
|
||||||
import { router, useRouter } from '../../../stores/router'
|
import { router, useRouter } from '../../../stores/router'
|
||||||
import { isCyrillic } from '../../../utils/cyrillic'
|
|
||||||
import { translit } from '../../../utils/ru2en'
|
import { translit } from '../../../utils/ru2en'
|
||||||
|
import { isCyrillic } from '../../../utils/translate'
|
||||||
import { Button } from '../../_shared/Button'
|
import { Button } from '../../_shared/Button'
|
||||||
import { CheckButton } from '../../_shared/CheckButton'
|
import { CheckButton } from '../../_shared/CheckButton'
|
||||||
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { Userpic } from '../Userpic'
|
import { Userpic } from '../Userpic'
|
||||||
|
|
||||||
import styles from './AuthorBadge.module.scss'
|
|
||||||
import stylesButton from '../../_shared/Button/Button.module.scss'
|
import stylesButton from '../../_shared/Button/Button.module.scss'
|
||||||
|
import styles from './AuthorBadge.module.scss'
|
||||||
|
|
||||||
type FollowedInfo = {
|
type FollowedInfo = {
|
||||||
value?: boolean
|
value?: boolean
|
||||||
|
@ -36,11 +36,7 @@ type Props = {
|
||||||
}
|
}
|
||||||
export const AuthorBadge = (props: Props) => {
|
export const AuthorBadge = (props: Props) => {
|
||||||
const { mediaMatches } = useMediaQuery()
|
const { mediaMatches } = useMediaQuery()
|
||||||
const {
|
const { author, requireAuthentication } = useSession()
|
||||||
author,
|
|
||||||
actions: { requireAuthentication },
|
|
||||||
} = useSession()
|
|
||||||
|
|
||||||
const [isMobileView, setIsMobileView] = createSignal(false)
|
const [isMobileView, setIsMobileView] = createSignal(false)
|
||||||
const [isFollowed, setIsFollowed] = createSignal<boolean>()
|
const [isFollowed, setIsFollowed] = createSignal<boolean>()
|
||||||
|
|
||||||
|
@ -55,7 +51,7 @@ export const AuthorBadge = (props: Props) => {
|
||||||
const initChat = () => {
|
const initChat = () => {
|
||||||
// eslint-disable-next-line solid/reactivity
|
// eslint-disable-next-line solid/reactivity
|
||||||
requireAuthentication(() => {
|
requireAuthentication(() => {
|
||||||
openPage(router, `inbox`)
|
openPage(router, 'inbox')
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
initChat: props.author.id.toString(),
|
initChat: props.author.id.toString(),
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { Author, Community } from '../../../graphql/schema/core.gen'
|
||||||
|
|
||||||
import { openPage, redirectPage } from '@nanostores/router'
|
import { openPage, redirectPage } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createEffect, createMemo, createSignal, For, onMount, Show } from 'solid-js'
|
import { For, Show, createEffect, createMemo, createSignal, onMount } from 'solid-js'
|
||||||
|
|
||||||
import { useFollowing } from '../../../context/following'
|
import { useFollowing } from '../../../context/following'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
@ -10,19 +10,19 @@ import { useSession } from '../../../context/session'
|
||||||
import { FollowingEntity, Topic } from '../../../graphql/schema/core.gen'
|
import { FollowingEntity, Topic } from '../../../graphql/schema/core.gen'
|
||||||
import { SubscriptionFilter } from '../../../pages/types'
|
import { SubscriptionFilter } from '../../../pages/types'
|
||||||
import { router, useRouter } from '../../../stores/router'
|
import { router, useRouter } from '../../../stores/router'
|
||||||
import { isCyrillic } from '../../../utils/cyrillic'
|
|
||||||
import { isAuthor } from '../../../utils/isAuthor'
|
import { isAuthor } from '../../../utils/isAuthor'
|
||||||
import { translit } from '../../../utils/ru2en'
|
import { translit } from '../../../utils/ru2en'
|
||||||
import { Button } from '../../_shared/Button'
|
import { isCyrillic } from '../../../utils/translate'
|
||||||
import { ShowOnlyOnClient } from '../../_shared/ShowOnlyOnClient'
|
import { SharePopup, getShareUrl } from '../../Article/SharePopup'
|
||||||
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
|
|
||||||
import { Modal } from '../../Nav/Modal'
|
import { Modal } from '../../Nav/Modal'
|
||||||
import { TopicBadge } from '../../Topic/TopicBadge'
|
import { TopicBadge } from '../../Topic/TopicBadge'
|
||||||
|
import { Button } from '../../_shared/Button'
|
||||||
|
import { ShowOnlyOnClient } from '../../_shared/ShowOnlyOnClient'
|
||||||
import { AuthorBadge } from '../AuthorBadge'
|
import { AuthorBadge } from '../AuthorBadge'
|
||||||
import { Userpic } from '../Userpic'
|
import { Userpic } from '../Userpic'
|
||||||
|
|
||||||
import styles from './AuthorCard.module.scss'
|
|
||||||
import stylesButton from '../../_shared/Button/Button.module.scss'
|
import stylesButton from '../../_shared/Button/Button.module.scss'
|
||||||
|
import styles from './AuthorCard.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
author: Author
|
author: Author
|
||||||
|
@ -31,12 +31,7 @@ type Props = {
|
||||||
}
|
}
|
||||||
export const AuthorCard = (props: Props) => {
|
export const AuthorCard = (props: Props) => {
|
||||||
const { t, lang } = useLocalize()
|
const { t, lang } = useLocalize()
|
||||||
const {
|
const { author, isSessionLoaded, requireAuthentication } = useSession()
|
||||||
author,
|
|
||||||
isSessionLoaded,
|
|
||||||
actions: { requireAuthentication },
|
|
||||||
} = useSession()
|
|
||||||
|
|
||||||
const [authorSubs, setAuthorSubs] = createSignal<Array<Author | Topic | Community>>([])
|
const [authorSubs, setAuthorSubs] = createSignal<Array<Author | Topic | Community>>([])
|
||||||
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
|
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
|
||||||
const [isFollowed, setIsFollowed] = createSignal<boolean>()
|
const [isFollowed, setIsFollowed] = createSignal<boolean>()
|
||||||
|
@ -66,7 +61,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
const initChat = () => {
|
const initChat = () => {
|
||||||
// eslint-disable-next-line solid/reactivity
|
// eslint-disable-next-line solid/reactivity
|
||||||
requireAuthentication(() => {
|
requireAuthentication(() => {
|
||||||
openPage(router, `inbox`)
|
openPage(router, 'inbox')
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
initChat: props.author.id.toString(),
|
initChat: props.author.id.toString(),
|
||||||
})
|
})
|
||||||
|
@ -157,7 +152,9 @@ export const AuthorCard = (props: Props) => {
|
||||||
class={styles.subscribersItem}
|
class={styles.subscribersItem}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else if ('title' in f) {
|
}
|
||||||
|
|
||||||
|
if ('title' in f) {
|
||||||
return (
|
return (
|
||||||
<Userpic
|
<Userpic
|
||||||
size={'XS'}
|
size={'XS'}
|
||||||
|
@ -167,6 +164,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}}
|
}}
|
||||||
</For>
|
</For>
|
||||||
|
@ -188,7 +186,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
class={styles.socialLink}
|
class={styles.socialLink}
|
||||||
href={link.startsWith('http') ? link : `https://${link}`}
|
href={link.startsWith('http') ? link : `https://${link}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="nofollow noopener"
|
rel="nofollow noopener noreferrer"
|
||||||
>
|
>
|
||||||
<span class={styles.authorSubscribeSocialLabel}>
|
<span class={styles.authorSubscribeSocialLabel}>
|
||||||
{link.startsWith('http') ? link : `https://${link}`}
|
{link.startsWith('http') ? link : `https://${link}`}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import { createMemo } from 'solid-js'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { Author } from '../../../graphql/schema/core.gen'
|
import { Author } from '../../../graphql/schema/core.gen'
|
||||||
import { capitalize } from '../../../utils/capitalize'
|
import { capitalize } from '../../../utils/capitalize'
|
||||||
import { isCyrillic } from '../../../utils/cyrillic'
|
|
||||||
import { translit } from '../../../utils/ru2en'
|
import { translit } from '../../../utils/ru2en'
|
||||||
|
import { isCyrillic } from '../../../utils/translate'
|
||||||
import { Userpic } from '../Userpic'
|
import { Userpic } from '../Userpic'
|
||||||
|
|
||||||
import styles from './AhtorLink.module.scss'
|
import styles from './AhtorLink.module.scss'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createMemo, Show } from 'solid-js'
|
import { Show, createMemo } from 'solid-js'
|
||||||
|
|
||||||
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
||||||
import { Image } from '../../_shared/Image'
|
import { Image } from '../../_shared/Image'
|
||||||
|
@ -22,7 +22,7 @@ export const Userpic = (props: Props) => {
|
||||||
const letters = () => {
|
const letters = () => {
|
||||||
if (!props.name) return
|
if (!props.name) return
|
||||||
const names = props.name ? props.name.split(' ') : []
|
const names = props.name ? props.name.split(' ') : []
|
||||||
return names[0][0 ?? names[0][0]] + '.' + (names.length > 1 ? names[1][0] + '.' : '')
|
return `${names[0][0 ?? names[0][0]]}.${names.length > 1 ? `${names[1][0]}.` : ''}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const avatarSize = createMemo(() => {
|
const avatarSize = createMemo(() => {
|
||||||
|
@ -48,7 +48,7 @@ export const Userpic = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.Userpic, props.class, styles[props.size ?? 'M'], {
|
class={clsx(styles.Userpic, props.class, styles[props.size ?? 'M'], {
|
||||||
['cursorPointer']: props.onClick,
|
cursorPointer: props.onClick,
|
||||||
})}
|
})}
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
>
|
>
|
||||||
|
|
|
@ -7,6 +7,9 @@ import { showModal } from '../../stores/ui'
|
||||||
|
|
||||||
import styles from './Donate.module.scss'
|
import styles from './Donate.module.scss'
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||||
|
type DWindow = Window & { cp: any }
|
||||||
|
|
||||||
export const Donate = () => {
|
export const Donate = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const once = ''
|
const once = ''
|
||||||
|
@ -24,43 +27,44 @@ export const Donate = () => {
|
||||||
const [showingPayment, setShowingPayment] = createSignal<boolean>()
|
const [showingPayment, setShowingPayment] = createSignal<boolean>()
|
||||||
const [period, setPeriod] = createSignal(monthly)
|
const [period, setPeriod] = createSignal(monthly)
|
||||||
const [amount, setAmount] = createSignal(0)
|
const [amount, setAmount] = createSignal(0)
|
||||||
const {
|
const { showSnackbar } = useSnackbar()
|
||||||
actions: { showSnackbar },
|
|
||||||
} = useSnackbar()
|
|
||||||
|
|
||||||
const initiated = () => {
|
const initiated = () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
try {
|
||||||
const CloudPayments = window['cp'] // Checkout(cpOptions)
|
const { cp: CloudPayments } = window as unknown as DWindow
|
||||||
setWidget(new CloudPayments())
|
|
||||||
console.log('[donate] payments initiated')
|
setWidget(new CloudPayments())
|
||||||
setCustomerReciept({
|
console.log('[donate] payments initiated')
|
||||||
Items: [
|
setCustomerReciept({
|
||||||
//товарные позиции
|
Items: [
|
||||||
{
|
//товарные позиции
|
||||||
label: cpOptions.description, //наименование товара
|
{
|
||||||
price: amount() || 0, //цена
|
label: cpOptions.description, //наименование товара
|
||||||
quantity: 1, //количество
|
price: amount() || 0, //цена
|
||||||
amount: amount() || 0, //сумма
|
quantity: 1, //количество
|
||||||
vat: 20, //ставка НДС
|
amount: amount() || 0, //сумма
|
||||||
method: 0, // тег-1214 признак способа расчета - признак способа расчета
|
vat: 20, //ставка НДС
|
||||||
object: 0, // тег-1212 признак предмета расчета - признак предмета товара, работы, услуги, платежа, выплаты, иного предмета расчета
|
method: 0, // тег-1214 признак способа расчета - признак способа расчета
|
||||||
|
object: 0, // тег-1212 признак предмета расчета - признак предмета товара, работы, услуги, платежа, выплаты, иного предмета расчета
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// taxationSystem: 0, //система налогообложения; необязательный, если у вас одна система налогообложения
|
||||||
|
// email: 'user@example.com', //e-mail покупателя, если нужно отправить письмо с чеком
|
||||||
|
// phone: '', //телефон покупателя в любом формате, если нужно отправить сообщение со ссылкой на чек
|
||||||
|
isBso: false, //чек является бланком строгой отчетности
|
||||||
|
amounts: {
|
||||||
|
electronic: amount(), // Сумма оплаты электронными деньгами
|
||||||
|
advancePayment: 0, // Сумма из предоплаты (зачетом аванса) (2 знака после запятой)
|
||||||
|
credit: 0, // Сумма постоплатой(в кредит) (2 знака после запятой)
|
||||||
|
provision: 0, // Сумма оплаты встречным предоставлением (сертификаты, др. мат.ценности) (2 знака после запятой)
|
||||||
},
|
},
|
||||||
],
|
})
|
||||||
// taxationSystem: 0, //система налогообложения; необязательный, если у вас одна система налогообложения
|
} catch (error) {
|
||||||
// email: 'user@example.com', //e-mail покупателя, если нужно отправить письмо с чеком
|
console.error(error)
|
||||||
// phone: '', //телефон покупателя в любом формате, если нужно отправить сообщение со ссылкой на чек
|
}
|
||||||
isBso: false, //чек является бланком строгой отчетности
|
|
||||||
amounts: {
|
|
||||||
electronic: amount(), // Сумма оплаты электронными деньгами
|
|
||||||
advancePayment: 0, // Сумма из предоплаты (зачетом аванса) (2 знака после запятой)
|
|
||||||
credit: 0, // Сумма постоплатой(в кредит) (2 знака после запятой)
|
|
||||||
provision: 0, // Сумма оплаты встречным предоставлением (сертификаты, др. мат.ценности) (2 знака после запятой)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const script = document.createElement('script')
|
const script = document.createElement('script')
|
||||||
script.type = 'text/javascript'
|
script.type = 'text/javascript'
|
||||||
script.src = 'https://widget.cloudpayments.ru/bundles/cloudpayments.js'
|
script.src = 'https://widget.cloudpayments.ru/bundles/cloudpayments.js'
|
||||||
|
@ -76,8 +80,8 @@ export const Donate = () => {
|
||||||
const choice: HTMLInputElement | undefined | null =
|
const choice: HTMLInputElement | undefined | null =
|
||||||
amountSwitchElement?.querySelector('input[type=radio]:checked')
|
amountSwitchElement?.querySelector('input[type=radio]:checked')
|
||||||
setAmount(Number.parseInt(customAmountElement?.value || choice?.value || '0'))
|
setAmount(Number.parseInt(customAmountElement?.value || choice?.value || '0'))
|
||||||
console.log('[donate] input amount ' + amount)
|
console.log(`[donate] input amount ${amount}`)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// biome-ignore lint/suspicious/noExplicitAny: it's a widget!
|
||||||
;(widget() as any).charge(
|
;(widget() as any).charge(
|
||||||
{
|
{
|
||||||
// options
|
// options
|
||||||
|
@ -105,7 +109,7 @@ export const Donate = () => {
|
||||||
console.debug('[donate] options', opts)
|
console.debug('[donate] options', opts)
|
||||||
showModal('thank')
|
showModal('thank')
|
||||||
},
|
},
|
||||||
function (reason: string, options) {
|
(reason: string, options) => {
|
||||||
// fail
|
// fail
|
||||||
// действие при неуспешной оплате
|
// действие при неуспешной оплате
|
||||||
console.debug('[donate] options', options)
|
console.debug('[donate] options', options)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createMemo, For } from 'solid-js'
|
import { For, createMemo } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
|
@ -11,7 +11,7 @@ export const Footer = () => {
|
||||||
const { t, lang } = useLocalize()
|
const { t, lang } = useLocalize()
|
||||||
|
|
||||||
const changeLangTitle = createMemo(() => (lang() === 'ru' ? 'English' : 'Русский'))
|
const changeLangTitle = createMemo(() => (lang() === 'ru' ? 'English' : 'Русский'))
|
||||||
const changeLangLink = createMemo(() => '?lng=' + (lang() === 'ru' ? 'en' : 'ru'))
|
const changeLangLink = createMemo(() => `?lng=${lang() === 'ru' ? 'en' : 'ru'}`)
|
||||||
const links = createMemo(() => [
|
const links = createMemo(() => [
|
||||||
{
|
{
|
||||||
header: 'About the project',
|
header: 'About the project',
|
||||||
|
@ -89,7 +89,7 @@ export const Footer = () => {
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
const SOCIAL = [
|
const social = [
|
||||||
{
|
{
|
||||||
name: 'facebook',
|
name: 'facebook',
|
||||||
href: 'https://facebook.com/discoursio',
|
href: 'https://facebook.com/discoursio',
|
||||||
|
@ -146,7 +146,7 @@ export const Footer = () => {
|
||||||
<a href="/about/terms-of-use">{t('Terms of use')}</a>
|
<a href="/about/terms-of-use">{t('Terms of use')}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class={clsx(styles.footerCopyrightSocial, 'col-md-6 col-lg-4')}>
|
<div class={clsx(styles.footerCopyrightSocial, 'col-md-6 col-lg-4')}>
|
||||||
<For each={SOCIAL}>
|
<For each={social}>
|
||||||
{(social) => (
|
{(social) => (
|
||||||
<div class={clsx(styles.socialItem, styles[`socialItem${social.name}`])}>
|
<div class={clsx(styles.socialItem, styles[`socialItem${social.name}`])}>
|
||||||
<a href={social.href}>
|
<a href={social.href}>
|
||||||
|
|
|
@ -20,13 +20,8 @@ type Props = {
|
||||||
|
|
||||||
export const Draft = (props: Props) => {
|
export const Draft = (props: Props) => {
|
||||||
const { t, formatDate } = useLocalize()
|
const { t, formatDate } = useLocalize()
|
||||||
const {
|
const { showConfirm } = useConfirm()
|
||||||
actions: { showConfirm },
|
const { showSnackbar } = useSnackbar()
|
||||||
} = useConfirm()
|
|
||||||
|
|
||||||
const {
|
|
||||||
actions: { showSnackbar },
|
|
||||||
} = useSnackbar()
|
|
||||||
|
|
||||||
const handlePublishLinkClick = (e) => {
|
const handlePublishLinkClick = (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
|
@ -6,8 +6,8 @@ import { Show } from 'solid-js'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { MediaItem } from '../../../pages/types'
|
import { MediaItem } from '../../../pages/types'
|
||||||
import { composeMediaItems } from '../../../utils/composeMediaItems'
|
import { composeMediaItems } from '../../../utils/composeMediaItems'
|
||||||
import { DropArea } from '../../_shared/DropArea'
|
|
||||||
import { AudioPlayer } from '../../Article/AudioPlayer'
|
import { AudioPlayer } from '../../Article/AudioPlayer'
|
||||||
|
import { DropArea } from '../../_shared/DropArea'
|
||||||
|
|
||||||
import styles from './AudioUploader.module.scss'
|
import styles from './AudioUploader.module.scss'
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@ import type { Editor } from '@tiptap/core'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { UploadedFile } from '../../../pages/types'
|
import { UploadedFile } from '../../../pages/types'
|
||||||
import { renderUploadedImage } from '../../../utils/renderUploadedImage'
|
import { renderUploadedImage } from '../../../utils/renderUploadedImage'
|
||||||
|
import { Modal } from '../../Nav/Modal'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { Popover } from '../../_shared/Popover'
|
import { Popover } from '../../_shared/Popover'
|
||||||
import { Modal } from '../../Nav/Modal'
|
|
||||||
import { UploadModalContent } from '../UploadModalContent'
|
import { UploadModalContent } from '../UploadModalContent'
|
||||||
|
|
||||||
import styles from './BubbleMenu.module.scss'
|
import styles from './BubbleMenu.module.scss'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Editor } from '@tiptap/core'
|
import type { Editor } from '@tiptap/core'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, Show, For } from 'solid-js'
|
import { For, Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
|
|
|
@ -38,8 +38,9 @@ import { useSession } from '../../context/session'
|
||||||
import { useSnackbar } from '../../context/snackbar'
|
import { useSnackbar } from '../../context/snackbar'
|
||||||
import { handleImageUpload } from '../../utils/handleImageUpload'
|
import { handleImageUpload } from '../../utils/handleImageUpload'
|
||||||
|
|
||||||
import { FigureBubbleMenu, BlockquoteBubbleMenu, IncutBubbleMenu } from './BubbleMenu'
|
import { BlockquoteBubbleMenu, FigureBubbleMenu, IncutBubbleMenu } from './BubbleMenu'
|
||||||
import { EditorFloatingMenu } from './EditorFloatingMenu'
|
import { EditorFloatingMenu } from './EditorFloatingMenu'
|
||||||
|
import { TextBubbleMenu } from './TextBubbleMenu'
|
||||||
import Article from './extensions/Article'
|
import Article from './extensions/Article'
|
||||||
import { CustomBlockquote } from './extensions/CustomBlockquote'
|
import { CustomBlockquote } from './extensions/CustomBlockquote'
|
||||||
import { Figcaption } from './extensions/Figcaption'
|
import { Figcaption } from './extensions/Figcaption'
|
||||||
|
@ -49,7 +50,6 @@ import { Iframe } from './extensions/Iframe'
|
||||||
import { Span } from './extensions/Span'
|
import { Span } from './extensions/Span'
|
||||||
import { ToggleTextWrap } from './extensions/ToggleTextWrap'
|
import { ToggleTextWrap } from './extensions/ToggleTextWrap'
|
||||||
import { TrailingNode } from './extensions/TrailingNode'
|
import { TrailingNode } from './extensions/TrailingNode'
|
||||||
import { TextBubbleMenu } from './TextBubbleMenu'
|
|
||||||
|
|
||||||
import './Prosemirror.scss'
|
import './Prosemirror.scss'
|
||||||
|
|
||||||
|
@ -80,9 +80,7 @@ export const Editor = (props: Props) => {
|
||||||
const [isCommonMarkup, setIsCommonMarkup] = createSignal(false)
|
const [isCommonMarkup, setIsCommonMarkup] = createSignal(false)
|
||||||
const [shouldShowTextBubbleMenu, setShouldShowTextBubbleMenu] = createSignal(false)
|
const [shouldShowTextBubbleMenu, setShouldShowTextBubbleMenu] = createSignal(false)
|
||||||
|
|
||||||
const {
|
const { showSnackbar } = useSnackbar()
|
||||||
actions: { showSnackbar },
|
|
||||||
} = useSnackbar()
|
|
||||||
|
|
||||||
const docName = `shout-${props.shoutId}`
|
const docName = `shout-${props.shoutId}`
|
||||||
|
|
||||||
|
@ -337,10 +335,7 @@ export const Editor = (props: Props) => {
|
||||||
content: initialContent ?? null,
|
content: initialContent ?? null,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const {
|
const { countWords, setEditor } = useEditorContext()
|
||||||
actions: { countWords, setEditor },
|
|
||||||
} = useEditorContext()
|
|
||||||
|
|
||||||
setEditor(editor)
|
setEditor(editor)
|
||||||
|
|
||||||
const html = useEditorHTML(() => editor())
|
const html = useEditorHTML(() => editor())
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import type { MenuItem } from './Menu/Menu'
|
|
||||||
import type { Editor } from '@tiptap/core'
|
import type { Editor } from '@tiptap/core'
|
||||||
|
import type { MenuItem } from './Menu/Menu'
|
||||||
|
|
||||||
import { createEffect, createSignal, Show } from 'solid-js'
|
import { Show, createEffect, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { UploadedFile } from '../../../pages/types'
|
import { UploadedFile } from '../../../pages/types'
|
||||||
import { showModal } from '../../../stores/ui'
|
import { showModal } from '../../../stores/ui'
|
||||||
import { renderUploadedImage } from '../../../utils/renderUploadedImage'
|
import { renderUploadedImage } from '../../../utils/renderUploadedImage'
|
||||||
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
|
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
|
||||||
import { Icon } from '../../_shared/Icon'
|
|
||||||
import { Modal } from '../../Nav/Modal'
|
import { Modal } from '../../Nav/Modal'
|
||||||
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { InlineForm } from '../InlineForm'
|
import { InlineForm } from '../InlineForm'
|
||||||
import { UploadModalContent } from '../UploadModalContent'
|
import { UploadModalContent } from '../UploadModalContent'
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ type FloatingMenuProps = {
|
||||||
ref: (el: HTMLDivElement) => void
|
ref: (el: HTMLDivElement) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const embedData = async (data) => {
|
const embedData = (data) => {
|
||||||
const element = document.createRange().createContextualFragment(data)
|
const element = document.createRange().createContextualFragment(data)
|
||||||
const { attributes } = element.firstChild as HTMLIFrameElement
|
const { attributes } = element.firstChild as HTMLIFrameElement
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => {
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateEmbed = async (value) => {
|
const validateEmbed = (value) => {
|
||||||
const element = document.createRange().createContextualFragment(value)
|
const element = document.createRange().createContextualFragment(value)
|
||||||
if (element.firstChild?.nodeName !== 'IFRAME') {
|
if (element.firstChild?.nodeName !== 'IFRAME') {
|
||||||
return t('Error')
|
return t('Error')
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const InsertLinkForm = (props: Props) => {
|
||||||
const currentUrl = createEditorTransaction(
|
const currentUrl = createEditorTransaction(
|
||||||
() => props.editor,
|
() => props.editor,
|
||||||
(ed) => {
|
(ed) => {
|
||||||
return (ed && ed.getAttributes('link').href) || ''
|
return ed?.getAttributes('link').href || ''
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
const handleClearLinkForm = () => {
|
const handleClearLinkForm = () => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { getPagePath } from '@nanostores/router'
|
import { getPagePath } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, Show } from 'solid-js'
|
import { Show, createSignal } from 'solid-js'
|
||||||
import { useEditorHTML } from 'solid-tiptap'
|
import { useEditorHTML } from 'solid-tiptap'
|
||||||
import Typograf from 'typograf'
|
import Typograf from 'typograf'
|
||||||
|
|
||||||
|
@ -24,18 +24,10 @@ type Props = {
|
||||||
|
|
||||||
export const Panel = (props: Props) => {
|
export const Panel = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const {
|
const { isEditorPanelVisible, wordCounter, editorRef, form, toggleEditorPanel, saveShout, publishShout } =
|
||||||
isEditorPanelVisible,
|
useEditorContext()
|
||||||
wordCounter,
|
|
||||||
editorRef,
|
|
||||||
form,
|
|
||||||
actions: { toggleEditorPanel, saveShout, publishShout },
|
|
||||||
} = useEditorContext()
|
|
||||||
|
|
||||||
const containerRef: { current: HTMLElement } = {
|
|
||||||
current: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const containerRef: { current: HTMLElement } = { current: null }
|
||||||
const [isShortcutsVisible, setIsShortcutsVisible] = createSignal(false)
|
const [isShortcutsVisible, setIsShortcutsVisible] = createSignal(false)
|
||||||
const [isTypographyFixed, setIsTypographyFixed] = createSignal(false)
|
const [isTypographyFixed, setIsTypographyFixed] = createSignal(false)
|
||||||
|
|
||||||
|
@ -59,8 +51,9 @@ export const Panel = (props: Props) => {
|
||||||
publishShout(form)
|
publishShout(form)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const html = useEditorHTML(() => editorRef.current())
|
||||||
|
|
||||||
const handleFixTypographyClick = () => {
|
const handleFixTypographyClick = () => {
|
||||||
const html = useEditorHTML(() => editorRef.current())
|
|
||||||
editorRef.current().commands.setContent(typograf.execute(html()))
|
editorRef.current().commands.setContent(typograf.execute(html()))
|
||||||
setIsTypographyFixed(true)
|
setIsTypographyFixed(true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { Paragraph } from '@tiptap/extension-paragraph'
|
||||||
import { Placeholder } from '@tiptap/extension-placeholder'
|
import { Placeholder } from '@tiptap/extension-placeholder'
|
||||||
import { Text } from '@tiptap/extension-text'
|
import { Text } from '@tiptap/extension-text'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createEffect, createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
import { Show, createEffect, createMemo, createSignal, onCleanup, onMount } from 'solid-js'
|
||||||
import { Portal } from 'solid-js/web'
|
import { Portal } from 'solid-js/web'
|
||||||
import {
|
import {
|
||||||
createEditorTransaction,
|
createEditorTransaction,
|
||||||
|
@ -24,17 +24,17 @@ import { useEditorContext } from '../../context/editor'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { UploadedFile } from '../../pages/types'
|
import { UploadedFile } from '../../pages/types'
|
||||||
import { hideModal, showModal } from '../../stores/ui'
|
import { hideModal, showModal } from '../../stores/ui'
|
||||||
|
import { Modal } from '../Nav/Modal'
|
||||||
import { Button } from '../_shared/Button'
|
import { Button } from '../_shared/Button'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { Popover } from '../_shared/Popover'
|
import { Popover } from '../_shared/Popover'
|
||||||
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
||||||
import { Modal } from '../Nav/Modal'
|
|
||||||
|
|
||||||
import { Figcaption } from './extensions/Figcaption'
|
|
||||||
import { Figure } from './extensions/Figure'
|
|
||||||
import { LinkBubbleMenuModule } from './LinkBubbleMenu'
|
import { LinkBubbleMenuModule } from './LinkBubbleMenu'
|
||||||
import { TextBubbleMenu } from './TextBubbleMenu'
|
import { TextBubbleMenu } from './TextBubbleMenu'
|
||||||
import { UploadModalContent } from './UploadModalContent'
|
import { UploadModalContent } from './UploadModalContent'
|
||||||
|
import { Figcaption } from './extensions/Figcaption'
|
||||||
|
import { Figure } from './extensions/Figure'
|
||||||
|
|
||||||
import styles from './SimplifiedEditor.module.scss'
|
import styles from './SimplifiedEditor.module.scss'
|
||||||
|
|
||||||
|
@ -94,9 +94,7 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
current: null,
|
current: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { setEditor } = useEditorContext()
|
||||||
actions: { setEditor },
|
|
||||||
} = useEditorContext()
|
|
||||||
|
|
||||||
const ImageFigure = Figure.extend({
|
const ImageFigure = Figure.extend({
|
||||||
name: 'capturedImage',
|
name: 'capturedImage',
|
||||||
|
@ -173,7 +171,7 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
createEditorTransaction(
|
createEditorTransaction(
|
||||||
() => editor(),
|
() => editor(),
|
||||||
(ed) => {
|
(ed) => {
|
||||||
return ed && ed.isActive(name)
|
return ed?.isActive(name)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -218,7 +216,7 @@ const SimplifiedEditor = (props: Props) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleKeyDown = async (event) => {
|
const handleKeyDown = (event) => {
|
||||||
if (isEmpty() || !isFocused()) {
|
if (isEmpty() || !isFocused()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Editor } from '@tiptap/core'
|
import type { Editor } from '@tiptap/core'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { Switch, Match, createSignal, Show, onMount, onCleanup, createEffect, lazy } from 'solid-js'
|
import { Match, Show, Switch, createEffect, createSignal, lazy, onCleanup, onMount } from 'solid-js'
|
||||||
import { createEditorTransaction } from 'solid-tiptap'
|
import { createEditorTransaction } from 'solid-tiptap'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
@ -26,7 +26,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
const isActive = (name: string, attributes?: unknown) =>
|
const isActive = (name: string, attributes?: unknown) =>
|
||||||
createEditorTransaction(
|
createEditorTransaction(
|
||||||
() => props.editor,
|
() => props.editor,
|
||||||
(editor) => editor && editor.isActive(name, attributes),
|
(editor) => editor?.isActive(name, attributes),
|
||||||
)
|
)
|
||||||
|
|
||||||
const [textSizeBubbleOpen, setTextSizeBubbleOpen] = createSignal(false)
|
const [textSizeBubbleOpen, setTextSizeBubbleOpen] = createSignal(false)
|
||||||
|
@ -71,7 +71,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
}
|
}
|
||||||
setListBubbleOpen((prev) => !prev)
|
setListBubbleOpen((prev) => !prev)
|
||||||
}
|
}
|
||||||
const handleKeyDown = async (event) => {
|
const handleKeyDown = (event) => {
|
||||||
if (event.code === 'KeyK' && (event.metaKey || event.ctrlKey) && !props.editor.state.selection.empty) {
|
if (event.code === 'KeyK' && (event.metaKey || event.ctrlKey) && !props.editor.state.selection.empty) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
setLinkEditorOpen(true)
|
setLinkEditorOpen(true)
|
||||||
|
@ -160,7 +160,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
submitButtonText={t('Send')}
|
submitButtonText={t('Send')}
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Match>
|
||||||
<Match when={!linkEditorOpen() || !footnoteEditorOpen()}>
|
<Match when={!(linkEditorOpen() && footnoteEditorOpen())}>
|
||||||
<>
|
<>
|
||||||
<Show when={!props.isCommonMarkup}>
|
<Show when={!props.isCommonMarkup}>
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Topic } from '../../../graphql/schema/core.gen'
|
import type { Topic } from '../../../graphql/schema/core.gen'
|
||||||
|
|
||||||
import { createOptions, Select } from '@thisbeyond/solid-select'
|
import { Select, createOptions } from '@thisbeyond/solid-select'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal } from 'solid-js'
|
import { createSignal } from 'solid-js'
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { createDropzone, createFileUploader, UploadFile } from '@solid-primitives/upload'
|
import { UploadFile, createDropzone, createFileUploader } from '@solid-primitives/upload'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, Show } from 'solid-js'
|
import { Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { UploadedFile } from '../../../pages/types'
|
import { UploadedFile } from '../../../pages/types'
|
||||||
|
@ -56,7 +56,7 @@ export const UploadModalContent = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUpload = async () => {
|
const handleUpload = () => {
|
||||||
selectFiles(async ([uploadFile]) => {
|
selectFiles(async ([uploadFile]) => {
|
||||||
await runUpload(uploadFile)
|
await runUpload(uploadFile)
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { MediaItem } from '../../../pages/types'
|
||||||
|
|
||||||
import { createDropzone } from '@solid-primitives/upload'
|
import { createDropzone } from '@solid-primitives/upload'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, For, Show } from 'solid-js'
|
import { For, Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSnackbar } from '../../../context/snackbar'
|
import { useSnackbar } from '../../../context/snackbar'
|
||||||
|
@ -24,9 +24,7 @@ export const VideoUploader = (props: Props) => {
|
||||||
const [error, setError] = createSignal<string>()
|
const [error, setError] = createSignal<string>()
|
||||||
const [incorrectUrl, setIncorrectUrl] = createSignal<boolean>(false)
|
const [incorrectUrl, setIncorrectUrl] = createSignal<boolean>(false)
|
||||||
|
|
||||||
const {
|
const { showSnackbar } = useSnackbar()
|
||||||
actions: { showSnackbar },
|
|
||||||
} = useSnackbar()
|
|
||||||
|
|
||||||
const urlInput: {
|
const urlInput: {
|
||||||
current: HTMLInputElement
|
current: HTMLInputElement
|
||||||
|
@ -59,7 +57,7 @@ export const VideoUploader = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUrlInput = async (value: string) => {
|
const handleUrlInput = (value: string) => {
|
||||||
setError()
|
setError()
|
||||||
if (validateUrl(value)) {
|
if (validateUrl(value)) {
|
||||||
props.onVideoAdd(composeMediaItems([{ url: value }]))
|
props.onVideoAdd(composeMediaItems([{ url: value }]))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { mergeAttributes, Node } from '@tiptap/core'
|
import { Node, mergeAttributes } from '@tiptap/core'
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
declare module '@tiptap/core' {
|
||||||
interface Commands<ReturnType> {
|
interface Commands<ReturnType> {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { mergeAttributes, Node } from '@tiptap/core'
|
import { Node, mergeAttributes } from '@tiptap/core'
|
||||||
import { Plugin } from '@tiptap/pm/state'
|
import { Plugin } from '@tiptap/pm/state'
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
declare module '@tiptap/core' {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { mergeAttributes, Node } from '@tiptap/core'
|
import { Node, mergeAttributes } from '@tiptap/core'
|
||||||
|
|
||||||
declare module '@tiptap/core' {
|
declare module '@tiptap/core' {
|
||||||
interface Commands<ReturnType> {
|
interface Commands<ReturnType> {
|
||||||
|
|
|
@ -41,9 +41,8 @@ export const ToggleTextWrap = Extension.create({
|
||||||
if (changesApplied) {
|
if (changesApplied) {
|
||||||
dispatch(tr)
|
dispatch(tr)
|
||||||
return true
|
return true
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,25 +2,25 @@ import type { Author, Shout, Topic } from '../../../graphql/schema/core.gen'
|
||||||
|
|
||||||
import { getPagePath, openPage } from '@nanostores/router'
|
import { getPagePath, openPage } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createMemo, createSignal, For, Show } from 'solid-js'
|
import { For, Show, createMemo, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
import { router, useRouter } from '../../../stores/router'
|
import { router, useRouter } from '../../../stores/router'
|
||||||
import { capitalize } from '../../../utils/capitalize'
|
import { capitalize } from '../../../utils/capitalize'
|
||||||
import { getDescription } from '../../../utils/meta'
|
import { getDescription } from '../../../utils/meta'
|
||||||
|
import { CoverImage } from '../../Article/CoverImage'
|
||||||
|
import { SharePopup, getShareUrl } from '../../Article/SharePopup'
|
||||||
|
import { ShoutRatingControl } from '../../Article/ShoutRatingControl'
|
||||||
|
import { AuthorLink } from '../../Author/AuthorLink'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { Image } from '../../_shared/Image'
|
import { Image } from '../../_shared/Image'
|
||||||
import { Popover } from '../../_shared/Popover'
|
import { Popover } from '../../_shared/Popover'
|
||||||
import { CoverImage } from '../../Article/CoverImage'
|
|
||||||
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
|
|
||||||
import { ShoutRatingControl } from '../../Article/ShoutRatingControl'
|
|
||||||
import { AuthorLink } from '../../Author/AuthorLink'
|
|
||||||
import { CardTopic } from '../CardTopic'
|
import { CardTopic } from '../CardTopic'
|
||||||
import { FeedArticlePopup } from '../FeedArticlePopup'
|
import { FeedArticlePopup } from '../FeedArticlePopup'
|
||||||
|
|
||||||
import styles from './ArticleCard.module.scss'
|
|
||||||
import stylesHeader from '../../Nav/Header/Header.module.scss'
|
import stylesHeader from '../../Nav/Header/Header.module.scss'
|
||||||
|
import styles from './ArticleCard.module.scss'
|
||||||
|
|
||||||
export type ArticleCardProps = {
|
export type ArticleCardProps = {
|
||||||
// TODO: refactor this, please
|
// TODO: refactor this, please
|
||||||
|
@ -83,28 +83,47 @@ const getTitleAndSubtitle = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: simple fast auto translated title/substitle
|
||||||
|
|
||||||
return { title, subtitle }
|
return { title, subtitle }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getMainTopicTitle = (article: Shout, lng: string) => {
|
||||||
|
const mainTopicSlug = article.main_topic || ''
|
||||||
|
const mainTopic = article.topics?.find((tpc: Topic) => tpc.slug === mainTopicSlug)
|
||||||
|
const mainTopicTitle =
|
||||||
|
mainTopicSlug && lng === 'en' ? mainTopicSlug.replace(/-/, ' ') : mainTopic?.title || ''
|
||||||
|
|
||||||
|
return [mainTopicTitle, mainTopicSlug]
|
||||||
|
}
|
||||||
|
|
||||||
|
const LAYOUT_ASPECT = {
|
||||||
|
music: styles.aspectRatio1x1,
|
||||||
|
literature: styles.aspectRatio16x9,
|
||||||
|
video: styles.aspectRatio16x9,
|
||||||
|
image: styles.aspectRatio4x3,
|
||||||
|
}
|
||||||
|
|
||||||
export const ArticleCard = (props: ArticleCardProps) => {
|
export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
const { t, lang, formatDate } = useLocalize()
|
const { t, lang, formatDate } = useLocalize()
|
||||||
const { author } = useSession()
|
const { author } = useSession()
|
||||||
const mainTopicSlug = props.article.main_topic || ''
|
const { changeSearchParams } = useRouter()
|
||||||
const mainTopic = props.article.topics?.find((tpc: Topic) => tpc.slug === mainTopicSlug)
|
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
|
||||||
const mainTopicTitle =
|
const [isCoverImageLoadError, setIsCoverImageLoadError] = createSignal(false)
|
||||||
mainTopicSlug && lang() === 'en' ? mainTopicSlug.replace(/-/, ' ') : mainTopic?.title || ''
|
const [isCoverImageLoading, setIsCoverImageLoading] = createSignal(true)
|
||||||
|
const description = getDescription(props.article.body)
|
||||||
|
const aspectRatio = () => LAYOUT_ASPECT[props.article.layout]
|
||||||
|
const [mainTopicTitle, mainTopicSlug] = getMainTopicTitle(props.article, lang())
|
||||||
|
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
||||||
|
|
||||||
const formattedDate = createMemo<string>(() =>
|
const formattedDate = createMemo<string>(() =>
|
||||||
props.article.published_at ? formatDate(new Date(props.article.published_at * 1000)) : '',
|
props.article.published_at ? formatDate(new Date(props.article.published_at * 1000)) : '',
|
||||||
)
|
)
|
||||||
|
|
||||||
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
|
||||||
|
|
||||||
const canEdit = () =>
|
const canEdit = () =>
|
||||||
props.article.authors?.some((a) => a && a?.slug === author()?.slug) ||
|
props.article.authors?.some((a) => a && a?.slug === author()?.slug) ||
|
||||||
props.article.created_by?.id === author()?.id
|
props.article.created_by?.id === author()?.id
|
||||||
|
|
||||||
const { changeSearchParams } = useRouter()
|
|
||||||
const scrollToComments = (event) => {
|
const scrollToComments = (event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
openPage(router, 'article', { slug: props.article.slug })
|
openPage(router, 'article', { slug: props.article.slug })
|
||||||
|
@ -112,28 +131,6 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
scrollTo: 'comments',
|
scrollTo: 'comments',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
|
|
||||||
const [isCoverImageLoadError, setIsCoverImageLoadError] = createSignal(false)
|
|
||||||
const [isCoverImageLoading, setIsCoverImageLoading] = createSignal(true)
|
|
||||||
|
|
||||||
const description = getDescription(props.article.body)
|
|
||||||
|
|
||||||
const aspectRatio = () => {
|
|
||||||
switch (props.article.layout) {
|
|
||||||
case 'music': {
|
|
||||||
return styles.aspectRatio1x1
|
|
||||||
}
|
|
||||||
case 'image': {
|
|
||||||
return styles.aspectRatio4x3
|
|
||||||
}
|
|
||||||
case 'video':
|
|
||||||
case 'literature': {
|
|
||||||
return styles.aspectRatio16x9
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
class={clsx(styles.shoutCard, props.settings?.additionalClass, {
|
class={clsx(styles.shoutCard, props.settings?.additionalClass, {
|
||||||
|
@ -152,7 +149,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
[aspectRatio()]: props.withAspectRatio,
|
[aspectRatio()]: props.withAspectRatio,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Show when={!props.settings?.noimage && !props.settings?.isFeedMode}>
|
<Show when={!(props.settings?.noimage || props.settings?.isFeedMode)}>
|
||||||
<div class={styles.shoutCardCoverContainer}>
|
<div class={styles.shoutCardCoverContainer}>
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.shoutCardCover, {
|
class={clsx(styles.shoutCardCover, {
|
||||||
|
@ -223,7 +220,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
</Show>
|
</Show>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<Show when={!props.settings?.noauthor || !props.settings?.nodate}>
|
<Show when={!(props.settings?.noauthor && props.settings?.nodate)}>
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.shoutDetails, { [styles.shoutDetailsFeedMode]: props.settings?.isFeedMode })}
|
class={clsx(styles.shoutDetails, { [styles.shoutDetailsFeedMode]: props.settings?.isFeedMode })}
|
||||||
>
|
>
|
||||||
|
|
|
@ -7,9 +7,9 @@ import { For, Show } from 'solid-js'
|
||||||
|
|
||||||
import { useFollowing } from '../../context/following'
|
import { useFollowing } from '../../context/following'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { Icon } from '../_shared/Icon'
|
|
||||||
import { AuthorBadge } from '../Author/AuthorBadge'
|
import { AuthorBadge } from '../Author/AuthorBadge'
|
||||||
import { TopicCard } from '../Topic/Card'
|
import { TopicCard } from '../Topic/Card'
|
||||||
|
import { Icon } from '../_shared/Icon'
|
||||||
|
|
||||||
import { ArticleCard } from './ArticleCard'
|
import { ArticleCard } from './ArticleCard'
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { PopupProps } from '../../_shared/Popup'
|
import type { PopupProps } from '../../_shared/Popup'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, Show } from 'solid-js'
|
import { Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { Popup } from '../../_shared/Popup'
|
import { Popup } from '../../_shared/Popup'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Shout } from '../../graphql/schema/core.gen'
|
|
||||||
import type { JSX } from 'solid-js/jsx-runtime'
|
import type { JSX } from 'solid-js/jsx-runtime'
|
||||||
|
import type { Shout } from '../../graphql/schema/core.gen'
|
||||||
|
|
||||||
import { For, Show } from 'solid-js'
|
import { For, Show } from 'solid-js'
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Shout } from '../../graphql/schema/core.gen'
|
import type { Shout } from '../../graphql/schema/core.gen'
|
||||||
|
|
||||||
import { createSignal, createEffect, For, Show } from 'solid-js'
|
import { For, Show, createEffect, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { ArticleCard } from './ArticleCard'
|
import { ArticleCard } from './ArticleCard'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Shout } from '../../graphql/schema/core.gen'
|
|
||||||
import type { JSX } from 'solid-js/jsx-runtime'
|
import type { JSX } from 'solid-js/jsx-runtime'
|
||||||
|
import type { Shout } from '../../graphql/schema/core.gen'
|
||||||
|
|
||||||
import { For, Show } from 'solid-js'
|
import { For, Show } from 'solid-js'
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { getPagePath } from '@nanostores/router'
|
import { getPagePath } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, For, Show } from 'solid-js'
|
import { For, Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useFollowing } from '../../../context/following'
|
import { useFollowing } from '../../../context/following'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
@ -8,8 +8,8 @@ import { Author } from '../../../graphql/schema/core.gen'
|
||||||
import { router, useRouter } from '../../../stores/router'
|
import { router, useRouter } from '../../../stores/router'
|
||||||
import { useArticlesStore } from '../../../stores/zine/articles'
|
import { useArticlesStore } from '../../../stores/zine/articles'
|
||||||
import { useSeenStore } from '../../../stores/zine/seen'
|
import { useSeenStore } from '../../../stores/zine/seen'
|
||||||
import { Icon } from '../../_shared/Icon'
|
|
||||||
import { Userpic } from '../../Author/Userpic'
|
import { Userpic } from '../../Author/Userpic'
|
||||||
|
import { Icon } from '../../_shared/Icon'
|
||||||
|
|
||||||
import styles from './Sidebar.module.scss'
|
import styles from './Sidebar.module.scss'
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Author } from '../../graphql/schema/core.gen'
|
import type { Author } from '../../graphql/schema/core.gen'
|
||||||
|
|
||||||
import { createSignal, For, createEffect } from 'solid-js'
|
import { For, createEffect, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useInbox } from '../../context/inbox'
|
import { useInbox } from '../../context/inbox'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
@ -21,6 +21,8 @@ const CreateModalContent = (props: Props) => {
|
||||||
const [chatTitle, setChatTitle] = createSignal<string>('')
|
const [chatTitle, setChatTitle] = createSignal<string>('')
|
||||||
const [usersId, setUsersId] = createSignal<number[]>([])
|
const [usersId, setUsersId] = createSignal<number[]>([])
|
||||||
const [collectionToInvite, setCollectionToInvite] = createSignal<inviteUser[]>(inviteUsers)
|
const [collectionToInvite, setCollectionToInvite] = createSignal<inviteUser[]>(inviteUsers)
|
||||||
|
const { createChat, loadChats } = useInbox()
|
||||||
|
|
||||||
let textInput: HTMLInputElement
|
let textInput: HTMLInputElement
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
|
@ -36,7 +38,7 @@ const CreateModalContent = (props: Props) => {
|
||||||
return user.selected === true
|
return user.selected === true
|
||||||
})
|
})
|
||||||
.map((user) => {
|
.map((user) => {
|
||||||
return user['id']
|
return user.id
|
||||||
})
|
})
|
||||||
return [...s]
|
return [...s]
|
||||||
})
|
})
|
||||||
|
@ -54,14 +56,12 @@ const CreateModalContent = (props: Props) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const { actions } = useInbox()
|
|
||||||
|
|
||||||
const handleCreate = async () => {
|
const handleCreate = async () => {
|
||||||
try {
|
try {
|
||||||
const initChat = await actions.createChat(usersId(), chatTitle())
|
const initChat = await createChat(usersId(), chatTitle())
|
||||||
console.debug('[components.Inbox] create chat result:', initChat)
|
console.debug('[components.Inbox] create chat result:', initChat)
|
||||||
hideModal()
|
hideModal()
|
||||||
await actions.loadChats()
|
await loadChats()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { ChatMember } from '../../graphql/schema/chat.gen'
|
import type { ChatMember } from '../../graphql/schema/chat.gen'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { Show, Switch, Match, createMemo } from 'solid-js'
|
import { Match, Show, Switch, createMemo } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { Author } from '../../graphql/schema/core.gen'
|
import { Author } from '../../graphql/schema/core.gen'
|
||||||
|
@ -26,8 +26,8 @@ type DialogProps = {
|
||||||
|
|
||||||
const DialogCard = (props: DialogProps) => {
|
const DialogCard = (props: DialogProps) => {
|
||||||
const { t, formatTime } = useLocalize()
|
const { t, formatTime } = useLocalize()
|
||||||
const companions = createMemo(
|
const companions = createMemo(() =>
|
||||||
() => props.members && props.members.filter((member: ChatMember) => member.id !== props.ownId),
|
props.members?.filter((member: ChatMember) => member.id !== props.ownId),
|
||||||
)
|
)
|
||||||
|
|
||||||
const names = createMemo<string>(() => (companions() || []).map((companion) => companion.name).join(', '))
|
const names = createMemo<string>(() => (companions() || []).map((companion) => companion.name).join(', '))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Message as MessageType, ChatMember } from '../../graphql/schema/chat.gen'
|
import type { ChatMember, Message as MessageType } from '../../graphql/schema/chat.gen'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, Show } from 'solid-js'
|
import { Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { PopupProps } from '../_shared/Popup'
|
import type { PopupProps } from '../_shared/Popup'
|
||||||
|
|
||||||
import { createEffect, createSignal, For } from 'solid-js'
|
import { For, createEffect, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { Popup } from '../_shared/Popup'
|
import { Popup } from '../_shared/Popup'
|
||||||
|
|
|
@ -187,7 +187,6 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
margin-top: 0.3em;
|
margin-top: 0.3em;
|
||||||
|
|
||||||
color: var(--danger-color);
|
color: var(--danger-color);
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { AuthModalSearchParams } from './types'
|
import type { AuthModalSearchParams } from './types'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, JSX, Show } from 'solid-js'
|
import { JSX, Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
|
@ -21,9 +21,7 @@ type ValidationErrors = Partial<Record<keyof FormFields, string | JSX.Element>>
|
||||||
export const ChangePasswordForm = () => {
|
export const ChangePasswordForm = () => {
|
||||||
const { searchParams, changeSearchParams } = useRouter<AuthModalSearchParams>()
|
const { searchParams, changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const {
|
const { changePassword } = useSession()
|
||||||
actions: { changePassword },
|
|
||||||
} = useSession()
|
|
||||||
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
||||||
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
||||||
const [newPassword, setNewPassword] = createSignal<string>()
|
const [newPassword, setNewPassword] = createSignal<string>()
|
||||||
|
@ -68,7 +66,7 @@ export const ChangePasswordForm = () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Show when={validationErrors()}>
|
<Show when={validationErrors()}>
|
||||||
<div>{validationErrors()['password']}</div>
|
<div>{validationErrors().password}</div>
|
||||||
</Show>
|
</Show>
|
||||||
<PasswordField
|
<PasswordField
|
||||||
errorMessage={(err) => setPasswordError(err)}
|
errorMessage={(err) => setPasswordError(err)}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createEffect, createSignal, Show } from 'solid-js'
|
import { Show, createEffect, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
import { useRouter } from '../../../stores/router'
|
import { useRouter } from '../../../stores/router'
|
||||||
import { hideModal } from '../../../stores/ui'
|
import { hideModal } from '../../../stores/ui'
|
||||||
|
|
||||||
import { setEmail, email } from './sharedLogic'
|
import { email, setEmail } from './sharedLogic'
|
||||||
|
|
||||||
import styles from './AuthModal.module.scss'
|
import styles from './AuthModal.module.scss'
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { AuthModalSearchParams } from './types'
|
import type { AuthModalSearchParams } from './types'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, JSX, Show } from 'solid-js'
|
import { JSX, Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
|
@ -25,9 +25,7 @@ export const ForgotPasswordForm = () => {
|
||||||
setValidationErrors(({ email: _notNeeded, ...rest }) => rest)
|
setValidationErrors(({ email: _notNeeded, ...rest }) => rest)
|
||||||
setEmail(newEmail.toLowerCase())
|
setEmail(newEmail.toLowerCase())
|
||||||
}
|
}
|
||||||
const {
|
const { forgotPassword } = useSession()
|
||||||
actions: { forgotPassword },
|
|
||||||
} = useSession()
|
|
||||||
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
||||||
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
||||||
const [isUserNotFound, setIsUserNotFound] = createSignal(false)
|
const [isUserNotFound, setIsUserNotFound] = createSignal(false)
|
||||||
|
@ -63,7 +61,7 @@ export const ForgotPasswordForm = () => {
|
||||||
redirect_uri: window.location.origin,
|
redirect_uri: window.location.origin,
|
||||||
})
|
})
|
||||||
console.debug('[ForgotPasswordForm] authorizer response:', data)
|
console.debug('[ForgotPasswordForm] authorizer response:', data)
|
||||||
if (errors && errors.some((error) => error.message.includes('bad user credentials'))) {
|
if (errors?.some((error) => error.message.includes('bad user credentials'))) {
|
||||||
setIsUserNotFound(true)
|
setIsUserNotFound(true)
|
||||||
}
|
}
|
||||||
if (data.message) setMessage(data.message)
|
if (data.message) setMessage(data.message)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { AuthModalSearchParams } from './types'
|
import type { AuthModalSearchParams } from './types'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, Show } from 'solid-js'
|
import { Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
|
@ -12,8 +12,8 @@ import { validateEmail } from '../../../utils/validateEmail'
|
||||||
|
|
||||||
import { AuthModalHeader } from './AuthModalHeader'
|
import { AuthModalHeader } from './AuthModalHeader'
|
||||||
import { PasswordField } from './PasswordField'
|
import { PasswordField } from './PasswordField'
|
||||||
import { email, setEmail } from './sharedLogic'
|
|
||||||
import { SocialProviders } from './SocialProviders'
|
import { SocialProviders } from './SocialProviders'
|
||||||
|
import { email, setEmail } from './sharedLogic'
|
||||||
|
|
||||||
import styles from './AuthModal.module.scss'
|
import styles from './AuthModal.module.scss'
|
||||||
|
|
||||||
|
@ -25,28 +25,18 @@ type FormFields = {
|
||||||
type ValidationErrors = Partial<Record<keyof FormFields, string>>
|
type ValidationErrors = Partial<Record<keyof FormFields, string>>
|
||||||
|
|
||||||
export const LoginForm = () => {
|
export const LoginForm = () => {
|
||||||
|
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
|
||||||
const [submitError, setSubmitError] = createSignal('')
|
const [submitError, setSubmitError] = createSignal('')
|
||||||
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
||||||
|
const [password, setPassword] = createSignal('')
|
||||||
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
||||||
// TODO: better solution for interactive error messages
|
// TODO: better solution for interactive error messages
|
||||||
const [isEmailNotConfirmed, setIsEmailNotConfirmed] = createSignal(false)
|
const [isEmailNotConfirmed, setIsEmailNotConfirmed] = createSignal(false)
|
||||||
const [isLinkSent, setIsLinkSent] = createSignal(false)
|
const [isLinkSent, setIsLinkSent] = createSignal(false)
|
||||||
|
|
||||||
const authFormRef: { current: HTMLFormElement } = { current: null }
|
const authFormRef: { current: HTMLFormElement } = { current: null }
|
||||||
|
const { showSnackbar } = useSnackbar()
|
||||||
const {
|
const { signIn } = useSession()
|
||||||
actions: { showSnackbar },
|
|
||||||
} = useSnackbar()
|
|
||||||
|
|
||||||
const {
|
|
||||||
actions: { signIn },
|
|
||||||
} = useSession()
|
|
||||||
|
|
||||||
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
|
||||||
|
|
||||||
const [password, setPassword] = createSignal('')
|
|
||||||
|
|
||||||
const handleEmailInput = (newEmail: string) => {
|
const handleEmailInput = (newEmail: string) => {
|
||||||
setValidationErrors(({ email: _notNeeded, ...rest }) => rest)
|
setValidationErrors(({ email: _notNeeded, ...rest }) => rest)
|
||||||
|
@ -58,7 +48,7 @@ export const LoginForm = () => {
|
||||||
setPassword(newPassword)
|
setPassword(newPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSendLinkAgainClick = async (event: Event) => {
|
const handleSendLinkAgainClick = (event: Event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
setIsLinkSent(true)
|
setIsLinkSent(true)
|
||||||
|
@ -67,7 +57,7 @@ export const LoginForm = () => {
|
||||||
changeSearchParams({ mode: 'forgot-password' })
|
changeSearchParams({ mode: 'forgot-password' })
|
||||||
// NOTE: temporary solution, needs logic in authorizer
|
// NOTE: temporary solution, needs logic in authorizer
|
||||||
/* FIXME:
|
/* FIXME:
|
||||||
const { actions: { authorizer } } = useSession()
|
const { authorizer } = useSession()
|
||||||
const result = await authorizer().verifyEmail({ token })
|
const result = await authorizer().verifyEmail({ token })
|
||||||
if (!result) setSubmitError('cant sign send link')
|
if (!result) setSubmitError('cant sign send link')
|
||||||
*/
|
*/
|
||||||
|
@ -82,15 +72,15 @@ export const LoginForm = () => {
|
||||||
|
|
||||||
const newValidationErrors: ValidationErrors = {}
|
const newValidationErrors: ValidationErrors = {}
|
||||||
|
|
||||||
if (!email()) {
|
const validateAndSetError = (field, message) => {
|
||||||
newValidationErrors.email = t('Please enter email')
|
if (!field()) {
|
||||||
} else if (!validateEmail(email())) {
|
newValidationErrors[field.name] = t(message)
|
||||||
newValidationErrors.email = t('Invalid email')
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!password()) {
|
validateAndSetError(email, 'Please enter email')
|
||||||
newValidationErrors.password = t('Please enter password')
|
validateAndSetError(() => validateEmail(email()), 'Invalid email')
|
||||||
}
|
validateAndSetError(password, 'Please enter password')
|
||||||
|
|
||||||
if (Object.keys(newValidationErrors).length > 0) {
|
if (Object.keys(newValidationErrors).length > 0) {
|
||||||
setValidationErrors(newValidationErrors)
|
setValidationErrors(newValidationErrors)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createEffect, createSignal, on, Show } from 'solid-js'
|
import { Show, createEffect, createSignal, on } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../../context/localize'
|
import { useLocalize } from '../../../../context/localize'
|
||||||
import { Icon } from '../../../_shared/Icon'
|
import { Icon } from '../../../_shared/Icon'
|
||||||
|
@ -50,7 +50,7 @@ export const PasswordField = (props: Props) => {
|
||||||
on(
|
on(
|
||||||
() => error(),
|
() => error(),
|
||||||
() => {
|
() => {
|
||||||
props.errorMessage && props.errorMessage(error())
|
props.errorMessage?.(error())
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true },
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { AuthModalSearchParams } from './types'
|
|
||||||
import type { JSX } from 'solid-js'
|
import type { JSX } from 'solid-js'
|
||||||
|
import type { AuthModalSearchParams } from './types'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { Show, createSignal } from 'solid-js'
|
import { Show, createSignal } from 'solid-js'
|
||||||
|
@ -13,8 +13,8 @@ import { validateEmail } from '../../../utils/validateEmail'
|
||||||
|
|
||||||
import { AuthModalHeader } from './AuthModalHeader'
|
import { AuthModalHeader } from './AuthModalHeader'
|
||||||
import { PasswordField } from './PasswordField'
|
import { PasswordField } from './PasswordField'
|
||||||
import { email, setEmail } from './sharedLogic'
|
|
||||||
import { SocialProviders } from './SocialProviders'
|
import { SocialProviders } from './SocialProviders'
|
||||||
|
import { email, setEmail } from './sharedLogic'
|
||||||
|
|
||||||
import styles from './AuthModal.module.scss'
|
import styles from './AuthModal.module.scss'
|
||||||
|
|
||||||
|
@ -34,9 +34,7 @@ export const RegisterForm = () => {
|
||||||
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { emailChecks } = useEmailChecks()
|
const { emailChecks } = useEmailChecks()
|
||||||
const {
|
const { signUp } = useSession()
|
||||||
actions: { signUp },
|
|
||||||
} = useSession()
|
|
||||||
const [submitError, setSubmitError] = createSignal('')
|
const [submitError, setSubmitError] = createSignal('')
|
||||||
const [fullName, setFullName] = createSignal('')
|
const [fullName, setFullName] = createSignal('')
|
||||||
const [password, setPassword] = createSignal('')
|
const [password, setPassword] = createSignal('')
|
||||||
|
@ -67,11 +65,9 @@ export const RegisterForm = () => {
|
||||||
}
|
}
|
||||||
setValidationErrors(({ email: _notNeeded, ...rest }) => rest)
|
setValidationErrors(({ email: _notNeeded, ...rest }) => rest)
|
||||||
setValidationErrors(({ fullName: _notNeeded, ...rest }) => rest)
|
setValidationErrors(({ fullName: _notNeeded, ...rest }) => rest)
|
||||||
|
|
||||||
setSubmitError('')
|
setSubmitError('')
|
||||||
|
|
||||||
const newValidationErrors: ValidationErrors = {}
|
const newValidationErrors: ValidationErrors = {}
|
||||||
|
|
||||||
const cleanName = fullName().trim()
|
const cleanName = fullName().trim()
|
||||||
const cleanEmail = email().trim()
|
const cleanEmail = email().trim()
|
||||||
|
|
||||||
|
@ -90,9 +86,7 @@ export const RegisterForm = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
setValidationErrors(newValidationErrors)
|
setValidationErrors(newValidationErrors)
|
||||||
|
|
||||||
const emailCheckResult = await checkEmail(cleanEmail)
|
const emailCheckResult = await checkEmail(cleanEmail)
|
||||||
|
|
||||||
const isValid = Object.keys(newValidationErrors).length === 0 && !emailCheckResult
|
const isValid = Object.keys(newValidationErrors).length === 0 && !emailCheckResult
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
|
@ -113,7 +107,7 @@ export const RegisterForm = () => {
|
||||||
redirect_uri: window.location.origin,
|
redirect_uri: window.location.origin,
|
||||||
}
|
}
|
||||||
const { errors } = await signUp(opts)
|
const { errors } = await signUp(opts)
|
||||||
if (errors && errors.some((error) => error.message.includes('has already signed up'))) {
|
if (errors?.some((error) => error.message.includes('has already signed up'))) {
|
||||||
setValidationErrors((prev) => ({
|
setValidationErrors((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
email: (
|
email: (
|
||||||
|
|
|
@ -35,8 +35,10 @@
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a, button {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.facebook,
|
.facebook,
|
||||||
|
|
|
@ -10,9 +10,7 @@ export const PROVIDERS = ['facebook', 'google', 'github'] // 'vk' | 'telegram'
|
||||||
|
|
||||||
export const SocialProviders = () => {
|
export const SocialProviders = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const {
|
const { oauth } = useSession()
|
||||||
actions: { oauth },
|
|
||||||
} = useSession()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={styles.container}>
|
<div class={styles.container}>
|
||||||
|
@ -20,9 +18,9 @@ export const SocialProviders = () => {
|
||||||
<div class={styles.social}>
|
<div class={styles.social}>
|
||||||
<For each={PROVIDERS}>
|
<For each={PROVIDERS}>
|
||||||
{(provider) => (
|
{(provider) => (
|
||||||
<a href="#" class={styles[provider]} onClick={(_e) => oauth(provider)}>
|
<button class={styles[provider]} onClick={(_e) => oauth(provider)}>
|
||||||
<Icon name={provider} />
|
<Icon name={provider} />
|
||||||
</a>
|
</button>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { AuthModalMode, AuthModalSearchParams } from './types'
|
import type { AuthModalMode, AuthModalSearchParams } from './types'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { Show, Component, createEffect, createMemo } from 'solid-js'
|
import { Component, Show, createEffect, createMemo } from 'solid-js'
|
||||||
import { Dynamic } from 'solid-js/web'
|
import { Dynamic } from 'solid-js/web'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
@ -29,7 +29,6 @@ export const AuthModal = () => {
|
||||||
const rootRef: { current: HTMLDivElement } = { current: null }
|
const rootRef: { current: HTMLDivElement } = { current: null }
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { searchParams } = useRouter<AuthModalSearchParams>()
|
const { searchParams } = useRouter<AuthModalSearchParams>()
|
||||||
|
|
||||||
const { source } = searchParams()
|
const { source } = searchParams()
|
||||||
|
|
||||||
const mode = createMemo<AuthModalMode>(() => {
|
const mode = createMemo<AuthModalMode>(() => {
|
||||||
|
@ -57,7 +56,7 @@ export const AuthModal = () => {
|
||||||
classList={{ [styles.hidden]: mode() !== 'register' && mode() !== 'confirm-email' }}
|
classList={{ [styles.hidden]: mode() !== 'register' && mode() !== 'confirm-email' }}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h4>{t(`Join the global community of authors!`)}</h4>
|
<h4>{t('Join the global community of authors!')}</h4>
|
||||||
<p class={styles.authBenefits}>
|
<p class={styles.authBenefits}>
|
||||||
{t(
|
{t(
|
||||||
'Get to know the most intelligent people of our time, edit and discuss the articles, share your expertise, rate and decide what to publish in the magazine',
|
'Get to know the most intelligent people of our time, edit and discuss the articles, share your expertise, rate and decide what to publish in the magazine',
|
||||||
|
|
|
@ -6,11 +6,7 @@ import styles from './ConfirmModal.module.scss'
|
||||||
|
|
||||||
export const ConfirmModal = () => {
|
export const ConfirmModal = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
const { confirmMessage, resolveConfirm } = useConfirm()
|
||||||
const {
|
|
||||||
confirmMessage,
|
|
||||||
actions: { resolveConfirm },
|
|
||||||
} = useConfirm()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={styles.confirmModal}>
|
<div class={styles.confirmModal}>
|
||||||
|
|
|
@ -2,18 +2,18 @@ import type { Topic } from '../../../graphql/schema/core.gen'
|
||||||
|
|
||||||
import { getPagePath, redirectPage } from '@nanostores/router'
|
import { getPagePath, redirectPage } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { Show, createSignal, createEffect, onMount, onCleanup, For } from 'solid-js'
|
import { For, Show, createEffect, createSignal, onCleanup, onMount } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
import { apiClient } from '../../../graphql/client/core'
|
import { apiClient } from '../../../graphql/client/core'
|
||||||
import { router, ROUTES, useRouter } from '../../../stores/router'
|
import { ROUTES, router, useRouter } from '../../../stores/router'
|
||||||
import { useModalStore } from '../../../stores/ui'
|
import { useModalStore } from '../../../stores/ui'
|
||||||
import { getDescription } from '../../../utils/meta'
|
import { getDescription } from '../../../utils/meta'
|
||||||
|
import { SharePopup, getShareUrl } from '../../Article/SharePopup'
|
||||||
|
import { RANDOM_TOPICS_COUNT } from '../../Views/Home'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { Subscribe } from '../../_shared/Subscribe'
|
import { Subscribe } from '../../_shared/Subscribe'
|
||||||
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
|
|
||||||
import { RANDOM_TOPICS_COUNT } from '../../Views/Home'
|
|
||||||
import { AuthModal } from '../AuthModal'
|
import { AuthModal } from '../AuthModal'
|
||||||
import { ConfirmModal } from '../ConfirmModal'
|
import { ConfirmModal } from '../ConfirmModal'
|
||||||
import { HeaderAuth } from '../HeaderAuth'
|
import { HeaderAuth } from '../HeaderAuth'
|
||||||
|
@ -46,12 +46,8 @@ export const Header = (props: Props) => {
|
||||||
const { t, lang } = useLocalize()
|
const { t, lang } = useLocalize()
|
||||||
const { modal } = useModalStore()
|
const { modal } = useModalStore()
|
||||||
const { page } = useRouter()
|
const { page } = useRouter()
|
||||||
const {
|
const { requireAuthentication } = useSession()
|
||||||
actions: { requireAuthentication },
|
|
||||||
} = useSession()
|
|
||||||
|
|
||||||
const { searchParams } = useRouter<HeaderSearchParams>()
|
const { searchParams } = useRouter<HeaderSearchParams>()
|
||||||
|
|
||||||
const [randomTopics, setRandomTopics] = createSignal([])
|
const [randomTopics, setRandomTopics] = createSignal([])
|
||||||
const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false)
|
const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false)
|
||||||
const [getIsScrolled, setIsScrolled] = createSignal(false)
|
const [getIsScrolled, setIsScrolled] = createSignal(false)
|
||||||
|
@ -62,9 +58,7 @@ export const Header = (props: Props) => {
|
||||||
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
|
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
|
||||||
const [isZineVisible, setIsZineVisible] = createSignal(false)
|
const [isZineVisible, setIsZineVisible] = createSignal(false)
|
||||||
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
|
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
|
||||||
const toggleFixed = () => {
|
const toggleFixed = () => setFixed(!fixed())
|
||||||
setFixed(!fixed())
|
|
||||||
}
|
|
||||||
|
|
||||||
const tag = (topic: Topic) =>
|
const tag = (topic: Topic) =>
|
||||||
/[ЁА-яё]/.test(topic.title || '') && lang() !== 'ru' ? topic.slug : topic.title
|
/[ЁА-яё]/.test(topic.title || '') && lang() !== 'ru' ? topic.slug : topic.title
|
||||||
|
@ -82,7 +76,7 @@ export const Header = (props: Props) => {
|
||||||
document.body.classList.toggle('fixed', fixed() || modal() !== null)
|
document.body.classList.toggle('fixed', fixed() || modal() !== null)
|
||||||
document.body.classList.toggle(styles.fixed, fixed() && !modal())
|
document.body.classList.toggle(styles.fixed, fixed() && !modal())
|
||||||
|
|
||||||
if (!fixed() && !modal()) {
|
if (!(fixed() || modal())) {
|
||||||
mainContent.style.marginTop = ''
|
mainContent.style.marginTop = ''
|
||||||
window.scrollTo(0, windowScrollTop)
|
window.scrollTo(0, windowScrollTop)
|
||||||
}
|
}
|
||||||
|
@ -135,13 +129,13 @@ export const Header = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let timer
|
let timer: string | number | NodeJS.Timeout
|
||||||
|
|
||||||
const clearTimer = () => {
|
const clearTimer = () => {
|
||||||
clearTimeout(timer)
|
clearTimeout(timer)
|
||||||
}
|
}
|
||||||
|
|
||||||
const hideSubnavigation = (event, time = 500) => {
|
const hideSubnavigation = (_event, time = 500) => {
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
toggleSubnavigation(false)
|
toggleSubnavigation(false)
|
||||||
}, time)
|
}, time)
|
||||||
|
@ -264,7 +258,7 @@ export const Header = (props: Props) => {
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h4 innerHTML={t('Subscribe us')} />
|
<h4>{t('Subscribe us')}</h4>
|
||||||
<ul class="view-switcher">
|
<ul class="view-switcher">
|
||||||
<li class={styles.mainNavigationSocial}>
|
<li class={styles.mainNavigationSocial}>
|
||||||
<a href="https://www.instagram.com/discoursio/">
|
<a href="https://www.instagram.com/discoursio/">
|
||||||
|
@ -358,14 +352,14 @@ export const Header = (props: Props) => {
|
||||||
<Icon name="comment" class={styles.icon} />
|
<Icon name="comment" class={styles.icon} />
|
||||||
<Icon name="comment-hover" class={clsx(styles.icon, styles.iconHover)} />
|
<Icon name="comment-hover" class={clsx(styles.icon, styles.iconHover)} />
|
||||||
</div>
|
</div>
|
||||||
<a href="#" class={styles.control} onClick={handleCreateButtonClick}>
|
<button class={styles.control} onClick={handleCreateButtonClick}>
|
||||||
<Icon name="pencil-outline" class={styles.icon} />
|
<Icon name="pencil-outline" class={styles.icon} />
|
||||||
<Icon name="pencil-outline-hover" class={clsx(styles.icon, styles.iconHover)} />
|
<Icon name="pencil-outline-hover" class={clsx(styles.icon, styles.iconHover)} />
|
||||||
</a>
|
</button>
|
||||||
<a href="#" class={styles.control} onClick={handleBookmarkButtonClick}>
|
<button class={styles.control} onClick={handleBookmarkButtonClick}>
|
||||||
<Icon name="bookmark" class={styles.icon} />
|
<Icon name="bookmark" class={styles.icon} />
|
||||||
<Icon name="bookmark-hover" class={clsx(styles.icon, styles.iconHover)} />
|
<Icon name="bookmark-hover" class={clsx(styles.icon, styles.iconHover)} />
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
@ -417,7 +411,7 @@ export const Header = (props: Props) => {
|
||||||
<a href="/podcasts">{t('Podcasts')}</a>
|
<a href="/podcasts">{t('Podcasts')}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="item">
|
<li class="item">
|
||||||
<a href="">{t('Special Projects')}</a>
|
<a href="/about/projects">{t('Special Projects')}</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/topic/interview">#{t('Interview')}</a>
|
<a href="/topic/interview">#{t('Interview')}</a>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { getPagePath } from '@nanostores/router'
|
import { getPagePath } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
import { router, ROUTES, useRouter } from '../../../stores/router'
|
import { ROUTES, router, useRouter } from '../../../stores/router'
|
||||||
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
||||||
|
|
||||||
import styles from './Header.module.scss'
|
import styles from './Header.module.scss'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { getPagePath } from '@nanostores/router'
|
import { getPagePath } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
|
import { Show, createMemo, createSignal, onCleanup, onMount } from 'solid-js'
|
||||||
|
|
||||||
import { useEditorContext } from '../../context/editor'
|
import { useEditorContext } from '../../context/editor'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
@ -8,11 +8,11 @@ import { useNotifications } from '../../context/notifications'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
import { router, useRouter } from '../../stores/router'
|
import { router, useRouter } from '../../stores/router'
|
||||||
import { showModal } from '../../stores/ui'
|
import { showModal } from '../../stores/ui'
|
||||||
|
import { Userpic } from '../Author/Userpic'
|
||||||
import { Button } from '../_shared/Button'
|
import { Button } from '../_shared/Button'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { Popover } from '../_shared/Popover'
|
import { Popover } from '../_shared/Popover'
|
||||||
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
||||||
import { Userpic } from '../Author/Userpic'
|
|
||||||
|
|
||||||
import { ProfilePopup } from './ProfilePopup'
|
import { ProfilePopup } from './ProfilePopup'
|
||||||
|
|
||||||
|
@ -33,15 +33,8 @@ export const HeaderAuth = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { page } = useRouter()
|
const { page } = useRouter()
|
||||||
const { session, author, isAuthenticated, isSessionLoaded } = useSession()
|
const { session, author, isAuthenticated, isSessionLoaded } = useSession()
|
||||||
const {
|
const { unreadNotificationsCount, showNotificationsPanel } = useNotifications()
|
||||||
unreadNotificationsCount,
|
const { form, toggleEditorPanel, saveShout, publishShout } = useEditorContext()
|
||||||
actions: { showNotificationsPanel },
|
|
||||||
} = useNotifications()
|
|
||||||
|
|
||||||
const {
|
|
||||||
form,
|
|
||||||
actions: { toggleEditorPanel, saveShout, publishShout },
|
|
||||||
} = useEditorContext()
|
|
||||||
|
|
||||||
const handleBellIconClick = (event: Event) => {
|
const handleBellIconClick = (event: Event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { JSX } from 'solid-js'
|
||||||
|
|
||||||
import { redirectPage } from '@nanostores/router'
|
import { redirectPage } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createEffect, createMemo, createSignal, Show } from 'solid-js'
|
import { Show, createEffect, createMemo, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useMediaQuery } from '../../../context/mediaQuery'
|
import { useMediaQuery } from '../../../context/mediaQuery'
|
||||||
import { router } from '../../../stores/router'
|
import { router } from '../../../stores/router'
|
||||||
|
@ -32,7 +32,7 @@ export const Modal = (props: Props) => {
|
||||||
const handleHide = () => {
|
const handleHide = () => {
|
||||||
if (modal()) {
|
if (modal()) {
|
||||||
if (allowClose()) {
|
if (allowClose()) {
|
||||||
props.onClose && props.onClose()
|
props.onClose?.()
|
||||||
} else {
|
} else {
|
||||||
redirectPage(router, 'home')
|
redirectPage(router, 'home')
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ export const Modal = (props: Props) => {
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.modal, {
|
class={clsx(styles.modal, {
|
||||||
[styles.narrow]: props.variant === 'narrow',
|
[styles.narrow]: props.variant === 'narrow',
|
||||||
['col-auto col-md-20 offset-md-2 col-lg-14 offset-lg-5']: props.variant === 'medium',
|
'col-auto col-md-20 offset-md-2 col-lg-14 offset-lg-5': props.variant === 'medium',
|
||||||
[styles.noPadding]: props.noPadding,
|
[styles.noPadding]: props.noPadding,
|
||||||
[styles.maxHeight]: props.maxHeight,
|
[styles.maxHeight]: props.maxHeight,
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { ModalType } from '../../../stores/ui'
|
|
||||||
import type { JSX } from 'solid-js/jsx-runtime'
|
import type { JSX } from 'solid-js/jsx-runtime'
|
||||||
|
import type { ModalType } from '../../../stores/ui'
|
||||||
|
|
||||||
import { showModal } from '../../../stores/ui'
|
import { showModal } from '../../../stores/ui'
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,7 @@ import styles from '../_shared/Popup/Popup.module.scss'
|
||||||
type ProfilePopupProps = Omit<PopupProps, 'children'>
|
type ProfilePopupProps = Omit<PopupProps, 'children'>
|
||||||
|
|
||||||
export const ProfilePopup = (props: ProfilePopupProps) => {
|
export const ProfilePopup = (props: ProfilePopupProps) => {
|
||||||
const {
|
const { author, signOut } = useSession()
|
||||||
author,
|
|
||||||
actions: { signOut },
|
|
||||||
} = useSession()
|
|
||||||
|
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
@mixin searchFilterControl {
|
@mixin search-filter-control {
|
||||||
@include font-size(1.4rem);
|
@include font-size(1.4rem);
|
||||||
|
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
|
|
||||||
padding: 0 2rem;
|
padding: 0 2rem;
|
||||||
|
background: rgb(64 64 64 / 50%);
|
||||||
background: rgb(64 64 64 / 0.5);
|
|
||||||
border-radius: 10rem;
|
border-radius: 10rem;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
@ -16,7 +14,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
color: rgb(255 255 255 / 0.4);
|
color: rgb(255 255 255 / 40%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,9 +26,7 @@
|
||||||
@include font-size(4.8rem);
|
@include font-size(4.8rem);
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
padding: 0 0 0.5rem;
|
padding: 0 0 0.5rem;
|
||||||
|
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 2px solid #fff;
|
border-bottom: 2px solid #fff;
|
||||||
|
@ -39,14 +35,13 @@
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: rgb(255 255 255 / 0.32);
|
color: rgb(255 255 255 / 32%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(:placeholder-shown) + .searchButton img {
|
&:not(:placeholder-shown) + .searchButton img {
|
||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-moz-selection,
|
|
||||||
&::selection {
|
&::selection {
|
||||||
color: #2638d9;
|
color: #2638d9;
|
||||||
}
|
}
|
||||||
|
@ -56,7 +51,6 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 2rem;
|
top: 2rem;
|
||||||
|
|
||||||
width: 3.2rem;
|
width: 3.2rem;
|
||||||
height: 3.2rem;
|
height: 3.2rem;
|
||||||
|
|
||||||
|
@ -67,9 +61,10 @@
|
||||||
|
|
||||||
.searchDescription {
|
.searchDescription {
|
||||||
margin-bottom: 44px;
|
margin-bottom: 44px;
|
||||||
|
|
||||||
@include font-size(1.6rem);
|
@include font-size(1.6rem);
|
||||||
|
|
||||||
color: rgb(255 255 255 / 0.64);
|
color: rgb(255 255 255 / 64%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.topicsList {
|
.topicsList {
|
||||||
|
@ -77,12 +72,11 @@
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|
||||||
margin-top: 9.6rem !important;
|
margin-top: 9.6rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topTopic {
|
.topTopic {
|
||||||
@include searchFilterControl;
|
@include search-filter-control;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filterSwitcher {
|
.filterSwitcher {
|
||||||
|
@ -108,22 +102,19 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|
||||||
margin: 6.4rem 0;
|
margin: 6.4rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filterResultsControl {
|
.filterResultsControl {
|
||||||
@include searchFilterControl;
|
@include search-filter-control;
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchLoader {
|
.searchLoader {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
|
|
||||||
border: 5px solid #fff;
|
border: 5px solid #fff;
|
||||||
border-bottom-color: transparent;
|
border-bottom-color: transparent;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
||||||
animation: rotation 1s linear infinite;
|
animation: rotation 1s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import type { Shout } from '../../../graphql/schema/core.gen'
|
import type { Shout } from '../../../graphql/schema/core.gen'
|
||||||
|
|
||||||
import { createResource, createSignal, For, onCleanup, Show } from 'solid-js'
|
import { For, Show, createResource, createSignal, onCleanup } from 'solid-js'
|
||||||
import { debounce } from 'throttle-debounce'
|
import { debounce } from 'throttle-debounce'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { loadShoutsSearch } from '../../../stores/zine/articles'
|
import { loadShoutsSearch } from '../../../stores/zine/articles'
|
||||||
import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll'
|
import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll'
|
||||||
import { byScore } from '../../../utils/sortby'
|
import { byScore } from '../../../utils/sortby'
|
||||||
|
import { FEED_PAGE_SIZE } from '../../Views/Feed/Feed'
|
||||||
import { Button } from '../../_shared/Button'
|
import { Button } from '../../_shared/Button'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { FEED_PAGE_SIZE } from '../../Views/Feed/Feed'
|
|
||||||
|
|
||||||
import { SearchResultItem } from './SearchResultItem'
|
import { SearchResultItem } from './SearchResultItem'
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const Snackbar = () => {
|
||||||
<Transition
|
<Transition
|
||||||
enterClass={styles.enter}
|
enterClass={styles.enter}
|
||||||
exitToClass={styles.exitTo}
|
exitToClass={styles.exitTo}
|
||||||
onExit={(el, done) => setTimeout(() => done(), 300)}
|
onExit={(_el, done) => setTimeout(() => done(), 300)}
|
||||||
>
|
>
|
||||||
<Show when={snackbarMessage()}>
|
<Show when={snackbarMessage()}>
|
||||||
<div class={styles.content}>
|
<div class={styles.content}>
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const Topics = () => {
|
||||||
<a href="/podcasts">{t('Podcasts')}</a>
|
<a href="/podcasts">{t('Podcasts')}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class={styles.item}>
|
<li class={styles.item}>
|
||||||
<a href="">{t('Special Projects')}</a>
|
<a href="/about/projects">{t('Special Projects')}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class={styles.item}>
|
<li class={styles.item}>
|
||||||
<a href="/topic/interview">#{t('Interview')}</a>
|
<a href="/topic/interview">#{t('Interview')}</a>
|
||||||
|
|
|
@ -5,10 +5,10 @@ import { For, Show } from 'solid-js'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useNotifications } from '../../../context/notifications'
|
import { useNotifications } from '../../../context/notifications'
|
||||||
import { NotificationGroup as Group } from '../../../graphql/schema/notifier.gen'
|
import { NotificationGroup as Group } from '../../../graphql/schema/notifier.gen'
|
||||||
import { useRouter, router } from '../../../stores/router'
|
import { router, useRouter } from '../../../stores/router'
|
||||||
|
import { ArticlePageSearchParams } from '../../Article/FullArticle'
|
||||||
import { GroupAvatar } from '../../_shared/GroupAvatar'
|
import { GroupAvatar } from '../../_shared/GroupAvatar'
|
||||||
import { TimeAgo } from '../../_shared/TimeAgo'
|
import { TimeAgo } from '../../_shared/TimeAgo'
|
||||||
import { ArticlePageSearchParams } from '../../Article/FullArticle'
|
|
||||||
|
|
||||||
import styles from './NotificationView.module.scss'
|
import styles from './NotificationView.module.scss'
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ const getTitle = (title: string) => {
|
||||||
const shoutTitleWords = title.split(' ')
|
const shoutTitleWords = title.split(' ')
|
||||||
|
|
||||||
while (shoutTitle.length <= 30 && i < shoutTitleWords.length) {
|
while (shoutTitle.length <= 30 && i < shoutTitleWords.length) {
|
||||||
shoutTitle += shoutTitleWords[i] + ' '
|
shoutTitle += `${shoutTitleWords[i]} `
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,9 +45,7 @@ const reactionsCaption = (threadId: string) =>
|
||||||
export const NotificationGroup = (props: NotificationGroupProps) => {
|
export const NotificationGroup = (props: NotificationGroupProps) => {
|
||||||
const { t, formatTime, formatDate } = useLocalize()
|
const { t, formatTime, formatDate } = useLocalize()
|
||||||
const { changeSearchParams } = useRouter<ArticlePageSearchParams>()
|
const { changeSearchParams } = useRouter<ArticlePageSearchParams>()
|
||||||
const {
|
const { hideNotificationsPanel, markSeenThread } = useNotifications()
|
||||||
actions: { hideNotificationsPanel, markSeenThread },
|
|
||||||
} = useNotifications()
|
|
||||||
const handleClick = (threadId: string) => {
|
const handleClick = (threadId: string) => {
|
||||||
props.onClick()
|
props.onClick()
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createEffect, createMemo, createSignal, on, onCleanup, onMount, Show } from 'solid-js'
|
import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js'
|
||||||
import { throttle } from 'throttle-debounce'
|
import { throttle } from 'throttle-debounce'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
@ -46,7 +46,6 @@ const isEarlier = (date: Date) => {
|
||||||
|
|
||||||
export const NotificationsPanel = (props: Props) => {
|
export const NotificationsPanel = (props: Props) => {
|
||||||
const [isLoading, setIsLoading] = createSignal(false)
|
const [isLoading, setIsLoading] = createSignal(false)
|
||||||
|
|
||||||
const { isAuthenticated } = useSession()
|
const { isAuthenticated } = useSession()
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const {
|
const {
|
||||||
|
@ -55,7 +54,8 @@ export const NotificationsPanel = (props: Props) => {
|
||||||
unreadNotificationsCount,
|
unreadNotificationsCount,
|
||||||
loadedNotificationsCount,
|
loadedNotificationsCount,
|
||||||
totalNotificationsCount,
|
totalNotificationsCount,
|
||||||
actions: { loadNotificationsGrouped, markSeenAll },
|
loadNotificationsGrouped,
|
||||||
|
markSeenAll,
|
||||||
} = useNotifications()
|
} = useNotifications()
|
||||||
const handleHide = () => {
|
const handleHide = () => {
|
||||||
props.onClose()
|
props.onClose()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { createFileUploader } from '@solid-primitives/upload'
|
import { createFileUploader } from '@solid-primitives/upload'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import deepEqual from 'fast-deep-equal'
|
import deepEqual from 'fast-deep-equal'
|
||||||
import { createEffect, createSignal, For, lazy, Match, onCleanup, onMount, Show, Switch } from 'solid-js'
|
import { For, Match, Show, Switch, createEffect, createSignal, lazy, onCleanup, onMount } from 'solid-js'
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
|
|
||||||
import { useConfirm } from '../../context/confirm'
|
import { useConfirm } from '../../context/confirm'
|
||||||
|
@ -9,20 +9,20 @@ import { useLocalize } from '../../context/localize'
|
||||||
import { useProfileForm } from '../../context/profile'
|
import { useProfileForm } from '../../context/profile'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
import { useSnackbar } from '../../context/snackbar'
|
import { useSnackbar } from '../../context/snackbar'
|
||||||
import { showModal, hideModal } from '../../stores/ui'
|
import { hideModal, showModal } from '../../stores/ui'
|
||||||
import { clone } from '../../utils/clone'
|
import { clone } from '../../utils/clone'
|
||||||
import { getImageUrl } from '../../utils/getImageUrl'
|
import { getImageUrl } from '../../utils/getImageUrl'
|
||||||
import { handleImageUpload } from '../../utils/handleImageUpload'
|
import { handleImageUpload } from '../../utils/handleImageUpload'
|
||||||
import { profileSocialLinks } from '../../utils/profileSocialLinks'
|
import { profileSocialLinks } from '../../utils/profileSocialLinks'
|
||||||
import { validateUrl } from '../../utils/validateUrl'
|
import { validateUrl } from '../../utils/validateUrl'
|
||||||
|
import { Modal } from '../Nav/Modal'
|
||||||
|
import { ProfileSettingsNavigation } from '../Nav/ProfileSettingsNavigation'
|
||||||
import { Button } from '../_shared/Button'
|
import { Button } from '../_shared/Button'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { ImageCropper } from '../_shared/ImageCropper'
|
import { ImageCropper } from '../_shared/ImageCropper'
|
||||||
import { Loading } from '../_shared/Loading'
|
import { Loading } from '../_shared/Loading'
|
||||||
import { Popover } from '../_shared/Popover'
|
import { Popover } from '../_shared/Popover'
|
||||||
import { SocialNetworkInput } from '../_shared/SocialNetworkInput'
|
import { SocialNetworkInput } from '../_shared/SocialNetworkInput'
|
||||||
import { Modal } from '../Nav/Modal'
|
|
||||||
import { ProfileSettingsNavigation } from '../Nav/ProfileSettingsNavigation'
|
|
||||||
|
|
||||||
import styles from '../../pages/profile/Settings.module.scss'
|
import styles from '../../pages/profile/Settings.module.scss'
|
||||||
|
|
||||||
|
@ -31,7 +31,6 @@ const GrowingTextarea = lazy(() => import('../../components/_shared/GrowingTexta
|
||||||
|
|
||||||
export const ProfileSettings = () => {
|
export const ProfileSettings = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
|
||||||
const [prevForm, setPrevForm] = createStore({})
|
const [prevForm, setPrevForm] = createStore({})
|
||||||
const [isFormInitialized, setIsFormInitialized] = createSignal(false)
|
const [isFormInitialized, setIsFormInitialized] = createSignal(false)
|
||||||
const [social, setSocial] = createSignal([])
|
const [social, setSocial] = createSignal([])
|
||||||
|
@ -44,21 +43,10 @@ export const ProfileSettings = () => {
|
||||||
const [hostname, setHostname] = createSignal<string | null>(null)
|
const [hostname, setHostname] = createSignal<string | null>(null)
|
||||||
const [slugError, setSlugError] = createSignal<string>()
|
const [slugError, setSlugError] = createSignal<string>()
|
||||||
const [nameError, setNameError] = createSignal<string>()
|
const [nameError, setNameError] = createSignal<string>()
|
||||||
|
const { form, submit, updateFormField, setForm } = useProfileForm()
|
||||||
const {
|
const { showSnackbar } = useSnackbar()
|
||||||
form,
|
const { loadAuthor } = useSession()
|
||||||
actions: { submit, updateFormField, setForm },
|
const { showConfirm } = useConfirm()
|
||||||
} = useProfileForm()
|
|
||||||
|
|
||||||
const {
|
|
||||||
actions: { showSnackbar },
|
|
||||||
} = useSnackbar()
|
|
||||||
const {
|
|
||||||
actions: { loadAuthor },
|
|
||||||
} = useSession()
|
|
||||||
const {
|
|
||||||
actions: { showConfirm },
|
|
||||||
} = useConfirm()
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (Object.keys(form).length > 0 && !isFormInitialized()) {
|
if (Object.keys(form).length > 0 && !isFormInitialized()) {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user