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:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- feature/email-templates
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests
|
||||
run: npm run check
|
||||
- name: Install Biome
|
||||
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
|
||||
name: Update templates on Mailgun
|
||||
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]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18'
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
|
||||
- name: Install deps
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Check
|
||||
run: npm run check
|
||||
- name: Check types
|
||||
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
|
||||
bun.lockb
|
||||
.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",
|
||||
"package.json": "sort-package-json",
|
||||
"public/locales/**/*.json": "sort-json"
|
||||
"*.{js,ts,cjs,mjs,d.mts,jsx,tsx,json,jsonc,scss,css}": [
|
||||
"npm run check"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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": [
|
||||
"stylelint-config-standard-scss"
|
||||
],
|
||||
"plugins": [
|
||||
"stylelint-order",
|
||||
"stylelint-scss"
|
||||
],
|
||||
"extends": ["stylelint-config-standard-scss"],
|
||||
"plugins": ["stylelint-order", "stylelint-scss"],
|
||||
"rules": {
|
||||
"keyframes-name-pattern": null,
|
||||
"declaration-block-no-redundant-longhand-properties": null,
|
||||
|
@ -15,13 +10,13 @@
|
|||
"scss/no-global-function-names": null,
|
||||
"function-url-quotes": null,
|
||||
"font-family-no-missing-generic-family-keyword": null,
|
||||
"order/order": [
|
||||
"custom-properties",
|
||||
"declarations"
|
||||
"order/order": ["custom-properties", "declarations"],
|
||||
"scss/dollar-variable-pattern": [
|
||||
"^[a-z][a-zA-Z]+$",
|
||||
{
|
||||
"ignore": "global"
|
||||
}
|
||||
],
|
||||
"scss/dollar-variable-pattern": ["^[a-z][a-zA-Z]+$", {
|
||||
"ignore": "global"
|
||||
}],
|
||||
"selector-pseudo-class-no-unknown": [
|
||||
true,
|
||||
{
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
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
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
14
README.md
14
README.md
|
@ -3,12 +3,6 @@
|
|||
npm install
|
||||
npm start
|
||||
```
|
||||
with different backends
|
||||
```
|
||||
npm run start:local
|
||||
npm run start:production
|
||||
npm run start:staging
|
||||
```
|
||||
|
||||
## Useful commands
|
||||
run checks
|
||||
|
@ -19,14 +13,18 @@ type checking with watch
|
|||
```
|
||||
npm run typecheck:watch
|
||||
```
|
||||
fix styles, imports, formatting and autofixable linting errors:
|
||||
```
|
||||
npm run fix
|
||||
```
|
||||
## Code generation
|
||||
|
||||
generate new SolidJS component:
|
||||
```
|
||||
npx hygen component new NewComponentName
|
||||
npm run hygen component new NewComponentName
|
||||
```
|
||||
|
||||
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'
|
||||
|
||||
type <%= h.changeCase.pascal(name) %>ContextType = {
|
||||
actions: {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 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>
|
||||
}
|
||||
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
to: _templates/<%= name %>/<%= action || 'new' %>/hello.ejs.t
|
||||
to: gen/<%= name %>/<%= action || 'new' %>/hello.ejs.t
|
||||
---
|
||||
---
|
||||
to: app/hello.js
|
||||
|
@ -14,5 +14,3 @@ https://github.com/jondot/hygen
|
|||
```
|
||||
|
||||
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
|
||||
|
@ -14,5 +14,3 @@ https://github.com/jondot/hygen
|
|||
```
|
||||
|
||||
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:
|
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",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
@ -10,22 +10,22 @@
|
|||
"codegen": "graphql-codegen",
|
||||
"deploy": "graphql-codegen && npm run typecheck && vite build && vercel",
|
||||
"dev": "vite",
|
||||
"fix": "npm run lint:code:fix && npm run lint:styles:fix",
|
||||
"format": "npx prettier \"{,!(node_modules)/**/}*.{js,ts,tsx,json,scss,css}\" --write --ignore-path .gitignore",
|
||||
"e2e": "npx playwright test --project=chromium",
|
||||
"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",
|
||||
"lint": "npm run lint:code && npm run lint:styles",
|
||||
"lint:code": "eslint .",
|
||||
"lint:code:fix": "eslint . --fix",
|
||||
"check:code": "npx @biomejs/biome check src --log-kind=compact --verbose",
|
||||
"check:code:fix": "npx @biomejs/biome check src --log-kind=compact --verbose --apply-unsafe",
|
||||
"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:fix": "stylelint **/*.{scss,css} --fix",
|
||||
"pre-commit": "lint-staged",
|
||||
"pre-push": "npm run typecheck",
|
||||
"prepare": "husky install",
|
||||
"preview": "vite preview",
|
||||
"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:watch": "tsc --noEmit --watch"
|
||||
},
|
||||
|
@ -44,19 +44,21 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.23.3",
|
||||
"@biomejs/biome": "^1.5.3",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/typescript": "4.0.1",
|
||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||
"@graphql-codegen/typescript-urql": "4.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",
|
||||
"@hocuspocus/provider": "2.0.6",
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@nanostores/router": "0.11.0",
|
||||
"@nanostores/router": "0.13.0",
|
||||
"@nanostores/solid": "0.4.2",
|
||||
"@playwright/test": "1.41.2",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@sentry/browser": "5.30.0",
|
||||
"@sentry/browser": "7.99.0",
|
||||
"@solid-primitives/media": "2.2.3",
|
||||
"@solid-primitives/memo": "1.2.4",
|
||||
"@solid-primitives/share": "2.0.4",
|
||||
|
@ -95,25 +97,13 @@
|
|||
"@tiptap/extension-youtube": "2.0.3",
|
||||
"@types/js-cookie": "3.0.6",
|
||||
"@types/node": "20.9.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.10.0",
|
||||
"@typescript-eslint/parser": "6.10.0",
|
||||
"@urql/core": "3.2.2",
|
||||
"@urql/core": "4.2.3",
|
||||
"@urql/devtools": "2.0.3",
|
||||
"babel-preset-solid": "1.8.4",
|
||||
"bootstrap": "5.3.2",
|
||||
"change-case": "5.2.0",
|
||||
"clsx": "2.0.0",
|
||||
"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",
|
||||
"graphql": "16.8.1",
|
||||
"graphql-tag": "2.12.6",
|
||||
|
@ -127,8 +117,6 @@
|
|||
"loglevel": "1.8.1",
|
||||
"loglevel-plugin-prefix": "0.8.4",
|
||||
"nanostores": "0.9.5",
|
||||
"prettier": "3.1.0",
|
||||
"prettier-eslint": "16.1.2",
|
||||
"prosemirror-history": "1.3.0",
|
||||
"prosemirror-trailing-node": "2.0.3",
|
||||
"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 { TopicPage } from '../pages/topic.page'
|
||||
import { ROUTES, useRouter } from '../stores/router'
|
||||
import { hideModal, MODALS, showModal } from '../stores/ui'
|
||||
import { MODALS, hideModal, showModal } from '../stores/ui'
|
||||
|
||||
// TODO: lazy load
|
||||
// const SomePage = lazy(() => import('./Pages/SomePage'))
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { createSignal, Show } from 'solid-js'
|
||||
import { Show, createSignal } from 'solid-js'
|
||||
|
||||
import { Topic } from '../../../graphql/schema/core.gen'
|
||||
import { MediaItem } from '../../../pages/types'
|
||||
import { CardTopic } from '../../Feed/CardTopic'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { Image } from '../../_shared/Image'
|
||||
import { CardTopic } from '../../Feed/CardTopic'
|
||||
|
||||
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'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { createSignal, Show } from 'solid-js'
|
||||
import { Show, createSignal } from 'solid-js'
|
||||
|
||||
import { MediaItem } from '../../../pages/types'
|
||||
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 { MediaItem } from '../../../pages/types'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { getPagePath } from '@nanostores/router'
|
||||
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 { useLocalize } from '../../../context/localize'
|
||||
|
@ -9,10 +9,10 @@ import { useSession } from '../../../context/session'
|
|||
import { useSnackbar } from '../../../context/snackbar'
|
||||
import { Author, Reaction, ReactionKind } from '../../../graphql/schema/core.gen'
|
||||
import { router } from '../../../stores/router'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { ShowIfAuthenticated } from '../../_shared/ShowIfAuthenticated'
|
||||
import { AuthorLink } from '../../Author/AuthorLink'
|
||||
import { Userpic } from '../../Author/Userpic'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { ShowIfAuthenticated } from '../../_shared/ShowIfAuthenticated'
|
||||
import { CommentDate } from '../CommentDate'
|
||||
import { CommentRatingControl } from '../CommentRatingControl'
|
||||
|
||||
|
@ -39,23 +39,14 @@ export const Comment = (props: Props) => {
|
|||
const [editMode, setEditMode] = createSignal(false)
|
||||
const [clearEditor, setClearEditor] = createSignal(false)
|
||||
const { author } = useSession()
|
||||
|
||||
const {
|
||||
actions: { createReaction, deleteReaction, updateReaction },
|
||||
} = useReactions()
|
||||
|
||||
const {
|
||||
actions: { showConfirm },
|
||||
} = useConfirm()
|
||||
|
||||
const {
|
||||
actions: { showSnackbar },
|
||||
} = useSnackbar()
|
||||
const { createReaction, deleteReaction, updateReaction } = useReactions()
|
||||
const { showConfirm } = useConfirm()
|
||||
const { showSnackbar } = useSnackbar()
|
||||
|
||||
const isCommentAuthor = createMemo(() => props.comment.created_by?.slug === author()?.slug)
|
||||
|
||||
const comment = createMemo(() => props.comment)
|
||||
const body = createMemo(() => (comment().body || '').trim())
|
||||
|
||||
const remove = async () => {
|
||||
if (comment()?.id) {
|
||||
try {
|
||||
|
|
|
@ -19,13 +19,8 @@ type Props = {
|
|||
export const CommentRatingControl = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const { author } = useSession()
|
||||
const {
|
||||
actions: { showSnackbar },
|
||||
} = useSnackbar()
|
||||
const {
|
||||
reactionEntities,
|
||||
actions: { createReaction, deleteReaction, loadReactionsBy },
|
||||
} = useReactions()
|
||||
const { showSnackbar } = useSnackbar()
|
||||
const { reactionEntities, createReaction, deleteReaction, loadReactionsBy } = useReactions()
|
||||
|
||||
const checkReaction = (reactionKind: ReactionKind) =>
|
||||
Object.values(reactionEntities).some(
|
||||
|
@ -86,7 +81,7 @@ export const CommentRatingControl = (props: Props) => {
|
|||
<div class={styles.commentRating}>
|
||||
<button
|
||||
role="button"
|
||||
disabled={!canVote() || !author()}
|
||||
disabled={!(canVote() && author())}
|
||||
onClick={() => handleRatingChange(true)}
|
||||
class={clsx(styles.commentRatingControl, styles.commentRatingControlUp, {
|
||||
[styles.voted]: isUpvoted(),
|
||||
|
@ -112,7 +107,7 @@ export const CommentRatingControl = (props: Props) => {
|
|||
</Popup>
|
||||
<button
|
||||
role="button"
|
||||
disabled={!canVote() || !author()}
|
||||
disabled={!(canVote() && author())}
|
||||
onClick={() => handleRatingChange(false)}
|
||||
class={clsx(styles.commentRatingControl, styles.commentRatingControlDown, {
|
||||
[styles.voted]: isDownvoted(),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 { useReactions } from '../../context/reactions'
|
||||
|
@ -22,8 +22,8 @@ const sortCommentsByRating = (a: Reaction, b: Reaction): -1 | 0 | 1 => {
|
|||
return 0
|
||||
}
|
||||
|
||||
const x = (a?.stat && a.stat.rating) || 0
|
||||
const y = (b?.stat && b.stat.rating) || 0
|
||||
const x = a.stat?.rating || 0
|
||||
const y = b.stat?.rating || 0
|
||||
|
||||
if (x > y) {
|
||||
return 1
|
||||
|
@ -49,11 +49,7 @@ export const CommentsTree = (props: Props) => {
|
|||
const [newReactions, setNewReactions] = createSignal<Reaction[]>([])
|
||||
const [clearEditor, setClearEditor] = createSignal(false)
|
||||
const [clickedReplyId, setClickedReplyId] = createSignal<number>()
|
||||
|
||||
const {
|
||||
reactionEntities,
|
||||
actions: { createReaction },
|
||||
} = useReactions()
|
||||
const { reactionEntities, createReaction } = useReactions()
|
||||
|
||||
const comments = createMemo(() =>
|
||||
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT'),
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import type { CoverImageProps } from './types'
|
||||
|
||||
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 { CoverImage3 } from './images/CoverImage3'
|
||||
import { CoverImage4 } from './images/CoverImage4'
|
||||
|
@ -12,6 +9,9 @@ import { CoverImage6 } from './images/CoverImage6'
|
|||
import { CoverImage7 } from './images/CoverImage7'
|
||||
import { CoverImage8 } from './images/CoverImage8'
|
||||
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
|
||||
const coverImages = [
|
||||
|
|
|
@ -5,7 +5,7 @@ import { createPopper } from '@popperjs/core'
|
|||
import { Link, Meta } from '@solidjs/meta'
|
||||
import { clsx } from 'clsx'
|
||||
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 { useLocalize } from '../../context/localize'
|
||||
|
@ -15,9 +15,14 @@ import { MediaItem } from '../../pages/types'
|
|||
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
|
||||
import { showModal } from '../../stores/ui'
|
||||
import { capitalize } from '../../utils/capitalize'
|
||||
import { isCyrillic } from '../../utils/cyrillic'
|
||||
import { getImageUrl, getOpenGraphImageUrl } from '../../utils/getImageUrl'
|
||||
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 { Image } from '../_shared/Image'
|
||||
import { InviteMembers } from '../_shared/InviteMembers'
|
||||
|
@ -26,20 +31,15 @@ import { Popover } from '../_shared/Popover'
|
|||
import { ShareModal } from '../_shared/ShareModal'
|
||||
import { ImageSwiper } from '../_shared/SolidSwiper'
|
||||
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 { AudioPlayer } from './AudioPlayer'
|
||||
import { CommentsTree } from './CommentsTree'
|
||||
import { getShareUrl, SharePopup } from './SharePopup'
|
||||
import { SharePopup, getShareUrl } from './SharePopup'
|
||||
import { ShoutRatingControl } from './ShoutRatingControl'
|
||||
|
||||
import styles from './Article.module.scss'
|
||||
import stylesHeader from '../Nav/Header/Header.module.scss'
|
||||
import styles from './Article.module.scss'
|
||||
|
||||
type Props = {
|
||||
article: Shout
|
||||
|
@ -69,32 +69,27 @@ const scrollTo = (el: HTMLElement) => {
|
|||
const imgSrcRegExp = /<img[^>]+src\s*=\s*["']([^"']+)["']/gi
|
||||
|
||||
export const FullArticle = (props: Props) => {
|
||||
const { searchParams, changeSearchParams } = useRouter<ArticlePageSearchParams>()
|
||||
const { loadReactionsBy } = useReactions()
|
||||
const [selectedImage, setSelectedImage] = createSignal('')
|
||||
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
|
||||
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
|
||||
|
||||
const { t, formatDate, lang } = useLocalize()
|
||||
const {
|
||||
author,
|
||||
isAuthenticated,
|
||||
actions: { requireAuthentication },
|
||||
} = useSession()
|
||||
const { author, isAuthenticated, requireAuthentication } = useSession()
|
||||
|
||||
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 main_topic_slug = props.article.topics.length > 0 ? props.article.main_topic : null
|
||||
const mt = props.article.topics.find((tpc: Topic) => tpc.slug === main_topic_slug)
|
||||
const mainTopicSlug = props.article.topics.length > 0 ? props.article.main_topic : null
|
||||
const mt = props.article.topics.find((tpc: Topic) => tpc.slug === mainTopicSlug)
|
||||
if (mt) {
|
||||
mt.title = lang() === 'en' ? capitalize(mt.slug.replace(/-/, ' ')) : mt.title
|
||||
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) => {
|
||||
requireAuthentication(() => {
|
||||
// TODO: implement bookmark clicked
|
||||
|
@ -154,8 +149,6 @@ export const FullArticle = (props: Props) => {
|
|||
scrollTo(commentsRef.current)
|
||||
}
|
||||
|
||||
const { searchParams, changeSearchParams } = useRouter<ArticlePageSearchParams>()
|
||||
|
||||
createEffect(() => {
|
||||
if (props.scrollToComments) {
|
||||
scrollToComments()
|
||||
|
@ -185,10 +178,6 @@ export const FullArticle = (props: Props) => {
|
|||
}
|
||||
})
|
||||
|
||||
const {
|
||||
actions: { loadReactionsBy },
|
||||
} = useReactions()
|
||||
|
||||
const clickHandlers = []
|
||||
const documentClickHandlers = []
|
||||
|
||||
|
@ -284,7 +273,7 @@ export const FullArticle = (props: Props) => {
|
|||
}
|
||||
|
||||
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
|
||||
openLightbox(getImageUrl(src))
|
||||
}
|
||||
|
@ -293,7 +282,7 @@ export const FullArticle = (props: Props) => {
|
|||
// Check iframes size
|
||||
const articleContainer: { current: HTMLElement } = { current: null }
|
||||
const updateIframeSizes = () => {
|
||||
if (!articleContainer?.current || !props.article.body) return
|
||||
if (!(articleContainer?.current && props.article.body)) return
|
||||
const iframes = articleContainer?.current?.querySelectorAll('iframe')
|
||||
if (!iframes) return
|
||||
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 ogImage = getOpenGraphImageUrl(cover, {
|
||||
title: props.article.title,
|
||||
topic: mainTopic().title,
|
||||
author: props.article.authors[0].name,
|
||||
topic: mainTopic()?.title || '',
|
||||
author: props.article?.authors[0]?.name || '',
|
||||
width: 1200,
|
||||
})
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { createMemo, createSignal, Show } from 'solid-js'
|
||||
import { Show, createMemo, createSignal } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { useReactions } from '../../context/reactions'
|
||||
|
@ -19,16 +19,8 @@ interface ShoutRatingControlProps {
|
|||
|
||||
export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
||||
const { t } = useLocalize()
|
||||
const {
|
||||
author,
|
||||
actions: { requireAuthentication },
|
||||
} = useSession()
|
||||
|
||||
const {
|
||||
reactionEntities,
|
||||
actions: { createReaction, deleteReaction, loadReactionsBy },
|
||||
} = useReactions()
|
||||
|
||||
const { author, requireAuthentication } = useSession()
|
||||
const { reactionEntities, createReaction, deleteReaction, loadReactionsBy } = useReactions()
|
||||
const [isLoading, setIsLoading] = createSignal(false)
|
||||
|
||||
const checkReaction = (reactionKind: ReactionKind) =>
|
||||
|
@ -60,7 +52,7 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
|
|||
return deleteReaction(reactionToDelete.id)
|
||||
}
|
||||
|
||||
const handleRatingChange = async (isUpvote: boolean) => {
|
||||
const handleRatingChange = (isUpvote: boolean) => {
|
||||
requireAuthentication(async () => {
|
||||
setIsLoading(true)
|
||||
if (isUpvoted()) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { JSX, createSignal } from 'solid-js'
|
||||
import './Tooltip.scss'
|
||||
import { createSignal, JSX } from 'solid-js'
|
||||
|
||||
interface TooltipProps {
|
||||
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 { RootSearchParams } from '../../pages/types'
|
||||
|
@ -15,7 +15,7 @@ export const AuthGuard = (props: Props) => {
|
|||
const { isAuthenticated, isSessionLoaded } = useSession()
|
||||
const { changeSearchParams } = useRouter<RootSearchParams & AuthModalSearchParams>()
|
||||
|
||||
createEffect(async () => {
|
||||
createEffect(() => {
|
||||
if (props.disabled) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { openPage } from '@nanostores/router'
|
||||
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 { useLocalize } from '../../../context/localize'
|
||||
|
@ -8,16 +8,16 @@ import { useMediaQuery } from '../../../context/mediaQuery'
|
|||
import { useSession } from '../../../context/session'
|
||||
import { Author, FollowingEntity } from '../../../graphql/schema/core.gen'
|
||||
import { router, useRouter } from '../../../stores/router'
|
||||
import { isCyrillic } from '../../../utils/cyrillic'
|
||||
import { translit } from '../../../utils/ru2en'
|
||||
import { isCyrillic } from '../../../utils/translate'
|
||||
import { Button } from '../../_shared/Button'
|
||||
import { CheckButton } from '../../_shared/CheckButton'
|
||||
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { Userpic } from '../Userpic'
|
||||
|
||||
import styles from './AuthorBadge.module.scss'
|
||||
import stylesButton from '../../_shared/Button/Button.module.scss'
|
||||
import styles from './AuthorBadge.module.scss'
|
||||
|
||||
type FollowedInfo = {
|
||||
value?: boolean
|
||||
|
@ -36,11 +36,7 @@ type Props = {
|
|||
}
|
||||
export const AuthorBadge = (props: Props) => {
|
||||
const { mediaMatches } = useMediaQuery()
|
||||
const {
|
||||
author,
|
||||
actions: { requireAuthentication },
|
||||
} = useSession()
|
||||
|
||||
const { author, requireAuthentication } = useSession()
|
||||
const [isMobileView, setIsMobileView] = createSignal(false)
|
||||
const [isFollowed, setIsFollowed] = createSignal<boolean>()
|
||||
|
||||
|
@ -55,7 +51,7 @@ export const AuthorBadge = (props: Props) => {
|
|||
const initChat = () => {
|
||||
// eslint-disable-next-line solid/reactivity
|
||||
requireAuthentication(() => {
|
||||
openPage(router, `inbox`)
|
||||
openPage(router, 'inbox')
|
||||
changeSearchParams({
|
||||
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 { 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 { useLocalize } from '../../../context/localize'
|
||||
|
@ -10,19 +10,19 @@ import { useSession } from '../../../context/session'
|
|||
import { FollowingEntity, Topic } from '../../../graphql/schema/core.gen'
|
||||
import { SubscriptionFilter } from '../../../pages/types'
|
||||
import { router, useRouter } from '../../../stores/router'
|
||||
import { isCyrillic } from '../../../utils/cyrillic'
|
||||
import { isAuthor } from '../../../utils/isAuthor'
|
||||
import { translit } from '../../../utils/ru2en'
|
||||
import { Button } from '../../_shared/Button'
|
||||
import { ShowOnlyOnClient } from '../../_shared/ShowOnlyOnClient'
|
||||
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
|
||||
import { isCyrillic } from '../../../utils/translate'
|
||||
import { SharePopup, getShareUrl } from '../../Article/SharePopup'
|
||||
import { Modal } from '../../Nav/Modal'
|
||||
import { TopicBadge } from '../../Topic/TopicBadge'
|
||||
import { Button } from '../../_shared/Button'
|
||||
import { ShowOnlyOnClient } from '../../_shared/ShowOnlyOnClient'
|
||||
import { AuthorBadge } from '../AuthorBadge'
|
||||
import { Userpic } from '../Userpic'
|
||||
|
||||
import styles from './AuthorCard.module.scss'
|
||||
import stylesButton from '../../_shared/Button/Button.module.scss'
|
||||
import styles from './AuthorCard.module.scss'
|
||||
|
||||
type Props = {
|
||||
author: Author
|
||||
|
@ -31,12 +31,7 @@ type Props = {
|
|||
}
|
||||
export const AuthorCard = (props: Props) => {
|
||||
const { t, lang } = useLocalize()
|
||||
const {
|
||||
author,
|
||||
isSessionLoaded,
|
||||
actions: { requireAuthentication },
|
||||
} = useSession()
|
||||
|
||||
const { author, isSessionLoaded, requireAuthentication } = useSession()
|
||||
const [authorSubs, setAuthorSubs] = createSignal<Array<Author | Topic | Community>>([])
|
||||
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
|
||||
const [isFollowed, setIsFollowed] = createSignal<boolean>()
|
||||
|
@ -66,7 +61,7 @@ export const AuthorCard = (props: Props) => {
|
|||
const initChat = () => {
|
||||
// eslint-disable-next-line solid/reactivity
|
||||
requireAuthentication(() => {
|
||||
openPage(router, `inbox`)
|
||||
openPage(router, 'inbox')
|
||||
changeSearchParams({
|
||||
initChat: props.author.id.toString(),
|
||||
})
|
||||
|
@ -157,7 +152,9 @@ export const AuthorCard = (props: Props) => {
|
|||
class={styles.subscribersItem}
|
||||
/>
|
||||
)
|
||||
} else if ('title' in f) {
|
||||
}
|
||||
|
||||
if ('title' in f) {
|
||||
return (
|
||||
<Userpic
|
||||
size={'XS'}
|
||||
|
@ -167,6 +164,7 @@ export const AuthorCard = (props: Props) => {
|
|||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}}
|
||||
</For>
|
||||
|
@ -188,7 +186,7 @@ export const AuthorCard = (props: Props) => {
|
|||
class={styles.socialLink}
|
||||
href={link.startsWith('http') ? link : `https://${link}`}
|
||||
target="_blank"
|
||||
rel="nofollow noopener"
|
||||
rel="nofollow noopener noreferrer"
|
||||
>
|
||||
<span class={styles.authorSubscribeSocialLabel}>
|
||||
{link.startsWith('http') ? link : `https://${link}`}
|
||||
|
|
|
@ -4,8 +4,8 @@ import { createMemo } from 'solid-js'
|
|||
import { useLocalize } from '../../../context/localize'
|
||||
import { Author } from '../../../graphql/schema/core.gen'
|
||||
import { capitalize } from '../../../utils/capitalize'
|
||||
import { isCyrillic } from '../../../utils/cyrillic'
|
||||
import { translit } from '../../../utils/ru2en'
|
||||
import { isCyrillic } from '../../../utils/translate'
|
||||
import { Userpic } from '../Userpic'
|
||||
|
||||
import styles from './AhtorLink.module.scss'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { createMemo, Show } from 'solid-js'
|
||||
import { Show, createMemo } from 'solid-js'
|
||||
|
||||
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
||||
import { Image } from '../../_shared/Image'
|
||||
|
@ -22,7 +22,7 @@ export const Userpic = (props: Props) => {
|
|||
const letters = () => {
|
||||
if (!props.name) return
|
||||
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(() => {
|
||||
|
@ -48,7 +48,7 @@ export const Userpic = (props: Props) => {
|
|||
return (
|
||||
<div
|
||||
class={clsx(styles.Userpic, props.class, styles[props.size ?? 'M'], {
|
||||
['cursorPointer']: props.onClick,
|
||||
cursorPointer: props.onClick,
|
||||
})}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
|
|
|
@ -7,6 +7,9 @@ import { showModal } from '../../stores/ui'
|
|||
|
||||
import styles from './Donate.module.scss'
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
|
||||
type DWindow = Window & { cp: any }
|
||||
|
||||
export const Donate = () => {
|
||||
const { t } = useLocalize()
|
||||
const once = ''
|
||||
|
@ -24,43 +27,44 @@ export const Donate = () => {
|
|||
const [showingPayment, setShowingPayment] = createSignal<boolean>()
|
||||
const [period, setPeriod] = createSignal(monthly)
|
||||
const [amount, setAmount] = createSignal(0)
|
||||
const {
|
||||
actions: { showSnackbar },
|
||||
} = useSnackbar()
|
||||
const { showSnackbar } = useSnackbar()
|
||||
|
||||
const initiated = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const CloudPayments = window['cp'] // Checkout(cpOptions)
|
||||
setWidget(new CloudPayments())
|
||||
console.log('[donate] payments initiated')
|
||||
setCustomerReciept({
|
||||
Items: [
|
||||
//товарные позиции
|
||||
{
|
||||
label: cpOptions.description, //наименование товара
|
||||
price: amount() || 0, //цена
|
||||
quantity: 1, //количество
|
||||
amount: amount() || 0, //сумма
|
||||
vat: 20, //ставка НДС
|
||||
method: 0, // тег-1214 признак способа расчета - признак способа расчета
|
||||
object: 0, // тег-1212 признак предмета расчета - признак предмета товара, работы, услуги, платежа, выплаты, иного предмета расчета
|
||||
try {
|
||||
const { cp: CloudPayments } = window as unknown as DWindow
|
||||
|
||||
setWidget(new CloudPayments())
|
||||
console.log('[donate] payments initiated')
|
||||
setCustomerReciept({
|
||||
Items: [
|
||||
//товарные позиции
|
||||
{
|
||||
label: cpOptions.description, //наименование товара
|
||||
price: amount() || 0, //цена
|
||||
quantity: 1, //количество
|
||||
amount: amount() || 0, //сумма
|
||||
vat: 20, //ставка НДС
|
||||
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, //система налогообложения; необязательный, если у вас одна система налогообложения
|
||||
// email: 'user@example.com', //e-mail покупателя, если нужно отправить письмо с чеком
|
||||
// phone: '', //телефон покупателя в любом формате, если нужно отправить сообщение со ссылкой на чек
|
||||
isBso: false, //чек является бланком строгой отчетности
|
||||
amounts: {
|
||||
electronic: amount(), // Сумма оплаты электронными деньгами
|
||||
advancePayment: 0, // Сумма из предоплаты (зачетом аванса) (2 знака после запятой)
|
||||
credit: 0, // Сумма постоплатой(в кредит) (2 знака после запятой)
|
||||
provision: 0, // Сумма оплаты встречным предоставлением (сертификаты, др. мат.ценности) (2 знака после запятой)
|
||||
},
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const script = document.createElement('script')
|
||||
script.type = 'text/javascript'
|
||||
script.src = 'https://widget.cloudpayments.ru/bundles/cloudpayments.js'
|
||||
|
@ -76,8 +80,8 @@ export const Donate = () => {
|
|||
const choice: HTMLInputElement | undefined | null =
|
||||
amountSwitchElement?.querySelector('input[type=radio]:checked')
|
||||
setAmount(Number.parseInt(customAmountElement?.value || choice?.value || '0'))
|
||||
console.log('[donate] input amount ' + amount)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
console.log(`[donate] input amount ${amount}`)
|
||||
// biome-ignore lint/suspicious/noExplicitAny: it's a widget!
|
||||
;(widget() as any).charge(
|
||||
{
|
||||
// options
|
||||
|
@ -105,7 +109,7 @@ export const Donate = () => {
|
|||
console.debug('[donate] options', opts)
|
||||
showModal('thank')
|
||||
},
|
||||
function (reason: string, options) {
|
||||
(reason: string, options) => {
|
||||
// fail
|
||||
// действие при неуспешной оплате
|
||||
console.debug('[donate] options', options)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { createMemo, For } from 'solid-js'
|
||||
import { For, createMemo } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
|
@ -11,7 +11,7 @@ export const Footer = () => {
|
|||
const { t, lang } = useLocalize()
|
||||
|
||||
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(() => [
|
||||
{
|
||||
header: 'About the project',
|
||||
|
@ -89,7 +89,7 @@ export const Footer = () => {
|
|||
},
|
||||
])
|
||||
|
||||
const SOCIAL = [
|
||||
const social = [
|
||||
{
|
||||
name: 'facebook',
|
||||
href: 'https://facebook.com/discoursio',
|
||||
|
@ -146,7 +146,7 @@ export const Footer = () => {
|
|||
<a href="/about/terms-of-use">{t('Terms of use')}</a>
|
||||
</div>
|
||||
<div class={clsx(styles.footerCopyrightSocial, 'col-md-6 col-lg-4')}>
|
||||
<For each={SOCIAL}>
|
||||
<For each={social}>
|
||||
{(social) => (
|
||||
<div class={clsx(styles.socialItem, styles[`socialItem${social.name}`])}>
|
||||
<a href={social.href}>
|
||||
|
|
|
@ -20,13 +20,8 @@ type Props = {
|
|||
|
||||
export const Draft = (props: Props) => {
|
||||
const { t, formatDate } = useLocalize()
|
||||
const {
|
||||
actions: { showConfirm },
|
||||
} = useConfirm()
|
||||
|
||||
const {
|
||||
actions: { showSnackbar },
|
||||
} = useSnackbar()
|
||||
const { showConfirm } = useConfirm()
|
||||
const { showSnackbar } = useSnackbar()
|
||||
|
||||
const handlePublishLinkClick = (e) => {
|
||||
e.preventDefault()
|
||||
|
|
|
@ -6,8 +6,8 @@ import { Show } from 'solid-js'
|
|||
import { useLocalize } from '../../../context/localize'
|
||||
import { MediaItem } from '../../../pages/types'
|
||||
import { composeMediaItems } from '../../../utils/composeMediaItems'
|
||||
import { DropArea } from '../../_shared/DropArea'
|
||||
import { AudioPlayer } from '../../Article/AudioPlayer'
|
||||
import { DropArea } from '../../_shared/DropArea'
|
||||
|
||||
import styles from './AudioUploader.module.scss'
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ import type { Editor } from '@tiptap/core'
|
|||
import { useLocalize } from '../../../context/localize'
|
||||
import { UploadedFile } from '../../../pages/types'
|
||||
import { renderUploadedImage } from '../../../utils/renderUploadedImage'
|
||||
import { Modal } from '../../Nav/Modal'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { Popover } from '../../_shared/Popover'
|
||||
import { Modal } from '../../Nav/Modal'
|
||||
import { UploadModalContent } from '../UploadModalContent'
|
||||
|
||||
import styles from './BubbleMenu.module.scss'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { Editor } from '@tiptap/core'
|
||||
|
||||
import { clsx } from 'clsx'
|
||||
import { createSignal, Show, For } from 'solid-js'
|
||||
import { For, Show, createSignal } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
|
|
|
@ -38,8 +38,9 @@ import { useSession } from '../../context/session'
|
|||
import { useSnackbar } from '../../context/snackbar'
|
||||
import { handleImageUpload } from '../../utils/handleImageUpload'
|
||||
|
||||
import { FigureBubbleMenu, BlockquoteBubbleMenu, IncutBubbleMenu } from './BubbleMenu'
|
||||
import { BlockquoteBubbleMenu, FigureBubbleMenu, IncutBubbleMenu } from './BubbleMenu'
|
||||
import { EditorFloatingMenu } from './EditorFloatingMenu'
|
||||
import { TextBubbleMenu } from './TextBubbleMenu'
|
||||
import Article from './extensions/Article'
|
||||
import { CustomBlockquote } from './extensions/CustomBlockquote'
|
||||
import { Figcaption } from './extensions/Figcaption'
|
||||
|
@ -49,7 +50,6 @@ import { Iframe } from './extensions/Iframe'
|
|||
import { Span } from './extensions/Span'
|
||||
import { ToggleTextWrap } from './extensions/ToggleTextWrap'
|
||||
import { TrailingNode } from './extensions/TrailingNode'
|
||||
import { TextBubbleMenu } from './TextBubbleMenu'
|
||||
|
||||
import './Prosemirror.scss'
|
||||
|
||||
|
@ -80,9 +80,7 @@ export const Editor = (props: Props) => {
|
|||
const [isCommonMarkup, setIsCommonMarkup] = createSignal(false)
|
||||
const [shouldShowTextBubbleMenu, setShouldShowTextBubbleMenu] = createSignal(false)
|
||||
|
||||
const {
|
||||
actions: { showSnackbar },
|
||||
} = useSnackbar()
|
||||
const { showSnackbar } = useSnackbar()
|
||||
|
||||
const docName = `shout-${props.shoutId}`
|
||||
|
||||
|
@ -337,10 +335,7 @@ export const Editor = (props: Props) => {
|
|||
content: initialContent ?? null,
|
||||
}))
|
||||
|
||||
const {
|
||||
actions: { countWords, setEditor },
|
||||
} = useEditorContext()
|
||||
|
||||
const { countWords, setEditor } = useEditorContext()
|
||||
setEditor(editor)
|
||||
|
||||
const html = useEditorHTML(() => editor())
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import type { MenuItem } from './Menu/Menu'
|
||||
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 { UploadedFile } from '../../../pages/types'
|
||||
import { showModal } from '../../../stores/ui'
|
||||
import { renderUploadedImage } from '../../../utils/renderUploadedImage'
|
||||
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { Modal } from '../../Nav/Modal'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { InlineForm } from '../InlineForm'
|
||||
import { UploadModalContent } from '../UploadModalContent'
|
||||
|
||||
|
@ -22,7 +22,7 @@ type FloatingMenuProps = {
|
|||
ref: (el: HTMLDivElement) => void
|
||||
}
|
||||
|
||||
const embedData = async (data) => {
|
||||
const embedData = (data) => {
|
||||
const element = document.createRange().createContextualFragment(data)
|
||||
const { attributes } = element.firstChild as HTMLIFrameElement
|
||||
|
||||
|
@ -69,7 +69,7 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => {
|
|||
.run()
|
||||
}
|
||||
|
||||
const validateEmbed = async (value) => {
|
||||
const validateEmbed = (value) => {
|
||||
const element = document.createRange().createContextualFragment(value)
|
||||
if (element.firstChild?.nodeName !== 'IFRAME') {
|
||||
return t('Error')
|
||||
|
|
|
@ -24,7 +24,7 @@ export const InsertLinkForm = (props: Props) => {
|
|||
const currentUrl = createEditorTransaction(
|
||||
() => props.editor,
|
||||
(ed) => {
|
||||
return (ed && ed.getAttributes('link').href) || ''
|
||||
return ed?.getAttributes('link').href || ''
|
||||
},
|
||||
)
|
||||
const handleClearLinkForm = () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { getPagePath } from '@nanostores/router'
|
||||
import { clsx } from 'clsx'
|
||||
import { createSignal, Show } from 'solid-js'
|
||||
import { Show, createSignal } from 'solid-js'
|
||||
import { useEditorHTML } from 'solid-tiptap'
|
||||
import Typograf from 'typograf'
|
||||
|
||||
|
@ -24,18 +24,10 @@ type Props = {
|
|||
|
||||
export const Panel = (props: Props) => {
|
||||
const { t } = useLocalize()
|
||||
const {
|
||||
isEditorPanelVisible,
|
||||
wordCounter,
|
||||
editorRef,
|
||||
form,
|
||||
actions: { toggleEditorPanel, saveShout, publishShout },
|
||||
} = useEditorContext()
|
||||
|
||||
const containerRef: { current: HTMLElement } = {
|
||||
current: null,
|
||||
}
|
||||
const { isEditorPanelVisible, wordCounter, editorRef, form, toggleEditorPanel, saveShout, publishShout } =
|
||||
useEditorContext()
|
||||
|
||||
const containerRef: { current: HTMLElement } = { current: null }
|
||||
const [isShortcutsVisible, setIsShortcutsVisible] = createSignal(false)
|
||||
const [isTypographyFixed, setIsTypographyFixed] = createSignal(false)
|
||||
|
||||
|
@ -59,8 +51,9 @@ export const Panel = (props: Props) => {
|
|||
publishShout(form)
|
||||
}
|
||||
|
||||
const html = useEditorHTML(() => editorRef.current())
|
||||
|
||||
const handleFixTypographyClick = () => {
|
||||
const html = useEditorHTML(() => editorRef.current())
|
||||
editorRef.current().commands.setContent(typograf.execute(html()))
|
||||
setIsTypographyFixed(true)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import { Paragraph } from '@tiptap/extension-paragraph'
|
|||
import { Placeholder } from '@tiptap/extension-placeholder'
|
||||
import { Text } from '@tiptap/extension-text'
|
||||
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 {
|
||||
createEditorTransaction,
|
||||
|
@ -24,17 +24,17 @@ import { useEditorContext } from '../../context/editor'
|
|||
import { useLocalize } from '../../context/localize'
|
||||
import { UploadedFile } from '../../pages/types'
|
||||
import { hideModal, showModal } from '../../stores/ui'
|
||||
import { Modal } from '../Nav/Modal'
|
||||
import { Button } from '../_shared/Button'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { Popover } from '../_shared/Popover'
|
||||
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 { TextBubbleMenu } from './TextBubbleMenu'
|
||||
import { UploadModalContent } from './UploadModalContent'
|
||||
import { Figcaption } from './extensions/Figcaption'
|
||||
import { Figure } from './extensions/Figure'
|
||||
|
||||
import styles from './SimplifiedEditor.module.scss'
|
||||
|
||||
|
@ -94,9 +94,7 @@ const SimplifiedEditor = (props: Props) => {
|
|||
current: null,
|
||||
}
|
||||
|
||||
const {
|
||||
actions: { setEditor },
|
||||
} = useEditorContext()
|
||||
const { setEditor } = useEditorContext()
|
||||
|
||||
const ImageFigure = Figure.extend({
|
||||
name: 'capturedImage',
|
||||
|
@ -173,7 +171,7 @@ const SimplifiedEditor = (props: Props) => {
|
|||
createEditorTransaction(
|
||||
() => editor(),
|
||||
(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()) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { Editor } from '@tiptap/core'
|
||||
|
||||
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 { useLocalize } from '../../../context/localize'
|
||||
|
@ -26,7 +26,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
|||
const isActive = (name: string, attributes?: unknown) =>
|
||||
createEditorTransaction(
|
||||
() => props.editor,
|
||||
(editor) => editor && editor.isActive(name, attributes),
|
||||
(editor) => editor?.isActive(name, attributes),
|
||||
)
|
||||
|
||||
const [textSizeBubbleOpen, setTextSizeBubbleOpen] = createSignal(false)
|
||||
|
@ -71,7 +71,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
|||
}
|
||||
setListBubbleOpen((prev) => !prev)
|
||||
}
|
||||
const handleKeyDown = async (event) => {
|
||||
const handleKeyDown = (event) => {
|
||||
if (event.code === 'KeyK' && (event.metaKey || event.ctrlKey) && !props.editor.state.selection.empty) {
|
||||
event.preventDefault()
|
||||
setLinkEditorOpen(true)
|
||||
|
@ -160,7 +160,7 @@ export const TextBubbleMenu = (props: BubbleMenuProps) => {
|
|||
submitButtonText={t('Send')}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={!linkEditorOpen() || !footnoteEditorOpen()}>
|
||||
<Match when={!(linkEditorOpen() && footnoteEditorOpen())}>
|
||||
<>
|
||||
<Show when={!props.isCommonMarkup}>
|
||||
<>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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 { 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 { createSignal, Show } from 'solid-js'
|
||||
import { Show, createSignal } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { UploadedFile } from '../../../pages/types'
|
||||
|
@ -56,7 +56,7 @@ export const UploadModalContent = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleUpload = async () => {
|
||||
const handleUpload = () => {
|
||||
selectFiles(async ([uploadFile]) => {
|
||||
await runUpload(uploadFile)
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { MediaItem } from '../../../pages/types'
|
|||
|
||||
import { createDropzone } from '@solid-primitives/upload'
|
||||
import { clsx } from 'clsx'
|
||||
import { createSignal, For, Show } from 'solid-js'
|
||||
import { For, Show, createSignal } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { useSnackbar } from '../../../context/snackbar'
|
||||
|
@ -24,9 +24,7 @@ export const VideoUploader = (props: Props) => {
|
|||
const [error, setError] = createSignal<string>()
|
||||
const [incorrectUrl, setIncorrectUrl] = createSignal<boolean>(false)
|
||||
|
||||
const {
|
||||
actions: { showSnackbar },
|
||||
} = useSnackbar()
|
||||
const { showSnackbar } = useSnackbar()
|
||||
|
||||
const urlInput: {
|
||||
current: HTMLInputElement
|
||||
|
@ -59,7 +57,7 @@ export const VideoUploader = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleUrlInput = async (value: string) => {
|
||||
const handleUrlInput = (value: string) => {
|
||||
setError()
|
||||
if (validateUrl(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' {
|
||||
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'
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { mergeAttributes, Node } from '@tiptap/core'
|
||||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
|
|
|
@ -41,9 +41,8 @@ export const ToggleTextWrap = Extension.create({
|
|||
if (changesApplied) {
|
||||
dispatch(tr)
|
||||
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 { 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 { useSession } from '../../../context/session'
|
||||
import { router, useRouter } from '../../../stores/router'
|
||||
import { capitalize } from '../../../utils/capitalize'
|
||||
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 { Image } from '../../_shared/Image'
|
||||
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 { FeedArticlePopup } from '../FeedArticlePopup'
|
||||
|
||||
import styles from './ArticleCard.module.scss'
|
||||
import stylesHeader from '../../Nav/Header/Header.module.scss'
|
||||
import styles from './ArticleCard.module.scss'
|
||||
|
||||
export type ArticleCardProps = {
|
||||
// TODO: refactor this, please
|
||||
|
@ -83,28 +83,47 @@ const getTitleAndSubtitle = (
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: simple fast auto translated title/substitle
|
||||
|
||||
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) => {
|
||||
const { t, lang, formatDate } = useLocalize()
|
||||
const { author } = useSession()
|
||||
const mainTopicSlug = props.article.main_topic || ''
|
||||
const mainTopic = props.article.topics?.find((tpc: Topic) => tpc.slug === mainTopicSlug)
|
||||
const mainTopicTitle =
|
||||
mainTopicSlug && lang() === 'en' ? mainTopicSlug.replace(/-/, ' ') : mainTopic?.title || ''
|
||||
const { changeSearchParams } = useRouter()
|
||||
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
|
||||
const [isCoverImageLoadError, setIsCoverImageLoadError] = createSignal(false)
|
||||
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>(() =>
|
||||
props.article.published_at ? formatDate(new Date(props.article.published_at * 1000)) : '',
|
||||
)
|
||||
|
||||
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
||||
|
||||
const canEdit = () =>
|
||||
props.article.authors?.some((a) => a && a?.slug === author()?.slug) ||
|
||||
props.article.created_by?.id === author()?.id
|
||||
|
||||
const { changeSearchParams } = useRouter()
|
||||
const scrollToComments = (event) => {
|
||||
event.preventDefault()
|
||||
openPage(router, 'article', { slug: props.article.slug })
|
||||
|
@ -112,28 +131,6 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
|||
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 (
|
||||
<section
|
||||
class={clsx(styles.shoutCard, props.settings?.additionalClass, {
|
||||
|
@ -152,7 +149,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
|||
[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={clsx(styles.shoutCardCover, {
|
||||
|
@ -223,7 +220,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
|||
</Show>
|
||||
</a>
|
||||
</div>
|
||||
<Show when={!props.settings?.noauthor || !props.settings?.nodate}>
|
||||
<Show when={!(props.settings?.noauthor && props.settings?.nodate)}>
|
||||
<div
|
||||
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 { useLocalize } from '../../context/localize'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { AuthorBadge } from '../Author/AuthorBadge'
|
||||
import { TopicCard } from '../Topic/Card'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
|
||||
import { ArticleCard } from './ArticleCard'
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { PopupProps } from '../../_shared/Popup'
|
||||
|
||||
import { clsx } from 'clsx'
|
||||
import { createSignal, Show } from 'solid-js'
|
||||
import { Show, createSignal } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
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 { Shout } from '../../graphql/schema/core.gen'
|
||||
|
||||
import { For, Show } from 'solid-js'
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { Shout } from '../../graphql/schema/core.gen'
|
||||
import type { JSX } from 'solid-js/jsx-runtime'
|
||||
import type { Shout } from '../../graphql/schema/core.gen'
|
||||
|
||||
import { For, Show } from 'solid-js'
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { getPagePath } from '@nanostores/router'
|
||||
import { clsx } from 'clsx'
|
||||
import { createSignal, For, Show } from 'solid-js'
|
||||
import { For, Show, createSignal } from 'solid-js'
|
||||
|
||||
import { useFollowing } from '../../../context/following'
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
|
@ -8,8 +8,8 @@ import { Author } from '../../../graphql/schema/core.gen'
|
|||
import { router, useRouter } from '../../../stores/router'
|
||||
import { useArticlesStore } from '../../../stores/zine/articles'
|
||||
import { useSeenStore } from '../../../stores/zine/seen'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { Userpic } from '../../Author/Userpic'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
|
||||
import styles from './Sidebar.module.scss'
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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 { useLocalize } from '../../context/localize'
|
||||
|
@ -21,6 +21,8 @@ const CreateModalContent = (props: Props) => {
|
|||
const [chatTitle, setChatTitle] = createSignal<string>('')
|
||||
const [usersId, setUsersId] = createSignal<number[]>([])
|
||||
const [collectionToInvite, setCollectionToInvite] = createSignal<inviteUser[]>(inviteUsers)
|
||||
const { createChat, loadChats } = useInbox()
|
||||
|
||||
let textInput: HTMLInputElement
|
||||
|
||||
const reset = () => {
|
||||
|
@ -36,7 +38,7 @@ const CreateModalContent = (props: Props) => {
|
|||
return user.selected === true
|
||||
})
|
||||
.map((user) => {
|
||||
return user['id']
|
||||
return user.id
|
||||
})
|
||||
return [...s]
|
||||
})
|
||||
|
@ -54,14 +56,12 @@ const CreateModalContent = (props: Props) => {
|
|||
})
|
||||
}
|
||||
|
||||
const { actions } = useInbox()
|
||||
|
||||
const handleCreate = async () => {
|
||||
try {
|
||||
const initChat = await actions.createChat(usersId(), chatTitle())
|
||||
const initChat = await createChat(usersId(), chatTitle())
|
||||
console.debug('[components.Inbox] create chat result:', initChat)
|
||||
hideModal()
|
||||
await actions.loadChats()
|
||||
await loadChats()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { ChatMember } from '../../graphql/schema/chat.gen'
|
||||
|
||||
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 { Author } from '../../graphql/schema/core.gen'
|
||||
|
@ -26,8 +26,8 @@ type DialogProps = {
|
|||
|
||||
const DialogCard = (props: DialogProps) => {
|
||||
const { t, formatTime } = useLocalize()
|
||||
const companions = createMemo(
|
||||
() => props.members && props.members.filter((member: ChatMember) => member.id !== props.ownId),
|
||||
const companions = createMemo(() =>
|
||||
props.members?.filter((member: ChatMember) => member.id !== props.ownId),
|
||||
)
|
||||
|
||||
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 { createSignal, Show } from 'solid-js'
|
||||
import { Show, createSignal } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../context/localize'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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 { Popup } from '../_shared/Popup'
|
||||
|
|
|
@ -187,7 +187,6 @@
|
|||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
margin-top: 0.3em;
|
||||
|
||||
color: var(--danger-color);
|
||||
|
||||
a {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { AuthModalSearchParams } from './types'
|
||||
|
||||
import { clsx } from 'clsx'
|
||||
import { createSignal, JSX, Show } from 'solid-js'
|
||||
import { JSX, Show, createSignal } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { useSession } from '../../../context/session'
|
||||
|
@ -21,9 +21,7 @@ type ValidationErrors = Partial<Record<keyof FormFields, string | JSX.Element>>
|
|||
export const ChangePasswordForm = () => {
|
||||
const { searchParams, changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||
const { t } = useLocalize()
|
||||
const {
|
||||
actions: { changePassword },
|
||||
} = useSession()
|
||||
const { changePassword } = useSession()
|
||||
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
||||
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
||||
const [newPassword, setNewPassword] = createSignal<string>()
|
||||
|
@ -68,7 +66,7 @@ export const ChangePasswordForm = () => {
|
|||
)}
|
||||
</div>
|
||||
<Show when={validationErrors()}>
|
||||
<div>{validationErrors()['password']}</div>
|
||||
<div>{validationErrors().password}</div>
|
||||
</Show>
|
||||
<PasswordField
|
||||
errorMessage={(err) => setPasswordError(err)}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { clsx } from 'clsx'
|
||||
import { createEffect, createSignal, Show } from 'solid-js'
|
||||
import { Show, createEffect, createSignal } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { useSession } from '../../../context/session'
|
||||
import { useRouter } from '../../../stores/router'
|
||||
import { hideModal } from '../../../stores/ui'
|
||||
|
||||
import { setEmail, email } from './sharedLogic'
|
||||
import { email, setEmail } from './sharedLogic'
|
||||
|
||||
import styles from './AuthModal.module.scss'
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { AuthModalSearchParams } from './types'
|
||||
|
||||
import { clsx } from 'clsx'
|
||||
import { createSignal, JSX, Show } from 'solid-js'
|
||||
import { JSX, Show, createSignal } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { useSession } from '../../../context/session'
|
||||
|
@ -25,9 +25,7 @@ export const ForgotPasswordForm = () => {
|
|||
setValidationErrors(({ email: _notNeeded, ...rest }) => rest)
|
||||
setEmail(newEmail.toLowerCase())
|
||||
}
|
||||
const {
|
||||
actions: { forgotPassword },
|
||||
} = useSession()
|
||||
const { forgotPassword } = useSession()
|
||||
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
||||
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
||||
const [isUserNotFound, setIsUserNotFound] = createSignal(false)
|
||||
|
@ -63,7 +61,7 @@ export const ForgotPasswordForm = () => {
|
|||
redirect_uri: window.location.origin,
|
||||
})
|
||||
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)
|
||||
}
|
||||
if (data.message) setMessage(data.message)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { AuthModalSearchParams } from './types'
|
||||
|
||||
import { clsx } from 'clsx'
|
||||
import { createSignal, Show } from 'solid-js'
|
||||
import { Show, createSignal } from 'solid-js'
|
||||
|
||||
import { useLocalize } from '../../../context/localize'
|
||||
import { useSession } from '../../../context/session'
|
||||
|
@ -12,8 +12,8 @@ import { validateEmail } from '../../../utils/validateEmail'
|
|||
|
||||
import { AuthModalHeader } from './AuthModalHeader'
|
||||
import { PasswordField } from './PasswordField'
|
||||
import { email, setEmail } from './sharedLogic'
|
||||
import { SocialProviders } from './SocialProviders'
|
||||
import { email, setEmail } from './sharedLogic'
|
||||
|
||||
import styles from './AuthModal.module.scss'
|
||||
|
||||
|
@ -25,28 +25,18 @@ type FormFields = {
|
|||
type ValidationErrors = Partial<Record<keyof FormFields, string>>
|
||||
|
||||
export const LoginForm = () => {
|
||||
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||
const { t } = useLocalize()
|
||||
|
||||
const [submitError, setSubmitError] = createSignal('')
|
||||
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
||||
const [password, setPassword] = createSignal('')
|
||||
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
||||
// TODO: better solution for interactive error messages
|
||||
const [isEmailNotConfirmed, setIsEmailNotConfirmed] = createSignal(false)
|
||||
const [isLinkSent, setIsLinkSent] = createSignal(false)
|
||||
|
||||
const authFormRef: { current: HTMLFormElement } = { current: null }
|
||||
|
||||
const {
|
||||
actions: { showSnackbar },
|
||||
} = useSnackbar()
|
||||
|
||||
const {
|
||||
actions: { signIn },
|
||||
} = useSession()
|
||||
|
||||
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||
|
||||
const [password, setPassword] = createSignal('')
|
||||
const { showSnackbar } = useSnackbar()
|
||||
const { signIn } = useSession()
|
||||
|
||||
const handleEmailInput = (newEmail: string) => {
|
||||
setValidationErrors(({ email: _notNeeded, ...rest }) => rest)
|
||||
|
@ -58,7 +48,7 @@ export const LoginForm = () => {
|
|||
setPassword(newPassword)
|
||||
}
|
||||
|
||||
const handleSendLinkAgainClick = async (event: Event) => {
|
||||
const handleSendLinkAgainClick = (event: Event) => {
|
||||
event.preventDefault()
|
||||
|
||||
setIsLinkSent(true)
|
||||
|
@ -67,7 +57,7 @@ export const LoginForm = () => {
|
|||
changeSearchParams({ mode: 'forgot-password' })
|
||||
// NOTE: temporary solution, needs logic in authorizer
|
||||
/* FIXME:
|
||||
const { actions: { authorizer } } = useSession()
|
||||
const { authorizer } = useSession()
|
||||
const result = await authorizer().verifyEmail({ token })
|
||||
if (!result) setSubmitError('cant sign send link')
|
||||
*/
|
||||
|
@ -82,15 +72,15 @@ export const LoginForm = () => {
|
|||
|
||||
const newValidationErrors: ValidationErrors = {}
|
||||
|
||||
if (!email()) {
|
||||
newValidationErrors.email = t('Please enter email')
|
||||
} else if (!validateEmail(email())) {
|
||||
newValidationErrors.email = t('Invalid email')
|
||||
const validateAndSetError = (field, message) => {
|
||||
if (!field()) {
|
||||
newValidationErrors[field.name] = t(message)
|
||||
}
|
||||
}
|
||||
|
||||
if (!password()) {
|
||||
newValidationErrors.password = t('Please enter password')
|
||||
}
|
||||
validateAndSetError(email, 'Please enter email')
|
||||
validateAndSetError(() => validateEmail(email()), 'Invalid email')
|
||||
validateAndSetError(password, 'Please enter password')
|
||||
|
||||
if (Object.keys(newValidationErrors).length > 0) {
|
||||
setValidationErrors(newValidationErrors)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 { Icon } from '../../../_shared/Icon'
|
||||
|
@ -50,7 +50,7 @@ export const PasswordField = (props: Props) => {
|
|||
on(
|
||||
() => error(),
|
||||
() => {
|
||||
props.errorMessage && props.errorMessage(error())
|
||||
props.errorMessage?.(error())
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { AuthModalSearchParams } from './types'
|
||||
import type { JSX } from 'solid-js'
|
||||
import type { AuthModalSearchParams } from './types'
|
||||
|
||||
import { clsx } from 'clsx'
|
||||
import { Show, createSignal } from 'solid-js'
|
||||
|
@ -13,8 +13,8 @@ import { validateEmail } from '../../../utils/validateEmail'
|
|||
|
||||
import { AuthModalHeader } from './AuthModalHeader'
|
||||
import { PasswordField } from './PasswordField'
|
||||
import { email, setEmail } from './sharedLogic'
|
||||
import { SocialProviders } from './SocialProviders'
|
||||
import { email, setEmail } from './sharedLogic'
|
||||
|
||||
import styles from './AuthModal.module.scss'
|
||||
|
||||
|
@ -34,9 +34,7 @@ export const RegisterForm = () => {
|
|||
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||
const { t } = useLocalize()
|
||||
const { emailChecks } = useEmailChecks()
|
||||
const {
|
||||
actions: { signUp },
|
||||
} = useSession()
|
||||
const { signUp } = useSession()
|
||||
const [submitError, setSubmitError] = createSignal('')
|
||||
const [fullName, setFullName] = createSignal('')
|
||||
const [password, setPassword] = createSignal('')
|
||||
|
@ -67,11 +65,9 @@ export const RegisterForm = () => {
|
|||
}
|
||||
setValidationErrors(({ email: _notNeeded, ...rest }) => rest)
|
||||
setValidationErrors(({ fullName: _notNeeded, ...rest }) => rest)
|
||||
|
||||
setSubmitError('')
|
||||
|
||||
const newValidationErrors: ValidationErrors = {}
|
||||
|
||||
const cleanName = fullName().trim()
|
||||
const cleanEmail = email().trim()
|
||||
|
||||
|
@ -90,9 +86,7 @@ export const RegisterForm = () => {
|
|||
}
|
||||
|
||||
setValidationErrors(newValidationErrors)
|
||||
|
||||
const emailCheckResult = await checkEmail(cleanEmail)
|
||||
|
||||
const isValid = Object.keys(newValidationErrors).length === 0 && !emailCheckResult
|
||||
|
||||
if (!isValid) {
|
||||
|
@ -113,7 +107,7 @@ export const RegisterForm = () => {
|
|||
redirect_uri: window.location.origin,
|
||||
}
|
||||
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) => ({
|
||||
...prev,
|
||||
email: (
|
||||
|
|
|
@ -35,8 +35,10 @@
|
|||
width: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
a, button {
|
||||
border: none !important;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.facebook,
|
||||
|
|
|
@ -10,9 +10,7 @@ export const PROVIDERS = ['facebook', 'google', 'github'] // 'vk' | 'telegram'
|
|||
|
||||
export const SocialProviders = () => {
|
||||
const { t } = useLocalize()
|
||||
const {
|
||||
actions: { oauth },
|
||||
} = useSession()
|
||||
const { oauth } = useSession()
|
||||
|
||||
return (
|
||||
<div class={styles.container}>
|
||||
|
@ -20,9 +18,9 @@ export const SocialProviders = () => {
|
|||
<div class={styles.social}>
|
||||
<For each={PROVIDERS}>
|
||||
{(provider) => (
|
||||
<a href="#" class={styles[provider]} onClick={(_e) => oauth(provider)}>
|
||||
<button class={styles[provider]} onClick={(_e) => oauth(provider)}>
|
||||
<Icon name={provider} />
|
||||
</a>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { AuthModalMode, AuthModalSearchParams } from './types'
|
||||
|
||||
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 { useLocalize } from '../../../context/localize'
|
||||
|
@ -29,7 +29,6 @@ export const AuthModal = () => {
|
|||
const rootRef: { current: HTMLDivElement } = { current: null }
|
||||
const { t } = useLocalize()
|
||||
const { searchParams } = useRouter<AuthModalSearchParams>()
|
||||
|
||||
const { source } = searchParams()
|
||||
|
||||
const mode = createMemo<AuthModalMode>(() => {
|
||||
|
@ -57,7 +56,7 @@ export const AuthModal = () => {
|
|||
classList={{ [styles.hidden]: mode() !== 'register' && mode() !== 'confirm-email' }}
|
||||
>
|
||||
<div>
|
||||
<h4>{t(`Join the global community of authors!`)}</h4>
|
||||
<h4>{t('Join the global community of authors!')}</h4>
|
||||
<p class={styles.authBenefits}>
|
||||
{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',
|
||||
|
|
|
@ -6,11 +6,7 @@ import styles from './ConfirmModal.module.scss'
|
|||
|
||||
export const ConfirmModal = () => {
|
||||
const { t } = useLocalize()
|
||||
|
||||
const {
|
||||
confirmMessage,
|
||||
actions: { resolveConfirm },
|
||||
} = useConfirm()
|
||||
const { confirmMessage, resolveConfirm } = useConfirm()
|
||||
|
||||
return (
|
||||
<div class={styles.confirmModal}>
|
||||
|
|
|
@ -2,18 +2,18 @@ import type { Topic } from '../../../graphql/schema/core.gen'
|
|||
|
||||
import { getPagePath, redirectPage } from '@nanostores/router'
|
||||
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 { useSession } from '../../../context/session'
|
||||
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 { getDescription } from '../../../utils/meta'
|
||||
import { SharePopup, getShareUrl } from '../../Article/SharePopup'
|
||||
import { RANDOM_TOPICS_COUNT } from '../../Views/Home'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { Subscribe } from '../../_shared/Subscribe'
|
||||
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
|
||||
import { RANDOM_TOPICS_COUNT } from '../../Views/Home'
|
||||
import { AuthModal } from '../AuthModal'
|
||||
import { ConfirmModal } from '../ConfirmModal'
|
||||
import { HeaderAuth } from '../HeaderAuth'
|
||||
|
@ -46,12 +46,8 @@ export const Header = (props: Props) => {
|
|||
const { t, lang } = useLocalize()
|
||||
const { modal } = useModalStore()
|
||||
const { page } = useRouter()
|
||||
const {
|
||||
actions: { requireAuthentication },
|
||||
} = useSession()
|
||||
|
||||
const { requireAuthentication } = useSession()
|
||||
const { searchParams } = useRouter<HeaderSearchParams>()
|
||||
|
||||
const [randomTopics, setRandomTopics] = createSignal([])
|
||||
const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false)
|
||||
const [getIsScrolled, setIsScrolled] = createSignal(false)
|
||||
|
@ -62,9 +58,7 @@ export const Header = (props: Props) => {
|
|||
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
|
||||
const [isZineVisible, setIsZineVisible] = createSignal(false)
|
||||
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
|
||||
const toggleFixed = () => {
|
||||
setFixed(!fixed())
|
||||
}
|
||||
const toggleFixed = () => setFixed(!fixed())
|
||||
|
||||
const tag = (topic: Topic) =>
|
||||
/[ЁА-яё]/.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(styles.fixed, fixed() && !modal())
|
||||
|
||||
if (!fixed() && !modal()) {
|
||||
if (!(fixed() || modal())) {
|
||||
mainContent.style.marginTop = ''
|
||||
window.scrollTo(0, windowScrollTop)
|
||||
}
|
||||
|
@ -135,13 +129,13 @@ export const Header = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
let timer
|
||||
let timer: string | number | NodeJS.Timeout
|
||||
|
||||
const clearTimer = () => {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
|
||||
const hideSubnavigation = (event, time = 500) => {
|
||||
const hideSubnavigation = (_event, time = 500) => {
|
||||
timer = setTimeout(() => {
|
||||
toggleSubnavigation(false)
|
||||
}, time)
|
||||
|
@ -264,7 +258,7 @@ export const Header = (props: Props) => {
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
<h4 innerHTML={t('Subscribe us')} />
|
||||
<h4>{t('Subscribe us')}</h4>
|
||||
<ul class="view-switcher">
|
||||
<li class={styles.mainNavigationSocial}>
|
||||
<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-hover" class={clsx(styles.icon, styles.iconHover)} />
|
||||
</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-hover" class={clsx(styles.icon, styles.iconHover)} />
|
||||
</a>
|
||||
<a href="#" class={styles.control} onClick={handleBookmarkButtonClick}>
|
||||
</button>
|
||||
<button class={styles.control} onClick={handleBookmarkButtonClick}>
|
||||
<Icon name="bookmark" class={styles.icon} />
|
||||
<Icon name="bookmark-hover" class={clsx(styles.icon, styles.iconHover)} />
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
|
@ -417,7 +411,7 @@ export const Header = (props: Props) => {
|
|||
<a href="/podcasts">{t('Podcasts')}</a>
|
||||
</li>
|
||||
<li class="item">
|
||||
<a href="">{t('Special Projects')}</a>
|
||||
<a href="/about/projects">{t('Special Projects')}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/topic/interview">#{t('Interview')}</a>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { getPagePath } from '@nanostores/router'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import { router, ROUTES, useRouter } from '../../../stores/router'
|
||||
import { ROUTES, router, useRouter } from '../../../stores/router'
|
||||
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
|
||||
|
||||
import styles from './Header.module.scss'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { getPagePath } from '@nanostores/router'
|
||||
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 { useLocalize } from '../../context/localize'
|
||||
|
@ -8,11 +8,11 @@ import { useNotifications } from '../../context/notifications'
|
|||
import { useSession } from '../../context/session'
|
||||
import { router, useRouter } from '../../stores/router'
|
||||
import { showModal } from '../../stores/ui'
|
||||
import { Userpic } from '../Author/Userpic'
|
||||
import { Button } from '../_shared/Button'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { Popover } from '../_shared/Popover'
|
||||
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
||||
import { Userpic } from '../Author/Userpic'
|
||||
|
||||
import { ProfilePopup } from './ProfilePopup'
|
||||
|
||||
|
@ -33,15 +33,8 @@ export const HeaderAuth = (props: Props) => {
|
|||
const { t } = useLocalize()
|
||||
const { page } = useRouter()
|
||||
const { session, author, isAuthenticated, isSessionLoaded } = useSession()
|
||||
const {
|
||||
unreadNotificationsCount,
|
||||
actions: { showNotificationsPanel },
|
||||
} = useNotifications()
|
||||
|
||||
const {
|
||||
form,
|
||||
actions: { toggleEditorPanel, saveShout, publishShout },
|
||||
} = useEditorContext()
|
||||
const { unreadNotificationsCount, showNotificationsPanel } = useNotifications()
|
||||
const { form, toggleEditorPanel, saveShout, publishShout } = useEditorContext()
|
||||
|
||||
const handleBellIconClick = (event: Event) => {
|
||||
event.preventDefault()
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { JSX } from 'solid-js'
|
|||
|
||||
import { redirectPage } from '@nanostores/router'
|
||||
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 { router } from '../../../stores/router'
|
||||
|
@ -32,7 +32,7 @@ export const Modal = (props: Props) => {
|
|||
const handleHide = () => {
|
||||
if (modal()) {
|
||||
if (allowClose()) {
|
||||
props.onClose && props.onClose()
|
||||
props.onClose?.()
|
||||
} else {
|
||||
redirectPage(router, 'home')
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ export const Modal = (props: Props) => {
|
|||
<div
|
||||
class={clsx(styles.modal, {
|
||||
[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.maxHeight]: props.maxHeight,
|
||||
})}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { ModalType } from '../../../stores/ui'
|
||||
import type { JSX } from 'solid-js/jsx-runtime'
|
||||
import type { ModalType } 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'>
|
||||
|
||||
export const ProfilePopup = (props: ProfilePopupProps) => {
|
||||
const {
|
||||
author,
|
||||
actions: { signOut },
|
||||
} = useSession()
|
||||
|
||||
const { author, signOut } = useSession()
|
||||
const { t } = useLocalize()
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
@mixin searchFilterControl {
|
||||
@mixin search-filter-control {
|
||||
@include font-size(1.4rem);
|
||||
|
||||
height: 4rem;
|
||||
|
||||
padding: 0 2rem;
|
||||
|
||||
background: rgb(64 64 64 / 0.5);
|
||||
background: rgb(64 64 64 / 50%);
|
||||
border-radius: 10rem;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
|
@ -16,7 +14,7 @@
|
|||
}
|
||||
|
||||
&:active {
|
||||
color: rgb(255 255 255 / 0.4);
|
||||
color: rgb(255 255 255 / 40%);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,9 +26,7 @@
|
|||
@include font-size(4.8rem);
|
||||
|
||||
width: 100%;
|
||||
|
||||
padding: 0 0 0.5rem;
|
||||
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid #fff;
|
||||
|
@ -39,14 +35,13 @@
|
|||
outline: none;
|
||||
|
||||
&::placeholder {
|
||||
color: rgb(255 255 255 / 0.32);
|
||||
color: rgb(255 255 255 / 32%);
|
||||
}
|
||||
|
||||
&:not(:placeholder-shown) + .searchButton img {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
&::-moz-selection,
|
||||
&::selection {
|
||||
color: #2638d9;
|
||||
}
|
||||
|
@ -56,7 +51,6 @@
|
|||
position: absolute;
|
||||
right: 0;
|
||||
top: 2rem;
|
||||
|
||||
width: 3.2rem;
|
||||
height: 3.2rem;
|
||||
|
||||
|
@ -67,9 +61,10 @@
|
|||
|
||||
.searchDescription {
|
||||
margin-bottom: 44px;
|
||||
|
||||
@include font-size(1.6rem);
|
||||
|
||||
color: rgb(255 255 255 / 0.64);
|
||||
color: rgb(255 255 255 / 64%);
|
||||
}
|
||||
|
||||
.topicsList {
|
||||
|
@ -77,12 +72,11 @@
|
|||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
margin-top: 9.6rem !important;
|
||||
}
|
||||
|
||||
.topTopic {
|
||||
@include searchFilterControl;
|
||||
@include search-filter-control;
|
||||
}
|
||||
|
||||
.filterSwitcher {
|
||||
|
@ -108,22 +102,19 @@
|
|||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
|
||||
margin: 6.4rem 0;
|
||||
}
|
||||
|
||||
.filterResultsControl {
|
||||
@include searchFilterControl;
|
||||
@include search-filter-control;
|
||||
}
|
||||
|
||||
.searchLoader {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
border: 5px solid #fff;
|
||||
border-bottom-color: transparent;
|
||||
border-radius: 50%;
|
||||
|
||||
animation: rotation 1s linear infinite;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
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 { useLocalize } from '../../../context/localize'
|
||||
import { loadShoutsSearch } from '../../../stores/zine/articles'
|
||||
import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll'
|
||||
import { byScore } from '../../../utils/sortby'
|
||||
import { FEED_PAGE_SIZE } from '../../Views/Feed/Feed'
|
||||
import { Button } from '../../_shared/Button'
|
||||
import { Icon } from '../../_shared/Icon'
|
||||
import { FEED_PAGE_SIZE } from '../../Views/Feed/Feed'
|
||||
|
||||
import { SearchResultItem } from './SearchResultItem'
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ export const Snackbar = () => {
|
|||
<Transition
|
||||
enterClass={styles.enter}
|
||||
exitToClass={styles.exitTo}
|
||||
onExit={(el, done) => setTimeout(() => done(), 300)}
|
||||
onExit={(_el, done) => setTimeout(() => done(), 300)}
|
||||
>
|
||||
<Show when={snackbarMessage()}>
|
||||
<div class={styles.content}>
|
||||
|
|
|
@ -22,7 +22,7 @@ export const Topics = () => {
|
|||
<a href="/podcasts">{t('Podcasts')}</a>
|
||||
</li>
|
||||
<li class={styles.item}>
|
||||
<a href="">{t('Special Projects')}</a>
|
||||
<a href="/about/projects">{t('Special Projects')}</a>
|
||||
</li>
|
||||
<li class={styles.item}>
|
||||
<a href="/topic/interview">#{t('Interview')}</a>
|
||||
|
|
|
@ -5,10 +5,10 @@ import { For, Show } from 'solid-js'
|
|||
import { useLocalize } from '../../../context/localize'
|
||||
import { useNotifications } from '../../../context/notifications'
|
||||
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 { TimeAgo } from '../../_shared/TimeAgo'
|
||||
import { ArticlePageSearchParams } from '../../Article/FullArticle'
|
||||
|
||||
import styles from './NotificationView.module.scss'
|
||||
|
||||
|
@ -25,7 +25,7 @@ const getTitle = (title: string) => {
|
|||
const shoutTitleWords = title.split(' ')
|
||||
|
||||
while (shoutTitle.length <= 30 && i < shoutTitleWords.length) {
|
||||
shoutTitle += shoutTitleWords[i] + ' '
|
||||
shoutTitle += `${shoutTitleWords[i]} `
|
||||
i++
|
||||
}
|
||||
|
||||
|
@ -45,9 +45,7 @@ const reactionsCaption = (threadId: string) =>
|
|||
export const NotificationGroup = (props: NotificationGroupProps) => {
|
||||
const { t, formatTime, formatDate } = useLocalize()
|
||||
const { changeSearchParams } = useRouter<ArticlePageSearchParams>()
|
||||
const {
|
||||
actions: { hideNotificationsPanel, markSeenThread },
|
||||
} = useNotifications()
|
||||
const { hideNotificationsPanel, markSeenThread } = useNotifications()
|
||||
const handleClick = (threadId: string) => {
|
||||
props.onClick()
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 { useLocalize } from '../../context/localize'
|
||||
|
@ -46,7 +46,6 @@ const isEarlier = (date: Date) => {
|
|||
|
||||
export const NotificationsPanel = (props: Props) => {
|
||||
const [isLoading, setIsLoading] = createSignal(false)
|
||||
|
||||
const { isAuthenticated } = useSession()
|
||||
const { t } = useLocalize()
|
||||
const {
|
||||
|
@ -55,7 +54,8 @@ export const NotificationsPanel = (props: Props) => {
|
|||
unreadNotificationsCount,
|
||||
loadedNotificationsCount,
|
||||
totalNotificationsCount,
|
||||
actions: { loadNotificationsGrouped, markSeenAll },
|
||||
loadNotificationsGrouped,
|
||||
markSeenAll,
|
||||
} = useNotifications()
|
||||
const handleHide = () => {
|
||||
props.onClose()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { createFileUploader } from '@solid-primitives/upload'
|
||||
import { clsx } from 'clsx'
|
||||
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 { useConfirm } from '../../context/confirm'
|
||||
|
@ -9,20 +9,20 @@ import { useLocalize } from '../../context/localize'
|
|||
import { useProfileForm } from '../../context/profile'
|
||||
import { useSession } from '../../context/session'
|
||||
import { useSnackbar } from '../../context/snackbar'
|
||||
import { showModal, hideModal } from '../../stores/ui'
|
||||
import { hideModal, showModal } from '../../stores/ui'
|
||||
import { clone } from '../../utils/clone'
|
||||
import { getImageUrl } from '../../utils/getImageUrl'
|
||||
import { handleImageUpload } from '../../utils/handleImageUpload'
|
||||
import { profileSocialLinks } from '../../utils/profileSocialLinks'
|
||||
import { validateUrl } from '../../utils/validateUrl'
|
||||
import { Modal } from '../Nav/Modal'
|
||||
import { ProfileSettingsNavigation } from '../Nav/ProfileSettingsNavigation'
|
||||
import { Button } from '../_shared/Button'
|
||||
import { Icon } from '../_shared/Icon'
|
||||
import { ImageCropper } from '../_shared/ImageCropper'
|
||||
import { Loading } from '../_shared/Loading'
|
||||
import { Popover } from '../_shared/Popover'
|
||||
import { SocialNetworkInput } from '../_shared/SocialNetworkInput'
|
||||
import { Modal } from '../Nav/Modal'
|
||||
import { ProfileSettingsNavigation } from '../Nav/ProfileSettingsNavigation'
|
||||
|
||||
import styles from '../../pages/profile/Settings.module.scss'
|
||||
|
||||
|
@ -31,7 +31,6 @@ const GrowingTextarea = lazy(() => import('../../components/_shared/GrowingTexta
|
|||
|
||||
export const ProfileSettings = () => {
|
||||
const { t } = useLocalize()
|
||||
|
||||
const [prevForm, setPrevForm] = createStore({})
|
||||
const [isFormInitialized, setIsFormInitialized] = createSignal(false)
|
||||
const [social, setSocial] = createSignal([])
|
||||
|
@ -44,21 +43,10 @@ export const ProfileSettings = () => {
|
|||
const [hostname, setHostname] = createSignal<string | null>(null)
|
||||
const [slugError, setSlugError] = createSignal<string>()
|
||||
const [nameError, setNameError] = createSignal<string>()
|
||||
|
||||
const {
|
||||
form,
|
||||
actions: { submit, updateFormField, setForm },
|
||||
} = useProfileForm()
|
||||
|
||||
const {
|
||||
actions: { showSnackbar },
|
||||
} = useSnackbar()
|
||||
const {
|
||||
actions: { loadAuthor },
|
||||
} = useSession()
|
||||
const {
|
||||
actions: { showConfirm },
|
||||
} = useConfirm()
|
||||
const { form, submit, updateFormField, setForm } = useProfileForm()
|
||||
const { showSnackbar } = useSnackbar()
|
||||
const { loadAuthor } = useSession()
|
||||
const { showConfirm } = useConfirm()
|
||||
|
||||
createEffect(() => {
|
||||
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