Merge pull request #396 from Discours/feature/biome

Feature/biome
This commit is contained in:
Tony 2024-02-05 19:47:58 +03:00 committed by GitHub
commit 7c2f8370b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
204 changed files with 1509 additions and 5343 deletions

View File

@ -1,7 +0,0 @@
node_modules
public
*.cjs
dist/
.vercel/
src/graphql/client/*
src/graphql/schema/*

View File

@ -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,
},
},
}

View File

@ -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'

View File

@ -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
View File

@ -17,3 +17,8 @@ stats.html
pnpm-lock.yaml
bun.lockb
.jj
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/plawright-report/

View File

@ -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

View File

@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run pre-push

View File

@ -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"
]
}

View File

@ -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"
}

View File

@ -1,16 +0,0 @@
{
"htmlWhitespaceSensitivity": "ignore",
"semi": false,
"singleQuote": true,
"proseWrap": "always",
"printWidth": 108,
"plugins": [],
"overrides": [
{
"files": "*.ts",
"options": {
"parser": "typescript"
}
}
]
}

View File

@ -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"
}

View File

@ -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,
{

View File

@ -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

View File

@ -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
```

View File

@ -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()

View File

@ -1 +0,0 @@

73
biome.json Normal file
View 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"
}
}
}
}

View File

@ -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>
}

View File

@ -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)

View File

@ -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)

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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
View 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,
// },
})

View File

@ -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'))

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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 {

View File

@ -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(),

View File

@ -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'),

View File

@ -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 = [

View File

@ -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,
})

View File

@ -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()) {

View File

@ -1,5 +1,5 @@
import { JSX, createSignal } from 'solid-js'
import './Tooltip.scss'
import { createSignal, JSX } from 'solid-js'
interface TooltipProps {
children?: JSX.Element

View File

@ -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
}

View File

@ -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(),
})

View File

@ -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}`}

View File

@ -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'

View File

@ -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}
>

View File

@ -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)

View File

@ -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}>

View File

@ -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()

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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())

View File

@ -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')

View File

@ -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 = () => {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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}>
<>

View File

@ -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'

View File

@ -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)
})

View File

@ -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 }]))

View File

@ -1,4 +1,4 @@
import { mergeAttributes, Node } from '@tiptap/core'
import { Node, mergeAttributes } from '@tiptap/core'
declare module '@tiptap/core' {
interface Commands<ReturnType> {

View File

@ -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' {

View File

@ -1,4 +1,4 @@
import { mergeAttributes, Node } from '@tiptap/core'
import { Node, mergeAttributes } from '@tiptap/core'
declare module '@tiptap/core' {
interface Commands<ReturnType> {

View File

@ -41,9 +41,8 @@ export const ToggleTextWrap = Extension.create({
if (changesApplied) {
dispatch(tr)
return true
} else {
return false
}
return false
},
}
},

View File

@ -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 })}
>

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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)
}

View File

@ -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(', '))

View File

@ -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'

View File

@ -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'

View File

@ -187,7 +187,6 @@
font-size: 12px;
line-height: 16px;
margin-top: 0.3em;
color: var(--danger-color);
a {

View File

@ -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)}

View File

@ -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'

View File

@ -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)

View File

@ -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)

View File

@ -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 },
),

View File

@ -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: (

View File

@ -35,8 +35,10 @@
width: auto;
}
a {
a, button {
border: none !important;
outline: none;
box-shadow: none;
}
.facebook,

View File

@ -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>

View File

@ -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',

View File

@ -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}>

View File

@ -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>

View File

@ -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'

View File

@ -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()

View File

@ -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,
})}

View File

@ -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'

View File

@ -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 (

View File

@ -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;
}

View File

@ -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'

View File

@ -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}>

View File

@ -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>

View File

@ -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()

View File

@ -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()

View File

@ -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