Compare commits

..

1 Commits
dev ... main

Author SHA1 Message Date
328acd9ce8 merged 2023-10-18 11:02:52 +03:00
779 changed files with 40308 additions and 42907 deletions

6
.eslintignore Normal file
View File

@ -0,0 +1,6 @@
node_modules
public
*.cjs
src/graphql/*.gen.ts
dist/
.vercel/

87
.eslintrc.js Normal file
View File

@ -0,0 +1,87 @@
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'
// Maybe one day...
// 'plugin:@typescript-eslint/recommended-requiring-type-checking'
],
rules: {
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_'
}
],
'@typescript-eslint/no-non-null-assertion': 'error',
// TODO: Remove any usage and enable
'@typescript-eslint/no-explicit-any': 'off'
}
}
],
env: {
browser: true,
node: true,
mocha: true
},
globals: {},
rules: {
// Solid
'solid/reactivity': 'off', // FIXME
'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': 'off', // FIXME
'unicorn/prefer-top-level-await': 'warn',
'unicorn/consistent-function-scoping': 'warn',
'unicorn/no-array-callback-reference': 'warn',
'unicorn/no-array-method-this-argument': 'warn',
'sonarjs/no-duplicate-string': ['warn', { threshold: 5 }],
// Promise
// 'promise/catch-or-return': 'off', // Should be enabled
'promise/always-return': 'off',
eqeqeq: 'error',
'no-param-reassign': 'error',
'no-nested-ternary': 'error',
'no-shadow': 'error'
},
settings: {
'import/resolver': {
typescript: true,
node: true
}
}
}

View File

@ -1,80 +1,22 @@
name: "deploy"
name: 'deploy'
on: [push]
jobs:
test:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Install Biome
run: npm install --global --save-exact @biomejs/biome
- name: Lint with Biome
run: npx @biomejs/biome ci
- name: Lint styles
run: npx stylelint **/*.{scss,css}
- name: Check types
run: npm run typecheck
- name: Test production build
run: npm run build
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npm run e2e
env:
BASE_URL: ${{ github.event.deployment_status.target_url }}
DEBUG: pw:api
email-templates:
runs-on: ubuntu-latest
name: Update templates on Mailgun
if: github.event_name == 'push' && github.ref == 'refs/heads/feature/email-templates'
continue-on-error: true
steps:
- name: Checkout
- name: Cloning repo
uses: actions/checkout@v2
- name: "Email confirmation template"
uses: gyto/mailgun-template-action@v2
with:
html-file: "./templates/authorizer_email_confirmation.html"
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
mailgun-domain: "discours.io"
mailgun-template: "authorizer_email_confirmation"
fetch-depth: 0
- name: "Password reset template"
uses: gyto/mailgun-template-action@v2
with:
html-file: "./templates/authorizer_password_reset.html"
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
mailgun-domain: "discours.io"
mailgun-template: "authorizer_password_reset"
- name: Get Repo Name
id: repo_name
run: echo "::set-output name=repo::$(echo ${GITHUB_REPOSITORY##*/})"
- name: "First publication notification"
uses: gyto/mailgun-template-action@v2
- name: Push to dokku
uses: dokku/github-action@master
with:
html-file: "./templates/first_publication_notification.html"
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
mailgun-domain: "discours.io"
mailgun-template: "first_publication_notification"
- name: "New comment notification template"
uses: gyto/mailgun-template-action@v2
with:
html-file: "./templates/new_comment_notification.html"
mailgun-api-key: ${{ secrets.MAILGUN_API_KEY }}
mailgun-domain: "discours.io"
mailgun-template: "new_comment_notification"
branch: 'main'
git_remote_url: 'ssh://dokku@staging.discours.io:22/${{ steps.repo_name.outputs.repo }}'
ssh_private_key: ${{ secrets.SSH_PRIVATE_KEY }}

View File

@ -1,58 +1,18 @@
name: "CI and E2E Tests"
name: CI
on:
push:
deployment_status:
types: [success]
on: [push]
jobs:
ci:
if: github.event_name == 'push'
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- name: Install dependencies
run: npm i
- name: Install CI checks
run: npm ci
- name: Check types
run: npm run typecheck
- name: Lint with Biome
run: npx @biomejs/biome check src/.
- name: Lint styles
run: npx stylelint **/*.{scss,css}
- name: Test production build
run: npm run build
e2e_tests:
needs: ci
runs-on: ubuntu-latest
steps:
- name: Debug event info
run: |
echo "Event Name: ${{ github.event_name }}"
echo "Deployment Status: ${{ github.event.deployment_status.state }}"
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Wait for deployment to be live
run: |
echo "Waiting for Vercel deployment to be live..."
until curl -sSf https://testing.discours.io > /dev/null; do
printf '.'
sleep 10
done
- name: Install Playwright and dependencies
run: npm run e2e:install
- name: Run Playwright tests
run: npm run e2e:tests:ci
env:
BASE_URL: https://testing.discours.io
continue-on-error: true
- name: Report test result if failed
if: failure()
run: echo "E2E tests failed. Please review the logs."
- name: Install deps
run: npm ci
- name: Check
run: npm run check

19
.gitignore vendored
View File

@ -1,9 +1,8 @@
.devcontainer
.pnpm-store
dist/
node_modules/
npm-debug.log*
pnpm-debug.log*
.vscode
.env
.env.production
.DS_Store
@ -12,22 +11,6 @@ pnpm-debug.log*
.eslint/.eslintcache
public/upload/*
src/graphql/introspec.gen.ts
src/graphql/schema/*.gen.ts
stats.html
*.scss.d.ts
pnpm-lock.yaml
bun.lockb
.jj
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/plawright-report/
target
.github/dependabot.yml
.output
.vinxi
*.pem
edge.*
.vscode/settings.json
storybook-static

21
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,21 @@
---
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

4
.husky/pre-commit Executable file
View File

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

4
.husky/pre-push Executable file
View File

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

4
.lintstagedrc Normal file
View File

@ -0,0 +1,4 @@
{
"*.{js,mjs,ts,tsx,json,scss,css,html}": "prettier --write",
"package.json": "sort-package-json"
}

6
.lintstagedrc.bak Normal file
View File

@ -0,0 +1,6 @@
{
"*.{js,ts,tsx,json,scss,css,html}": "prettier --write",
"package.json": "sort-package-json",
"*.{scss,css}": "stylelint",
"*.{ts,tsx,js}": "eslint --fix"
}

17
.prettierrc.json Normal file
View File

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

View File

@ -1,49 +0,0 @@
import type { FrameworkOptions, StorybookConfig } from 'storybook-solidjs-vite'
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-a11y',
'@storybook/addon-themes',
'storybook-addon-sass-postcss'
],
framework: {
name: 'storybook-solidjs-vite',
options: {
builder: {
viteConfigPath: './vite.config.ts'
}
} as FrameworkOptions
},
docs: {
autodocs: 'tag'
},
viteFinal: (config) => {
if (config.build) {
config.build.sourcemap = true
config.build.minify = process.env.NODE_ENV === 'production'
}
if (config.css) {
config.css.preprocessorOptions = {
scss: {
silenceDeprecations: ['mixed-decls'],
additionalData: '@import "~/styles/imports";\n',
includePaths: ['./public', './src/styles', './node_modules']
}
}
}
return config
},
previewHead: (head) => `
${head}
<style>
body {
transition: none !important;
}
</style>
`
}
export default config

View File

@ -1,34 +0,0 @@
import { withThemeByClassName } from '@storybook/addon-themes'
import '../src/styles/app.scss'
const preview = {
parameters: {
themes: {
default: 'light',
list: [
{ name: 'light', class: '', color: '#f8fafc' },
{ name: 'dark', class: 'dark', color: '#0f172a' }
]
},
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/
}
}
}
}
export default preview
export const decorators = [
withThemeByClassName({
themes: {
light: '',
dark: 'dark'
},
defaultTheme: 'light',
parentSelector: 'body'
})
]

View File

@ -1,23 +0,0 @@
import type { Page } from '@playwright/test'
import type { TestRunnerConfig } from '@storybook/test-runner'
import { checkA11y, injectAxe } from 'axe-playwright'
/*
* See https://storybook.js.org/docs/react/writing-tests/test-runner#test-hook-api-experimental
* to learn more about the test-runner hooks API.
*/
const a11yConfig = {
async preRender(page: Page) {
await injectAxe(page)
},
async postRender(page: Page) {
await checkA11y(page, '#storybook-root', {
detailedReport: true,
detailedReportOptions: {
html: true
}
})
}
} as TestRunnerConfig
module.exports = a11yConfig

View File

@ -1,6 +1,2 @@
node_modules
.vercel/
dist/
storybook-static
.output
.vinxi
.vercel

37
.stylelintrc Normal file
View File

@ -0,0 +1,37 @@
{
"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"]
}
],
"property-no-vendor-prefix": [
true,
{
"ignoreProperties": ["box-decoration-break"]
}
]
},
"defaultSeverity": "warning"
}

31
.stylelintrc.bak Normal file
View File

@ -0,0 +1,31 @@
{
"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,73 +0,0 @@
{
"defaultSeverity": "warning",
"extends": ["stylelint-config-standard-scss", "stylelint-config-recommended"],
"plugins": ["stylelint-order", "stylelint-scss"],
"rules": {
"annotation-no-unknown": [
true,
{
"ignoreAnnotations": ["default"]
}
],
"at-rule-no-unknown": null,
"declaration-block-no-redundant-longhand-properties": null,
"font-family-no-missing-generic-family-keyword": null,
"function-no-unknown": [
true,
{
"ignoreFunctions": ["divide", "transparentize"]
}
],
"function-url-quotes": null,
"keyframes-name-pattern": null,
"no-descending-specificity": null,
"order/order": [
{
"type": "at-rule",
"name": "include"
},
"custom-properties",
"declarations",
"rules"
],
"property-no-vendor-prefix": [
true,
{
"ignoreProperties": ["box-decoration-break"]
}
],
"scss/at-function-pattern": null,
"scss/at-mixin-pattern": null,
"scss/dollar-variable-colon-space-after": "always-single-line",
"scss/dollar-variable-colon-space-before": "never",
"scss/dollar-variable-pattern": [
"^[a-z][a-zA-Z]+$",
{
"ignore": "global"
}
],
"scss/double-slash-comment-empty-line-before": [
"always",
{
"except": ["first-nested"],
"ignore": ["between-comments", "stylelint-commands"]
}
],
"scss/double-slash-comment-whitespace-inside": "always",
"scss/function-no-unknown": null,
"scss/no-duplicate-dollar-variables": null,
"scss/no-duplicate-mixins": null,
"scss/no-global-function-names": null,
"scss/operator-no-newline-after": null,
"scss/operator-no-newline-before": null,
"scss/operator-no-unspaced": null,
"scss/percent-placeholder-pattern": null,
"selector-class-pattern": null,
"selector-pseudo-class-no-unknown": [
true,
{
"ignorePseudoClasses": ["global", "export"]
}
]
}
}

View File

@ -1,3 +0,0 @@
{
"recommendations": ["biomejs.biome", "stylelint.vscode-stylelint", "wayou.vscode-todo-highlight"]
}

View File

@ -1,5 +0,0 @@
{
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "always"
}
}

179
CHANGELOG.txt Normal file
View File

@ -0,0 +1,179 @@
[0.8.0]
[+] i18next for ,solid
[-] i18n
[+] custom snackbar
[+] editor lazy load
[+] hygen
[-] astro removed
[+] vite ssr plugin
[0.7.1]
[+] reactions CUDL
[+] api/upload with storj
[+] api/feedback
[+] bumped astro pkgs versions
[+] graphql ws subs
[0.7.0]
[+] inbox: context provider, chats
[+] comments: show
[+] session: context provider
[+] views tracker: counting for shouts
[0.6.1]
[+] auth ver. 0.9
[+] load-by interfaces for shouts, authors and messages
[+] inbox logix and markup
[-] old views counting
[0.6.0]
[+] hybrid routing ssr/spa
[+] 'expo' pages
[-] layout term usage with an exception
[-] less nanostores
[+] inbox
[+] css modules
[+] draft editor
[+] solid-driven storages
[0.5.1]
[+] nanostores-base global store
[-] Root.tsx components
[+] astro/solid basic hydration
[0.5.0]
[-] removed solid-primitives/i18n
[+] added custom dummy utils/intl
[-] solid-app-router
[+] astro build and routing
[-] solid-top-loading-bar
[+] lint, prettier
[-] context providers, _cache
[+] ssr PoW
[0.4.1]
[-] markdown-it
[+] remark, rehype, gfm
[+] api fixes
[0.4.0]
[+] upload, feedback, newsletter serverless
[-] ratings
[-] comments
[-] proposals
[+] universal reaction entity
[+] staged preload
[0.3.1]
[+] promisisified stores
[+] prerender based on mdx
[+] hybryd zine state manager
[0.3.0]
[+] markup is simpler
[+] really use mdx
[+] really use i18n
[+] refactored queries
[+] final routing
[0.2.1]
[+] custom store
[+] playwright
[+] mdx
[0.2.0]
[-] sveltekit
[-] graphql-request
[+] migrated to solid
[+] urql
[+] graphql caching results
[0.1.0]
[+] husky, lint-staged
[+] components refactoring
[+] 'static' pages fixes
[+] ShoutFeed's reusable components
[+] render order revised
[0.0.9]
[+] lots of visual changes for demo
[+] cookie-based subscriptions
[+] prerender fix
[+] refactor queries
[+] caching topics with localStorage
[+] added some 'static' routes
[0.0.8]
[+] isolated editor codebase
[+] sveo
[+] SSG first
[+] svelte-kit caching fixes
[+] isolated MD component
[-] code cleanup
[-] /auth route
[-] top nav changes
[0.0.7]
[+] nav refactoring /[what] /@[who]
[+] /reset/[code], /reset/password
[+] modal auth dialog
[+] Topic.pic field
[+] internal svelte prerender
[+] GET_SHOUTS, TOP_SHOUTS_BY_RATING, GET_TOPICS, GET_COMMUNITIES via caching json trick
[~] User.username -> User.name
[0.0.6]
[-] organization, org_id
[+] community entity
[+] mainpage markup
[+] topics filter navigation
[+] monor schema fixes
[-] gitea.js api
[-] postcss with plugins
[-] bootstrap
[+] windicss
[+] async sveltekit-styled queries
[+] login basic markup
[0.0.5]
[+] migrate to sveltekit
[-] removed apollo due bug
[-] removed custom prerender code
[+] stylelint enabled
[+] precompiler windows support
[+] precompiler separated
[0.0.4]
[+] precompiler generated static indexes
[-] puppeteer switched off
[+] topic entity added
[-] i18n switched off
[+] own signaling server connected
[-] store-based routing removed
[+] reset password page
[+] login/register form
[+] social auth fb, ggl, vk
[0.0.3]
[~] prerender with puppeteer
[+] precompiled data.json
[~] international content support
[+] auth graphql client
[-] removed ws yjs-server
[-] pathfinder replaced
[+] mdsvex support
[0.0.2]
[+] apollo client with codegen
[+] ci basics
[+] code organized
[0.0.1]
[+] 3rd party deps: tiptap, apollo,
[+] boiilerplate with esbuild
[+] simple structure

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2024 Discours
Copyright (c) 2021-2023 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

@ -1,57 +0,0 @@
## Development setup recommendations
### How to start
Use `bun i`, `npm i`, `pnpm i` or `yarn` to install packages.
### Config of variables
- Use `.env` file to setup your own development environment
- Env vars with prefix `PUBLIC_` are widely used in `/src/utils/config.ts`
### Useful commands
run checks, fix styles, imports, formatting and autofixable linting errors:
```
bun run typecheck
bun run fix
```
## End-to-End (E2E) Tests
This directory contains end-to-end tests. These tests are written using [Playwright](https://playwright.dev/)
### Structure
- `/tests/*`: This directory contains the test files.
- `/playwright.config.ts`: This is the configuration file for Playwright.
### Getting Started
Follow these steps:
1. **Install dependencies**: Run `npm run e2e:install` to install the necessary dependencies for running the tests.
2. **Run the tests**: After using `npm run e2e:tests`.
### Additional Information
If workers is no needed use:
- `npx playwright test --project=webkit --workers 4`
For more information on how to write tests using Playwright - [Playwright documentation](https://playwright.dev/docs/intro).
### 🚀 Tests in CI Mode
Tests are executed within a GitHub workflow. We organize our tests into two main directories:
- `tests`: Contains tests that do not require authentication.
- `tests-with-auth`: Houses tests that interact with authenticated parts of the application.
🔧 **Configuration:**
Playwright is configured to utilize the `BASE_URL` environment variable. Ensure this is properly set in your CI configuration to point to the correct environment.
📝 **Note:**
After pages have been adjusted to work with authentication, all tests should be moved to the `tests` directory to streamline the testing process.

View File

@ -1,57 +1,32 @@
[English](README.en.md)
## Рекомендации по настройке разработки
### Как начать
Используйте `bun i`, `npm i`, `pnpm i` или `yarn`, чтобы установить пакеты.
### Настройка переменных
- Используйте файл `.env` для настройки переменных собственной среды разработки.
- Переменные окружения с префиксом `PUBLIC_` широко используются в `/src/utils/config.ts`.
### Полезные команды
Запуск проверки соответствия типов и автоматически исправить ошибки стилей, порядок импорта, форматирование:
## How to start
```
bun run typecheck
bun run fix
npm install
npm start
```
with different backends
```
npm run start:local
npm run start:production
npm run start:staging
```
## End-to-End (E2E) тесты
## Useful commands
run checks
```
npm run check
```
type checking with watch
```
npm run typecheck:watch
```
## Code generation
End-to-end тесты написаны с использованием [Playwright](https://playwright.dev/).
generate new SolidJS component:
```
npx hygen component new NewComponentName
```
### Структура
- `/tests/*`: содержит файлы тестов
- `/playwright.config.ts`: конфиг для Playwright
### Начало работы
Следуйте этим шагам:
1. **Установите зависимости**: Запустите `npm run e2e:install`, чтобы установить необходимые зависимости для выполнения тестов.
2. **Запустите тесты**: После установки зависимостей используйте `npm run e2e:tests`.
### Дополнительная информация
Для параллельного исполнения:
- `npx playwright test --project=webkit --workers 4`
Для получения дополнительной информации о написании тестов с использованием Playwright - [Документация Playwright](https://playwright.dev/docs/intro).
### 🚀 Тесты в режиме CI
Тесты выполняются в рамках GitHub workflow из папки `tests`
🔧 **Конфигурация:**
Playwright настроен на использование переменной окружения `BASE_URL`. Убедитесь, что она правильно установлена в вашей конфигурации CI для указания на правильную среду.
📝 **Примечание:**
После того как страницы были настроены для работы с аутентификацией, все тесты должны быть перемещены в директорию `tests` для упрощения процесса тестирования.
generate new SolidJS context:
```
npx hygen context new NewContextName
```

View File

@ -0,0 +1,21 @@
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

@ -0,0 +1,18 @@
---
to: src/components/<%= h.changeCase.pascal(name) %>/<%= h.changeCase.pascal(name) %>.tsx
---
import { clsx } from 'clsx'
import styles from './<%= h.changeCase.pascal(name) %>.module.scss'
type Props = {
class?: string
}
export const <%= h.changeCase.pascal(name) %> = (props: Props) => {
return (
<div class={clsx(styles.<%= h.changeCase.pascal(name) %>, props.class)}>
<%= h.changeCase.pascal(name) %>
</div>
)
}

View File

@ -0,0 +1,4 @@
---
to: src/components/<%= h.changeCase.pascal(name) %>/index.ts
---
export { <%= h.changeCase.pascal(name) %> } from './<%= h.changeCase.pascal(name) %>'

View File

@ -0,0 +1,7 @@
---
to: src/components/<%= h.changeCase.pascal(name) %>/<%= h.changeCase.pascal(name) %>.module.scss
---
.<%= h.changeCase.pascal(name) %> {
display: block;
}

View File

@ -0,0 +1,27 @@
---
to: src/context/<%= h.changeCase.camel(name) %>.tsx
---
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>()
export function use<%= h.changeCase.pascal(name) %>() {
return useContext(<%= h.changeCase.pascal(name) %>Context)
}
export const <%= h.changeCase.pascal(name) %>Provider = (props: { children: JSX.Element }) => {
const 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

@ -0,0 +1,5 @@
---
message: |
hygen {bold generator new} --name [NAME] --action [ACTION]
hygen {bold generator with-prompt} --name [NAME] --action [ACTION]
---

View File

@ -0,0 +1,18 @@
---
to: _templates/<%= name %>/<%= action || 'new' %>/hello.ejs.t
---
---
to: app/hello.js
---
const hello = ```
Hello!
This is your first hygen template.
Learn what it can do here:
https://github.com/jondot/hygen
```
console.log(hello)

View File

@ -0,0 +1,18 @@
---
to: _templates/<%= name %>/<%= action || 'new' %>/hello.ejs.t
---
---
to: app/hello.js
---
const hello = ```
Hello!
This is your first prompt based hygen template.
Learn what it can do here:
https://github.com/jondot/hygen
```
console.log(hello)

View File

@ -0,0 +1,14 @@
---
to: _templates/<%= name %>/<%= action || 'new' %>/prompt.js
---
// see types of prompts:
// https://github.com/enquirer/enquirer/tree/master/examples
//
module.exports = [
{
type: 'input',
name: 'message',
message: "What's your message?"
}
]

View File

@ -0,0 +1,4 @@
---
setup: <%= name %>
force: true # this is because mostly, people init into existing folders is safe
---

View File

@ -1,8 +1,10 @@
import FormData from 'form-data'
import Mailgun from 'mailgun.js'
const formData = require('form-data')
const Mailgun = require('mailgun.js')
const mailgun = new Mailgun(FormData)
const mg = mailgun.client({ username: 'discoursio', key: process.env.MAILGUN_API_KEY })
const mailgun = new Mailgun(formData)
const { MAILGUN_API_KEY, MAILGUN_DOMAIN } = process.env
const mg = mailgun.client({ username: 'discoursio', key: MAILGUN_API_KEY })
export default async function handler(req, res) {
const { contact, subject, message } = req.body
@ -17,7 +19,7 @@ export default async function handler(req, res) {
}
try {
const response = await mg.messages.create('discours.io', data)
const response = await mg.messages.create(MAILGUN_DOMAIN, data)
console.log('Email sent successfully!', response)
res.status(200).json({ result: 'great success' })
} catch (error) {

24
api/image.mjs Normal file
View File

@ -0,0 +1,24 @@
import fetch from 'node-fetch'
export default async function handler(req, res) {
const imageUrl = req.query.url
if (!imageUrl) {
return res.status(400).send('Missing URL parameter')
}
try {
const imageRes = await fetch(imageUrl)
if (!imageRes.ok) {
return res.status(404).send('Image not found')
}
res.setHeader('Content-Type', imageRes.headers.get('content-type'))
imageRes.body.pipe(res)
} catch (err) {
console.error(err)
return res.status(404).send('Error')
}
}

View File

@ -1,8 +1,10 @@
import FormData from 'form-data'
import Mailgun from 'mailgun.js'
const formData = require('form-data')
const Mailgun = require('mailgun.js')
const mailgun = new Mailgun(FormData)
const mg = mailgun.client({ username: 'discoursio', key: process.env.MAILGUN_API_KEY })
const mailgun = new Mailgun(formData)
const { MAILGUN_API_KEY } = process.env
const mg = mailgun.client({ username: 'discoursio', key: MAILGUN_API_KEY })
export default async (req, res) => {
const { email } = req.body

27
api/ssr.mjs Normal file
View File

@ -0,0 +1,27 @@
import { renderPage } from 'vite-plugin-ssr/server'
export default async function handler(req, res) {
const { url, cookies } = req
const pageContext = await renderPage({ urlOriginal: url, cookies })
const { httpResponse, errorWhileRendering } = pageContext
if (errorWhileRendering) {
console.error(errorWhileRendering)
res.statusCode = 500
res.end()
return
}
if (!httpResponse) {
res.statusCode = 200
res.end()
return
}
const { body, statusCode, contentType } = httpResponse
res.statusCode = statusCode
res.setHeader('Content-Type', contentType)
res.end(body)
}

View File

@ -1,23 +0,0 @@
import { SolidStartInlineConfig, defineConfig } from '@solidjs/start/config'
import viteConfig, { isDev } from './vite.config'
const isVercel = Boolean(process.env.VERCEL)
const isNetlify = Boolean(process.env.NETLIFY)
const isBun = Boolean(process.env.BUN)
const preset = isNetlify ? 'netlify' : isVercel ? 'vercel_edge' : isBun ? 'bun' : 'node'
console.info(`[app.config] solid-start preset {> ${preset} <}`)
export default defineConfig({
nitro: {
timing: true
},
ssr: true,
server: {
preset,
port: 3000,
https: true
},
devOverlay: isDev,
vite: viteConfig
} as SolidStartInlineConfig)

View File

@ -1,86 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
"files": {
"include": ["*.tsx", "*.ts", "*.js", "*.json"],
"ignore": ["./dist", "./node_modules", ".husky", "docs", "gen", "*.gen.ts", "*.d.ts"]
},
"vcs": {
"defaultBranch": "dev",
"useIgnoreFile": true,
"enabled": true,
"clientKind": "git"
},
"organizeImports": {
"enabled": true,
"ignore": ["./gen"]
},
"formatter": {
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 108,
"ignore": ["./src/graphql/schema", "./gen"]
},
"javascript": {
"formatter": {
"semicolons": "asNeeded",
"quoteStyle": "single",
"enabled": true,
"jsxQuoteStyle": "double",
"arrowParentheses": "always",
"trailingCommas": "none"
}
},
"linter": {
"ignore": ["*.scss", "*.md", ".DS_Store", "*.svg", "*.d.ts"],
"enabled": true,
"rules": {
"all": true,
"complexity": {
"noForEach": "off",
"useOptionalChain": "warn",
"useLiteralKeys": "off",
"noExcessiveCognitiveComplexity": "off"
},
"correctness": {
"useHookAtTopLevel": "off",
"useImportExtensions": "off",
"noUndeclaredDependencies": "off"
},
"a11y": {
"useHeadingContent": "off",
"useKeyWithClickEvents": "off",
"useKeyWithMouseEvents": "off",
"useAnchorContent": "off",
"useValidAnchor": "off",
"useMediaCaption": "off",
"useAltText": "off",
"useButtonType": "off",
"noRedundantAlt": "off",
"noSvgWithoutTitle": "off",
"noLabelWithoutControl": "off"
},
"nursery": {
"useImportRestrictions": "off"
},
"performance": {
"noBarrelFile": "off"
},
"style": {
"noNonNullAssertion": "off",
"noNamespaceImport": "warn",
"useBlockStatements": "off",
"noImplicitBoolean": "off",
"useNamingConvention": "off",
"useImportType": "off",
"noDefaultExport": "off",
"useFilenamingConvention": "off",
"useExplicitLengthCheck": "off"
},
"suspicious": {
"noConsole": "off",
"noConsoleLog": "off",
"noAssignInExpressions": "off"
}
}
}
}

View File

@ -1,27 +1,21 @@
overwrite: true
schema: 'http://127.0.0.1:8080'
#schema: 'https://v2.discours.io'
generates:
# Generate types for chat
src/graphql/schema/chat.gen.ts:
schema: 'https://chat.discours.io'
src/graphql/introspec.gen.ts:
plugins:
- urql-introspection
config:
useTypeImports: true
includeScalars: true
includeEnums: true
src/graphql/types.gen.ts:
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-urql'
config:
skipTypename: true
useTypeImports: true
outputPath: './src/graphql/types/chat.gen.ts'
# namingConvention: change-case#CamelCase # for generated types
# Generate types for core
src/graphql/schema/core.gen.ts:
schema: 'https://core.discours.io'
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-urql'
config:
skipTypename: true
useTypeImports: true
outputPath: './src/graphql/types/core.gen.ts'
# namingConvention: change-case#CamelCase # for generated types
hooks:
afterAllFileWrite:
- prettier --ignore-path .gitignore --write --plugin-search-dir=. src/graphql/types.gen.ts

66
docs/article.puml Normal file
View File

@ -0,0 +1,66 @@
@startuml
actor User
participant Browser
participant Vercel
participant article.page.server.ts
participant Solid
participant Store
User -> Browser: discours.io
activate Browser
Browser -> Vercel: GET <slug>
activate Vercel
Vercel -> article.page.server.ts: render
activate article.page.server.ts
article.page.server.ts -> apiClient: getArticle({ slug })
activate apiClient
apiClient -> DB: query: articleBySlug
activate DB
DB --> apiClient: response
deactivate DB
apiClient --> article.page.server.ts: article data
deactivate apiClient
article.page.server.ts -> Solid: render <ArticlePage article={article} />
activate Solid
Solid -> Store: useCurrentArticleStore(article)
activate Store
Store -> Store: create store with initial data (server)
Store --> Solid: currentArticle
deactivate Store
Solid -> Solid: render component
Solid --> article.page.server.ts: rendered component
deactivate Solid
article.page.server.ts --> Vercel: rendered page
Vercel -> Vercel: save rendered page to CDN
deactivate article.page.server.ts
Vercel --> Browser: rendered page
deactivate Vercel
Browser --> User: rendered page
deactivate Browser
Browser -> Browser: load client scripts
Browser -> Solid: render <ArticlePage article={article} />
Solid -> Store: useCurrentArticleStore(article)
activate Store
Store -> Store: create store with initial data (client)
Store --> Solid: currentArticle
deactivate Store
Solid -> Solid: render component (no changes)
Solid -> Solid: onMount
Solid -> Store: loadArticleComments
activate Store
Store -> apiClient: getArticleComments
activate apiClient
apiClient -> DB: query: getReactions
activate DB
DB --> apiClient: response
deactivate DB
apiClient --> Store: comments data
deactivate apiClient
Store -> Store: update store
Store --> Solid: store updated
deactivate Store
Solid -> Solid: render comments
Solid --> Browser: rendered comments
Browser --> User: comments
@enduml

40
docs/i18n.puml Normal file
View File

@ -0,0 +1,40 @@
@startuml
actor User
participant Browser
participant Server
User -> Browser: discours.io
activate Browser
Browser -> Server: GET\nquery { lng }\ncookies { lng }
opt lng in query
Server -> Server: lng = lng from query
else no lng in query
opt lng in cookies
Server -> Server: lng = lng from cookies
else no lng in cookies
Server -> Server: lng = 'ru'
end opt
end opt
note right
_dafault.page.server.ts render
end note
opt i18next is not initialized
Server -> Server: initialize i18next with lng
else i18next not initialized
Server -> Server: change i18next language to lng
end opt
note right
all resources loaded synchronously
end note
Server --> Browser: pageContext { lng }
Browser -> Browser: init client side i18next with http backend
activate Browser
Browser -> Server: get translations for current language
Server --> Browser: translations JSON
deactivate Browser
Browser -> Browser: render page
Browser --> User: rendered page
deactivate Browser
@enduml

24
docs/routing.puml Normal file
View File

@ -0,0 +1,24 @@
@startuml
actor User
participant Browser
participant Server
User -> Browser: discours.io
activate Browser
Browser -> Server: GET
activate Server
Server -> Server: resolve route
note right
based on routes from
*.page.route.ts files
end note
Server -> Server: some.page.server.ts onBeforeRender
Server -> Server: _default.page.server.tsx render
Server --> Browser: pageContent
deactivate Server
Browser -> Browser: _default.page.client.tsx render(pageContext)
Browser --> User: rendered page
deactivate Browser
@enduml

38178
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,151 +1,165 @@
{
"name": "discoursio-webapp",
"version": "0.8.0",
"private": true,
"version": "0.9.6",
"type": "module",
"license": "MIT",
"scripts": {
"dev": "vinxi dev",
"build": "vinxi build",
"start": "vinxi start",
"build": "vite build",
"check": "npm run lint && npm run typecheck",
"codegen": "graphql-codegen",
"e2e": "E2E=1 npm run e2e:tests",
"e2e:tests": "npx playwright test --project=webkit",
"e2e:tests:ci": "CI=true npx playwright test --project=webkit",
"e2e:install": "npx playwright install webkit && npx playwright install-deps ",
"fix": "npx @biomejs/biome check . --fix && stylelint **/*.{scss,css} --fix",
"format": "npx @biomejs/biome format src/. --write",
"postinstall": "npm run codegen && npx patch-package",
"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",
"lint": "npm run lint:code && npm run lint:styles",
"lint:code": "eslint .",
"lint:code:fix": "eslint . --fix",
"lint:styles": "stylelint **/*.{scss,css}",
"lint:styles:fix": "stylelint **/*.{scss,css} --fix",
"pre-commit": "lint-staged",
"pre-push": "",
"pre-push-old": "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",
"storybook": "storybook dev -p 6006",
"storybook:test": "test-storybook",
"build-storybook": "storybook build"
"typecheck:watch": "tsc --noEmit --watch"
},
"dependencies": {
"form-data": "4.0.0",
"i18next": "22.4.15",
"i18next-icu": "2.3.0",
"intl-messageformat": "10.5.3",
"just-throttle": "4.2.0",
"mailgun.js": "8.2.1",
"node-fetch": "3.3.1"
},
"devDependencies": {
"@authorizerdev/authorizer-js": "^2.0.3",
"@biomejs/biome": "^1.9.3",
"@graphql-codegen/cli": "^5.0.2",
"@graphql-codegen/typescript": "^4.0.9",
"@graphql-codegen/typescript-operations": "^4.2.3",
"@graphql-codegen/typescript-urql": "^4.0.0",
"@hocuspocus/provider": "^2.13.6",
"@playwright/test": "^1.47.2",
"@popperjs/core": "^2.11.8",
"@solid-primitives/media": "^2.2.9",
"@solid-primitives/memo": "^1.3.9",
"@solid-primitives/pagination": "^0.3.0",
"@solid-primitives/script-loader": "^2.2.0",
"@solid-primitives/share": "^2.0.6",
"@solid-primitives/storage": "^4.2.1",
"@solid-primitives/upload": "^0.0.117",
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.14.7",
"@solidjs/start": "^1.0.8",
"@storybook/addon-a11y": "^8.3.4",
"@storybook/addon-actions": "^8.3.4",
"@storybook/addon-controls": "^8.3.4",
"@storybook/addon-essentials": "^8.3.4",
"@storybook/addon-interactions": "^8.3.4",
"@storybook/addon-links": "^8.3.4",
"@storybook/addon-themes": "^8.3.4",
"@storybook/addon-viewport": "^8.3.4",
"@storybook/builder-vite": "^8.3.4",
"@storybook/docs-tools": "^8.3.4",
"@storybook/test": "^8.3.4",
"@storybook/test-runner": "^0.19.1",
"@tiptap/core": "^2.8.0",
"@tiptap/extension-blockquote": "^2.8.0",
"@tiptap/extension-bold": "^2.8.0",
"@tiptap/extension-bubble-menu": "^2.8.0",
"@tiptap/extension-bullet-list": "^2.8.0",
"@tiptap/extension-character-count": "^2.8.0",
"@tiptap/extension-collaboration": "^2.8.0",
"@tiptap/extension-collaboration-cursor": "^2.8.0",
"@tiptap/extension-document": "^2.8.0",
"@tiptap/extension-dropcursor": "^2.8.0",
"@tiptap/extension-floating-menu": "^2.8.0",
"@tiptap/extension-focus": "^2.8.0",
"@tiptap/extension-gapcursor": "^2.8.0",
"@tiptap/extension-hard-break": "^2.8.0",
"@tiptap/extension-heading": "^2.8.0",
"@tiptap/extension-highlight": "^2.8.0",
"@tiptap/extension-history": "^2.8.0",
"@tiptap/extension-horizontal-rule": "^2.8.0",
"@tiptap/extension-image": "^2.8.0",
"@tiptap/extension-italic": "^2.8.0",
"@tiptap/extension-link": "^2.8.0",
"@tiptap/extension-list-item": "^2.8.0",
"@tiptap/extension-ordered-list": "^2.8.0",
"@tiptap/extension-paragraph": "^2.8.0",
"@tiptap/extension-placeholder": "^2.8.0",
"@tiptap/extension-strike": "^2.8.0",
"@tiptap/extension-text": "^2.8.0",
"@tiptap/extension-underline": "^2.8.0",
"@tiptap/extension-youtube": "^2.8.0",
"@tiptap/starter-kit": "^2.8.0",
"@types/cookie": "^0.6.0",
"@types/cookie-signature": "^1.1.2",
"@types/node": "^22.7.4",
"@types/throttle-debounce": "^5.0.2",
"@urql/core": "^5.0.6",
"axe-playwright": "^2.0.3",
"bootstrap": "^5.3.3",
"clsx": "^2.1.1",
"cookie": "^0.6.0",
"cookie-signature": "^1.2.1",
"cropperjs": "^1.6.2",
"extended-eventsource": "^1.6.4",
"fast-deep-equal": "^3.1.3",
"graphql": "^16.9.0",
"i18next": "^23.15.1",
"i18next-http-backend": "^2.6.1",
"i18next-icu": "^2.3.0",
"intl-messageformat": "^10.5.14",
"javascript-time-ago": "^2.5.11",
"patch-package": "^8.0.0",
"prosemirror-history": "^1.4.1",
"prosemirror-trailing-node": "^2.0.9",
"prosemirror-view": "^1.34.3",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "1.77.6",
"solid-js": "^1.9.1",
"solid-popper": "^0.3.0",
"solid-tiptap": "0.7.0",
"solid-transition-group": "^0.2.3",
"storybook": "^8.3.4",
"storybook-addon-sass-postcss": "^0.3.2",
"storybook-solidjs": "^1.0.0-beta.2",
"storybook-solidjs-vite": "^1.0.0-beta.2",
"stylelint": "^16.9.0",
"stylelint-config-recommended": "^14.0.1",
"stylelint-config-standard-scss": "^13.1.0",
"stylelint-order": "^6.0.4",
"stylelint-scss": "^6.7.0",
"swiper": "^11.1.14",
"throttle-debounce": "^5.0.2",
"tslib": "^2.7.0",
"typescript": "^5.6.2",
"typograf": "^7.4.1",
"uniqolor": "^1.1.1",
"vinxi": "^0.4.3",
"vite-plugin-mkcert": "^1.17.6",
"vite-plugin-node-polyfills": "^0.22.0",
"vite-plugin-sass-dts": "^1.3.29",
"y-prosemirror": "1.2.12",
"yjs": "13.6.19"
"@babel/core": "7.21.8",
"@graphql-codegen/cli": "3.2.2",
"@graphql-codegen/typescript": "3.0.4",
"@graphql-codegen/typescript-operations": "3.0.4",
"@graphql-codegen/typescript-urql": "3.7.3",
"@graphql-codegen/urql-introspection": "2.2.1",
"@graphql-tools/url-loader": "7.17.18",
"@graphql-typed-document-node/core": "3.2.0",
"@hocuspocus/provider": "2.0.6",
"@nanostores/router": "0.8.3",
"@nanostores/solid": "0.3.2",
"@popperjs/core": "2.11.8",
"@sentry/browser": "5.30.0",
"@solid-primitives/media": "2.2.3",
"@solid-primitives/memo": "1.2.4",
"@solid-primitives/share": "2.0.4",
"@solid-primitives/storage": "1.3.9",
"@solid-primitives/upload": "0.0.110",
"@solidjs/meta": "0.28.2",
"@thisbeyond/solid-select": "0.14.0",
"@tiptap/core": "2.0.3",
"@tiptap/extension-blockquote": "2.0.3",
"@tiptap/extension-bold": "2.0.3",
"@tiptap/extension-bubble-menu": "2.0.3",
"@tiptap/extension-bullet-list": "2.0.3",
"@tiptap/extension-character-count": "2.0.3",
"@tiptap/extension-collaboration": "2.0.3",
"@tiptap/extension-collaboration-cursor": "2.0.3",
"@tiptap/extension-document": "2.0.3",
"@tiptap/extension-dropcursor": "2.0.3",
"@tiptap/extension-floating-menu": "2.0.3",
"@tiptap/extension-focus": "2.0.3",
"@tiptap/extension-gapcursor": "2.0.3",
"@tiptap/extension-hard-break": "2.0.3",
"@tiptap/extension-heading": "2.0.3",
"@tiptap/extension-highlight": "2.0.3",
"@tiptap/extension-history": "2.0.3",
"@tiptap/extension-horizontal-rule": "2.0.3",
"@tiptap/extension-image": "2.0.3",
"@tiptap/extension-italic": "2.0.3",
"@tiptap/extension-link": "2.0.3",
"@tiptap/extension-list-item": "2.0.3",
"@tiptap/extension-ordered-list": "2.0.3",
"@tiptap/extension-paragraph": "2.0.3",
"@tiptap/extension-placeholder": "2.0.3",
"@tiptap/extension-strike": "2.0.3",
"@tiptap/extension-text": "2.0.3",
"@tiptap/extension-underline": "2.0.3",
"@tiptap/extension-youtube": "2.0.3",
"@types/js-cookie": "3.0.4",
"@types/node": "20.1.1",
"@typescript-eslint/eslint-plugin": "6.7.3",
"@typescript-eslint/parser": "6.7.3",
"@urql/core": "3.2.2",
"@urql/devtools": "2.0.3",
"babel-preset-solid": "1.7.4",
"bootstrap": "5.3.2",
"clsx": "2.0.0",
"cross-env": "7.0.3",
"debounce": "1.2.1",
"eslint": "8.50.0",
"eslint-config-stylelint": "20.0.0",
"eslint-import-resolver-typescript": "3.6.1",
"eslint-plugin-import": "2.28.1",
"eslint-plugin-jest": "27.4.0",
"eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-solid": "0.13.0",
"eslint-plugin-sonarjs": "0.21.0",
"eslint-plugin-unicorn": "48.0.1",
"fast-deep-equal": "3.1.3",
"graphql": "16.6.0",
"graphql-tag": "2.12.6",
"html-to-json-parser": "1.1.0",
"husky": "8.0.3",
"hygen": "6.2.11",
"i18next-http-backend": "2.2.0",
"jest": "29.7.0",
"js-cookie": "3.0.5",
"lint-staged": "14.0.1",
"loglevel": "1.8.1",
"loglevel-plugin-prefix": "0.8.4",
"markdown-it": "13.0.1",
"markdown-it-container": "3.0.0",
"markdown-it-implicit-figures": "0.11.0",
"markdown-it-mark": "3.0.1",
"markdown-it-replace-link": "1.2.0",
"nanostores": "0.7.4",
"prettier": "3.0.3",
"prettier-eslint": "15.0.1",
"prosemirror-history": "1.3.0",
"prosemirror-trailing-node": "2.0.3",
"prosemirror-view": "1.30.2",
"rollup": "3.21.6",
"sass": "1.68.0",
"solid-js": "1.7.5",
"solid-popper": "0.3.0",
"solid-tiptap": "0.6.0",
"solid-transition-group": "0.2.2",
"sort-package-json": "2.6.0",
"stylelint": "15.10.3",
"stylelint-config-standard-scss": "11.0.0",
"stylelint-order": "6.0.3",
"stylelint-scss": "5.2.1",
"swiper": "9.4.1",
"typescript": "5.2.2",
"typograf": "7.1.0",
"uniqolor": "1.1.0",
"vite": "4.3.9",
"vite-plugin-mkcert": "1.16.0",
"vite-plugin-sass-dts": "1.3.11",
"vite-plugin-solid": "2.7.0",
"vite-plugin-ssr": "0.4.123",
"y-prosemirror": "1.2.1",
"yjs": "13.6.0"
},
"overrides": {
"sass": "1.77.6",
"vite": "5.3.5",
"yjs": "13.6.19",
"y-prosemirror": "1.2.12"
},
"engines": {
"node": ">= 20"
},
"trustedDependencies": ["@biomejs/biome", "@swc/core", "esbuild", "protobufjs"],
"dependencies": {
"form-data": "^4.0.0",
"idb": "^8.0.0",
"mailgun.js": "^10.2.3"
"@tiptap/extension-collaboration": {
"y-prosemirror": "1.2.1"
},
"@tiptap/extension-collaboration-cursor": {
"y-prosemirror": "1.2.1"
}
}
}

View File

@ -1,62 +0,0 @@
diff --git a/node_modules/solid-tiptap/src/Editor.tsx b/node_modules/solid-tiptap/src/Editor.tsx
index 9d1e51a..2cc36b3 100644
--- a/node_modules/solid-tiptap/src/Editor.tsx
+++ b/node_modules/solid-tiptap/src/Editor.tsx
@@ -1,6 +1,6 @@
import type { EditorOptions } from '@tiptap/core';
import { Editor } from '@tiptap/core';
-import { createEffect, createSignal, onCleanup } from 'solid-js';
+import { createEffect, createSignal, onCleanup, on } from 'solid-js';
export type EditorRef = Editor | ((editor: Editor) => void);
@@ -42,17 +42,19 @@ export default function useEditor<T extends HTMLElement>(
): () => Editor | undefined {
const [signal, setSignal] = createSignal<Editor>();
- createEffect(() => {
- const instance = new Editor({
- ...props(),
- });
-
- onCleanup(() => {
- instance.destroy();
- });
-
- setSignal(instance);
- });
+ createEffect(
+ on(
+ props,
+ (properties) => {
+ if (properties) {
+ const instance = new Editor({ ...properties })
+ onCleanup(instance.destroy)
+ setSignal(instance)
+ }
+ },
+ { defer: true }
+ )
+ )
return signal;
}
@@ -65,14 +67,16 @@ export function useEditorHTML<V extends Editor | undefined>(
export function useEditorJSON<
V extends Editor | undefined,
- R extends Record<string, any>,
+ // biome-ignore lint/suspicious/noExplicitAny: TODO: <explanation>
+R extends Record<string, any>,
>(editor: () => V): () => R | undefined {
return createEditorTransaction(editor, instance => instance?.getJSON() as R);
}
export function useEditorIsActive<
V extends Editor | undefined,
- R extends Record<string, any>,
+ // biome-ignore lint/suspicious/noExplicitAny: TODO: <explanation>
+R extends Record<string, any>,
>(
editor: () => V,
...args: [name: () => string, options?: R] | [options: R]

View File

@ -1,78 +0,0 @@
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({
/* Directory to search for tests */
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: false,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: 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: 'list',
/* Timeout for each test */
timeout: 40000,
/* 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: process.env.BASE_URL || 'https://localhost:3000',
/* Headless */
headless: true,
/* Ignode SSL certificates */
ignoreHTTPSErrors: true,
/* 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: 'webkit',
use: { ...devices['Desktop Safari'] }
}
/* Test against many 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 local dev server before starting the tests */
/* If process env CI is set to false */
webServer: process.env.CI
? undefined
: {
command: 'npm run dev',
url: 'http://localhost:3000',
ignoreHTTPSErrors: true,
reuseExistingServer: !process.env.CI,
timeout: 5 * 60 * 1000
}
})

BIN
public/auth-page.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
public/bonfire.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 KiB

BIN
public/discours-banner.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.125 12.75H4.5C4.08854 12.75 3.75 12.4115 3.75 12C3.75 11.5885 4.08854 11.25 4.5 11.25H19.125C19.5365 11.25 19.875 11.5885 19.875 12C19.875 12.4115 19.5365 12.75 19.125 12.75Z" fill="currentColor"/>
<path
d="M14.0678 18.3593C13.8803 18.3593 13.6928 18.2916 13.547 18.151C13.2501 17.8593 13.2397 17.3853 13.5314 17.0885L18.4584 11.9999L13.5314 6.91137C13.2397 6.6145 13.2501 6.14054 13.547 5.84887C13.8439 5.56241 14.3178 5.57283 14.6043 5.8697L20.0366 11.4791C20.3178 11.7707 20.3178 12.2291 20.0366 12.5207L14.6043 18.1301C14.4584 18.2864 14.2657 18.3593 14.0678 18.3593Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 713 B

View File

@ -1,4 +0,0 @@
<svg width="25" height="28" viewBox="0 0 25 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M19.5 8.01738L17.4826 6L10.5029 12.9797L7.51738 9.99421L5.5 12.0116L10.5029 17.0145L19.5 8.01738Z" fill="#2BB452"/>
</svg>

Before

Width:  |  Height:  |  Size: 271 B

View File

@ -1,5 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="ic24_copy">
<path id="vector" fill-rule="evenodd" clip-rule="evenodd" d="M4 6C4 3.79086 5.79086 2 8 2H12C14.2091 2 16 3.79086 16 6V14C16 16.2091 14.2091 18 12 18H8C5.79086 18 4 16.2091 4 14V6ZM8 4C6.89543 4 6 4.89543 6 6V14C6 15.1046 6.89543 16 8 16H12C13.1046 16 14 15.1046 14 14V6C14 4.89543 13.1046 4 12 4H8ZM16.6344 6.90064C16.9109 6.42258 17.5227 6.25922 18.0007 6.53576C19.1937 7.22587 20 8.5182 20 10V18C20 20.2092 18.2091 22 16 22H12C10.5182 22 9.22586 21.1937 8.53575 20.0007C8.2592 19.5227 8.42257 18.911 8.90063 18.6344C9.37869 18.3579 9.99041 18.5212 10.267 18.9993C10.6143 19.5997 11.261 20 12 20H16C17.1046 20 18 19.1046 18 18V10C18 9.261 17.5997 8.61429 16.9993 8.26697C16.5212 7.99043 16.3579 7.3787 16.6344 6.90064Z" fill="#141414"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 866 B

View File

Before

Width:  |  Height:  |  Size: 290 B

After

Width:  |  Height:  |  Size: 290 B

View File

Before

Width:  |  Height:  |  Size: 350 B

After

Width:  |  Height:  |  Size: 350 B

View File

Before

Width:  |  Height:  |  Size: 714 B

After

Width:  |  Height:  |  Size: 714 B

View File

@ -0,0 +1,11 @@
<svg
width="13" height="16"
viewBox="0 0 13 16"
fill="none"
version="1.1"
xmlns="http://www.w3.org/2000/svg">
<path
d="M 10.1573,7.43667 C 11.2197,6.70286 11.9645,5.49809 11.9645,4.38095 11.9645,1.90571 10.0478,0 7.58352,0 H 0.738281 V 15.3333 H 8.44876 c 2.28904,0 4.06334,-1.8619 4.06334,-4.1509 0,-1.66478 -0.9419,-3.08859 -2.3548,-3.74573 z M 4.02344,2.73828 h 3.28571 c 0.90905,0 1.64286,0.73381 1.64286,1.64286 0,0.90905 -0.73381,1.64286 -1.64286,1.64286 H 4.02344 Z M 4.01629,9.3405869 h 3.87946 c 0.9090501,0 1.6428601,0.7338101 1.6428601,1.6428601 0,0.90905 -0.73381,1.64286 -1.6428601,1.64286 H 4.01629 Z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 677 B

View File

@ -1,4 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M11.9967 4.51318C11.5931 4.51318 11.1868 4.59652 10.8118 4.75798L7.9056 6.01058L9.83268 6.81266L11.4056 6.13558C11.5931 6.05225 11.7962 6.01058 11.9993 6.01058C12.2025 6.01058 12.4056 6.05225 12.5931 6.13558L20.5801 9.57829C20.6504 9.60693 20.6947 9.67464 20.6947 9.75016C20.6947 9.82568 20.6504 9.89339 20.5801 9.92204L12.5931 13.3647C12.2181 13.5262 11.7806 13.5262 11.4056 13.3647L3.41862 9.92204C3.34831 9.89339 3.30404 9.82568 3.30404 9.75016C3.30404 9.67464 3.34831 9.60693 3.41862 9.57829L6.47591 8.26058L11.7103 10.4429C11.804 10.4819 11.903 10.5002 11.9993 10.5002C12.291 10.5002 12.5723 10.3283 12.6921 10.0392C12.8509 9.65641 12.6712 9.21631 12.2884 9.05746L8.39258 7.43506L8.39518 7.43246L6.4681 6.63037L2.42643 8.37516C1.87435 8.60954 1.51758 9.1512 1.51758 9.75016C1.51758 10.3491 1.87435 10.8908 2.42643 11.1252L4.87435 12.1825V18.5679C4.64779 18.7371 4.49935 19.008 4.49935 19.3127V20.8127C4.49935 21.3309 4.91862 21.7502 5.43685 21.7502H5.81185C6.33008 21.7502 6.74935 21.3309 6.74935 20.8127V19.3127C6.74935 19.008 6.60091 18.7371 6.37435 18.5679V17.1512C7.42904 17.909 9.2181 18.7502 11.9993 18.7502C15.5384 18.7502 17.4889 17.3856 18.3353 16.5705C18.8379 16.0887 19.1243 15.4064 19.1243 14.6955V12.1825L21.5723 11.1252C22.1243 10.8908 22.4811 10.3491 22.4811 9.75016C22.4811 9.1512 22.1243 8.60954 21.5723 8.37516L13.1868 4.75798C12.8092 4.59652 12.403 4.51318 11.9967 4.51318ZM6.37435 12.8283L10.8118 14.7424C11.1895 14.9064 11.5931 14.9845 11.9993 14.9845C12.4056 14.9845 12.8092 14.9064 13.1868 14.7424L17.6243 12.8283V14.6955C17.6243 15.0002 17.5046 15.2892 17.2962 15.4897C16.6113 16.146 15.015 17.2502 11.9993 17.2502C8.98372 17.2502 7.38737 16.146 6.70247 15.4897C6.49414 15.2892 6.37435 15.0002 6.37435 14.6955V12.8283Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.625 4.5C7.59115 4.5 6.75 5.34115 6.75 6.375V8.25H5.625C4.59115 8.25 3.75 9.09115 3.75 10.125V17.25C3.75 18.4896 4.76042 19.5 6 19.5H18C19.2396 19.5 20.25 18.4896 20.25 17.25V6.375C20.25 5.34115 19.4089 4.5 18.375 4.5H8.625ZM8.625 6H18.375C18.5807 6 18.75 6.16927 18.75 6.375V17.25C18.75 17.6641 18.4141 18 18 18H8.1224C8.20313 17.7656 8.25 17.513 8.25 17.25V6.375C8.25 6.16927 8.41927 6 8.625 6ZM10.125 7.5C9.71094 7.5 9.375 7.83594 9.375 8.25C9.375 8.66406 9.71094 9 10.125 9H16.875C17.2891 9 17.625 8.66406 17.625 8.25C17.625 7.83594 17.2891 7.5 16.875 7.5H10.125ZM5.625 9.75H6.75V17.25C6.75 17.6641 6.41406 18 6 18C5.58594 18 5.25 17.6641 5.25 17.25V10.125C5.25 9.91927 5.41927 9.75 5.625 9.75ZM10.125 10.125C9.71094 10.125 9.375 10.4609 9.375 10.875C9.375 11.2891 9.71094 11.625 10.125 11.625H16.875C17.2891 11.625 17.625 11.2891 17.625 10.875C17.625 10.4609 17.2891 10.125 16.875 10.125H10.125ZM10.125 12.75C9.71094 12.75 9.375 13.0859 9.375 13.5V16.125C9.375 16.5391 9.71094 16.875 10.125 16.875H12.375C12.7891 16.875 13.125 16.5391 13.125 16.125V13.5C13.125 13.0859 12.7891 12.75 12.375 12.75H10.125ZM15 12.75C14.5859 12.75 14.25 13.0859 14.25 13.5C14.25 13.9141 14.5859 14.25 15 14.25H16.875C17.2891 14.25 17.625 13.9141 17.625 13.5C17.625 13.0859 17.2891 12.75 16.875 12.75H15ZM15 15.375C14.5859 15.375 14.25 15.7109 14.25 16.125C14.25 16.5391 14.5859 16.875 15 16.875H16.875C17.2891 16.875 17.625 16.5391 17.625 16.125C17.625 15.7109 17.2891 15.375 16.875 15.375H15Z" fill="black"/>
<path d="M8.25 4.125C7.14583 4.125 6.13281 4.6901 5.60937 5.60156C5.40365 5.95573 5.16406 6.53385 5.03125 6.91927C4.91146 7.2474 3.07813 11.349 1.95313 13.8568L1.95833 13.8594C1.66667 14.4349 1.5 15.0755 1.5 15.75C1.5 18.2318 3.6875 20.25 6.375 20.25C9.0625 20.25 11.25 18.2318 11.25 15.75V14.3724C11.4505 14.3099 11.7109 14.25 12 14.25C12.2891 14.25 12.5495 14.3099 12.75 14.3724V15.75C12.75 18.2318 14.9375 20.25 17.625 20.25C20.3125 20.25 22.5 18.2318 22.5 15.75C22.5 15.0755 22.3333 14.4349 22.0417 13.8594L22.0469 13.8568C20.9219 11.349 19.0885 7.2474 18.9688 6.92448C18.8359 6.53646 18.5964 5.95833 18.3906 5.60417C17.8672 4.6901 16.8542 4.125 15.75 4.125C14.1354 4.125 12.8177 5.32813 12.7552 6.82813C12.526 6.78125 12.2734 6.75 12 6.75C11.7266 6.75 11.474 6.78125 11.2448 6.82813C11.1823 5.32813 9.86458 4.125 8.25 4.125ZM8.25 5.625C9.07813 5.625 9.75 6.21354 9.75 6.9375V12.5104C8.8724 11.7318 7.6849 11.25 6.375 11.25C5.75781 11.25 5.16927 11.362 4.625 11.5547C5.48177 9.64063 6.36458 7.65365 6.45052 7.40885C6.57292 7.04688 6.77604 6.58333 6.90885 6.35156C7.16667 5.90365 7.67969 5.625 8.25 5.625ZM15.75 5.625C16.3203 5.625 16.8333 5.90365 17.0911 6.35156C17.224 6.58333 17.4271 7.04948 17.5495 7.40885C17.6354 7.65365 18.5182 9.64063 19.3724 11.5547C18.8307 11.362 18.2422 11.25 17.625 11.25C16.3151 11.25 15.1276 11.7318 14.25 12.5104V6.9375C14.25 6.21354 14.9219 5.625 15.75 5.625ZM12 8.25C12.2891 8.25 12.5495 8.3099 12.75 8.3724V9.82552C12.5208 9.78125 12.2708 9.75 12 9.75C11.7292 9.75 11.4792 9.78125 11.25 9.82552V8.3724C11.4505 8.3099 11.7109 8.25 12 8.25ZM12 11.25C12.2891 11.25 12.5495 11.3099 12.75 11.3724V12.8255C12.5208 12.7812 12.2708 12.75 12 12.75C11.7292 12.75 11.4792 12.7812 11.25 12.8255V11.3724C11.4505 11.3099 11.7109 11.25 12 11.25ZM6.375 12.75C8.23698 12.75 9.75 14.0964 9.75 15.75C9.75 17.4036 8.23698 18.75 6.375 18.75C4.51302 18.75 3 17.4036 3 15.75C3 14.0964 4.51302 12.75 6.375 12.75ZM17.625 12.75C19.487 12.75 21 14.0964 21 15.75C21 17.4036 19.487 18.75 17.625 18.75C15.763 18.75 14.25 17.4036 14.25 15.75C14.25 14.0964 15.763 12.75 17.625 12.75Z" fill="#141414"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.89589 13.4965C6.77089 13.6545 6.63373 13.7326 6.48269 13.7309C6.33339 13.7291 6.21533 13.6684 6.132 13.5468C6.04867 13.4271 6.04172 13.2691 6.11637 13.0712L7.61117 9.19789H4.76915C4.65283 9.19789 4.55561 9.16143 4.47575 9.08504C4.39589 9.01039 4.35596 8.91491 4.35596 8.79859C4.35596 8.68227 4.40283 8.56421 4.49832 8.44442L9.10422 2.50345C9.22922 2.34546 9.36637 2.26733 9.51742 2.26907C9.66672 2.27081 9.78478 2.33157 9.86811 2.4531C9.95144 2.57289 9.95839 2.73088 9.88373 2.92879L8.38894 6.80206H11.231C11.3473 6.80206 11.4445 6.83852 11.5244 6.9149C11.6042 6.98956 11.6442 7.08504 11.6442 7.20136C11.6442 7.31768 11.5973 7.43574 11.5018 7.55553L6.89589 13.4965Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 797 B

View File

@ -1,4 +0,0 @@
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M16.1785 3.05371C15.1421 3.05371 14.3035 3.89486 14.3035 4.92871C14.3035 5.96256 15.1421 6.80371 16.1785 6.80371C17.215 6.80371 18.0535 5.96256 18.0535 4.92871C18.0535 3.89486 17.215 3.05371 16.1785 3.05371ZM14.6785 7.55371C14.4051 7.55371 14.1473 7.61621 13.9129 7.72038L10.9181 9.12923C10.7723 9.19694 10.6577 9.31413 10.5926 9.45736L9.12124 12.7308C9.07957 12.8089 9.05353 12.8975 9.05353 12.9912C9.05353 13.3011 9.30613 13.5537 9.61603 13.5537C9.8478 13.5537 10.0483 13.4131 10.1343 13.2126V13.21L11.702 10.5303L12.4858 10.249L11.7462 12.6761L11.7541 12.6787C11.7098 12.8376 11.6785 13.0042 11.6785 13.1787C11.6785 13.8923 12.0822 14.5068 12.6707 14.8245L12.6655 14.8298L15.5848 16.9626L16.5874 20.5225H16.59C16.6837 20.8298 16.965 21.0537 17.3035 21.0537C17.7176 21.0537 18.0535 20.7178 18.0535 20.3037C18.0535 20.2282 18.0379 20.1553 18.0171 20.085H18.0197L17.2671 16.3454C17.2436 16.223 17.1968 16.1058 17.1317 15.999L15.4806 13.2881L16.4806 9.99902L16.4572 9.99121C16.5145 9.81152 16.5535 9.62663 16.5535 9.42871C16.5535 8.39486 15.715 7.55371 14.6785 7.55371ZM17.1681 10.5355L16.603 12.0771L17.0353 12.4001C17.0718 12.4261 17.1108 12.4469 17.1525 12.4626L19.8869 13.5042C19.8973 13.5094 19.9103 13.512 19.9207 13.5173L19.9363 13.5225C19.9936 13.5407 20.0535 13.5537 20.116 13.5537C20.4259 13.5537 20.6785 13.3011 20.6785 12.9912C20.6785 12.7699 20.5483 12.5771 20.3608 12.486L17.9233 11.21L17.1681 10.5355ZM8.91551 13.9313C8.69676 13.9105 8.47801 14.0225 8.36863 14.2282L7.43895 15.9886L6.11343 15.2829C5.74884 15.0876 5.29572 15.2256 5.1004 15.5928L3.33738 18.9053C3.14468 19.2673 3.2853 19.723 3.64988 19.9183L4.48582 20.3636C4.47801 20.1058 4.5379 19.8454 4.66551 19.611C4.92593 19.1188 5.43374 18.8115 5.99103 18.8115C6.23322 18.8115 6.47801 18.874 6.69155 18.9886C6.80353 19.0485 6.9077 19.1188 6.99884 19.21L7.98843 17.348C7.99103 17.3454 7.99103 17.3454 7.99363 17.3428L8.2254 16.9027L8.43113 16.5173V16.5146L9.36343 14.7542C9.50926 14.4782 9.40249 14.1396 9.12905 13.9938C9.06134 13.9574 8.98843 13.9365 8.91551 13.9313ZM11.8608 15.3532L11.4988 17.0225L9.93113 19.8844L9.89988 19.9417C9.83999 20.0485 9.80353 20.1709 9.80353 20.3037C9.80353 20.7178 10.1395 21.0537 10.5535 21.0537C10.8244 21.0537 11.0613 20.9079 11.1916 20.6917L13.7332 16.7334L11.8608 15.3532ZM6.05613 19.5641C5.76447 19.5407 5.4728 19.6865 5.32697 19.96C5.13165 20.3271 5.27228 20.7803 5.63686 20.9756C6.00145 21.1683 6.45718 21.0303 6.65249 20.6657C6.8452 20.2985 6.70718 19.8454 6.33999 19.6501C6.24884 19.6006 6.15249 19.5745 6.05613 19.5641Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,4 +0,0 @@
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.4285 3.05347C11.392 3.05347 10.5535 3.89461 10.5535 4.92847C10.5535 5.96232 11.392 6.80347 12.4285 6.80347C13.4649 6.80347 14.3035 5.96232 14.3035 4.92847C14.3035 3.89461 13.4649 3.05347 12.4285 3.05347ZM12.4285 7.55347C10.3113 7.55347 9.05347 9.05347 9.05347 10.1785V14.6785C9.05347 15.0925 9.3894 15.4285 9.80347 15.4285H10.1785V21.7852C10.1785 22.2097 10.5222 22.5535 10.9467 22.5535C11.3582 22.5535 11.6941 22.2332 11.7149 21.8243L12.017 15.4285H12.8399L13.142 21.8243C13.1628 22.2332 13.4988 22.5535 13.9102 22.5535C14.3347 22.5535 14.6785 22.2097 14.6785 21.7852V15.4285H15.0535C15.4675 15.4285 15.8035 15.0925 15.8035 14.6785V10.1785C15.8035 9.05347 14.5457 7.55347 12.4285 7.55347Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 831 B

View File

@ -1,4 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M10.125 3.375C6.40365 3.375 3.375 6.40365 3.375 10.125C3.375 13.8464 6.40365 16.875 10.125 16.875C11.6745 16.875 13.0964 16.3464 14.237 15.4661L19.1432 20.3698C19.4818 20.7109 20.0312 20.7109 20.3698 20.3698C20.7109 20.0312 20.7109 19.4818 20.3698 19.1432L15.4661 14.237C16.3464 13.0964 16.875 11.6745 16.875 10.125C16.875 6.40365 13.8464 3.375 10.125 3.375ZM10.125 4.875C13.0208 4.875 15.375 7.22917 15.375 10.125C15.375 13.0208 13.0208 15.375 10.125 15.375C7.22917 15.375 4.875 13.0208 4.875 10.125C4.875 7.22917 7.22917 4.875 10.125 4.875Z" fill="currentColor"/>
<path d="M10.125 3.375C6.40365 3.375 3.375 6.40365 3.375 10.125C3.375 13.8464 6.40365 16.875 10.125 16.875C11.6745 16.875 13.0964 16.3464 14.237 15.4661L19.1432 20.3698C19.4818 20.7109 20.0312 20.7109 20.3698 20.3698C20.7109 20.0312 20.7109 19.4818 20.3698 19.1432L15.4661 14.237C16.3464 13.0964 16.875 11.6745 16.875 10.125C16.875 6.40365 13.8464 3.375 10.125 3.375ZM10.125 4.875C13.0208 4.875 15.375 7.22917 15.375 10.125C15.375 13.0208 13.0208 15.375 10.125 15.375C7.22917 15.375 4.875 13.0208 4.875 10.125C4.875 7.22917 7.22917 4.875 10.125 4.875Z" fill="#141414"/>
</svg>

Before

Width:  |  Height:  |  Size: 680 B

After

Width:  |  Height:  |  Size: 673 B

View File

@ -1,3 +0,0 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.3459 13.1364H8.69673V22.1618C8.69673 22.34 8.85155 22.4844 9.04263 22.4844H13.0285C13.2196 22.4844 13.3744 22.34 13.3744 22.1618V13.1789H16.0769C16.2526 13.1789 16.4005 13.0559 16.4205 12.8931L16.831 9.57044C16.8423 9.47902 16.8112 9.38747 16.7456 9.31889C16.68 9.25025 16.586 9.21096 16.4874 9.21096H13.3746V7.12812C13.3746 6.50025 13.7371 6.18186 14.4521 6.18186C14.554 6.18186 16.4874 6.18186 16.4874 6.18186C16.6785 6.18186 16.8333 6.03741 16.8333 5.85928V2.80934C16.8333 2.63115 16.6785 2.48676 16.4874 2.48676H13.6825C13.6627 2.48586 13.6188 2.48438 13.554 2.48438C13.0673 2.48438 11.3757 2.57347 10.0394 3.71992C8.55878 4.99038 8.76459 6.51154 8.81378 6.77528V9.21089H6.3459C6.15483 9.21089 6 9.35528 6 9.53347V12.8137C6 12.9919 6.15483 13.1364 6.3459 13.1364Z" fill="#141414"/>
</svg>

Before

Width:  |  Height:  |  Size: 901 B

View File

@ -1,5 +0,0 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="vk logo">
<path id="Vector" d="M14.88 12.4844C14.88 13.054 14.7111 13.6108 14.3946 14.0844C14.0782 14.558 13.6284 14.9272 13.1021 15.1451C12.5759 15.3631 11.9968 15.4202 11.4381 15.309C10.8795 15.1979 10.3663 14.9236 9.96353 14.5208C9.56076 14.1181 9.28646 13.6049 9.17534 13.0462C9.06421 12.4876 9.12125 11.9085 9.33923 11.3822C9.55721 10.856 9.92634 10.4062 10.4 10.0897C10.8736 9.77328 11.4304 9.60438 12 9.60438C12.7636 9.60525 13.4956 9.90896 14.0355 10.4489C14.5754 10.9888 14.8791 11.7208 14.88 12.4844ZM21 8.52437V16.4444C20.9985 17.7806 20.467 19.0617 19.5221 20.0065C18.5773 20.9514 17.2962 21.4829 15.96 21.4844H8.04C6.70377 21.4829 5.42271 20.9514 4.47785 20.0065C3.533 19.0617 3.00151 17.7806 3 16.4444V8.52437C3.00151 7.18815 3.533 5.90708 4.47785 4.96223C5.42271 4.01737 6.70377 3.48589 8.04 3.48438H15.96C17.2962 3.48589 18.5773 4.01737 19.5221 4.96223C20.467 5.90708 20.9985 7.18815 21 8.52437ZM16.32 12.4844C16.32 11.63 16.0666 10.7947 15.5919 10.0843C15.1173 9.37389 14.4426 8.82019 13.6532 8.49322C12.8638 8.16625 11.9952 8.0807 11.1572 8.24738C10.3192 8.41407 9.54946 8.82551 8.9453 9.42967C8.34114 10.0338 7.9297 10.8036 7.76301 11.6416C7.59632 12.4796 7.68187 13.3482 8.00884 14.1376C8.33581 14.9269 8.88952 15.6016 9.59994 16.0763C10.3104 16.551 11.1456 16.8044 12 16.8044C13.1453 16.8031 14.2434 16.3475 15.0533 15.5376C15.8631 14.7278 16.3187 13.6297 16.32 12.4844ZM17.76 7.80438C17.76 7.59077 17.6967 7.38196 17.578 7.20436C17.4593 7.02675 17.2906 6.88833 17.0933 6.80659C16.896 6.72484 16.6788 6.70345 16.4693 6.74513C16.2598 6.7868 16.0674 6.88966 15.9163 7.0407C15.7653 7.19174 15.6624 7.38418 15.6208 7.59368C15.5791 7.80318 15.6005 8.02033 15.6822 8.21767C15.764 8.41502 15.9024 8.58369 16.08 8.70236C16.2576 8.82103 16.4664 8.88438 16.68 8.88438C16.9664 8.88438 17.2411 8.77059 17.4437 8.56805C17.6462 8.36551 17.76 8.09081 17.76 7.80438Z" fill="black"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1,7 +0,0 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="linkedin">
<g id="Group">
<path id="Vector" d="M6.93855 21.4004V9.51472H2.99144V21.4004H6.93896H6.93855ZM4.96582 7.89221C6.34197 7.89221 7.19872 6.97953 7.19872 5.83894C7.17296 4.67236 6.34197 3.78516 4.99199 3.78516C3.64109 3.78516 2.75879 4.67236 2.75879 5.83884C2.75879 6.97943 3.61522 7.89211 4.93996 7.89211H4.96551L4.96582 7.89221ZM9.12333 21.4004H13.0701V14.7636C13.0701 14.4089 13.0959 14.0532 13.2002 13.7998C13.4854 13.0897 14.1348 12.3548 15.2254 12.3548C16.6533 12.3548 17.2248 13.4446 17.2248 15.0426V21.4004H21.1715V14.5855C21.1715 10.9349 19.2246 9.23607 16.6278 9.23607C14.4987 9.23607 13.5637 10.4271 13.0442 11.2383H13.0704V9.51513H9.12353C9.17505 10.6302 9.12322 21.4008 9.12322 21.4008L9.12333 21.4004Z" fill="black"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 859 B

View File

@ -1,5 +0,0 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="telegram logo">
<path id="Icon" d="M2.35326 12.1563L6.96167 13.7367L8.7454 19.0076C8.85953 19.3452 9.3088 19.4699 9.607 19.2459L12.1758 17.3218C12.4451 17.1202 12.8286 17.1101 13.11 17.2978L17.7432 20.3886C18.0622 20.6016 18.5141 20.441 18.5941 20.0869L21.9882 5.08587C22.0756 4.69898 21.6618 4.37623 21.2609 4.51871L2.34786 11.2226C1.88113 11.388 1.88519 11.9952 2.35326 12.1563ZM8.45793 12.8954L17.4645 7.79852C17.6263 7.70719 17.7929 7.90829 17.6539 8.02676L10.2209 14.3753C9.9596 14.5988 9.79107 14.8978 9.74334 15.2224L9.49014 16.9465C9.4566 17.1767 9.10467 17.1996 9.03553 16.9768L8.06173 13.8328C7.9502 13.4742 8.11273 13.0912 8.45793 12.8954Z" fill="#141414"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 784 B

View File

@ -1,4 +0,0 @@
<svg width="20" height="16" viewBox="0 0 20 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M19.5467 1.84651C19.2325 1.98268 18.9095 2.0976 18.5794 2.1909C18.9702 1.75908 19.2681 1.25097 19.45 0.694956C19.4908 0.570325 19.4485 0.433906 19.3437 0.352395C19.239 0.270821 19.0935 0.26105 18.9782 0.327809C18.2772 0.734041 17.521 1.02598 16.728 1.19669C15.9293 0.434032 14.8444 0 13.7228 0C11.3554 0 9.42934 1.88194 9.42934 4.19514C9.42934 4.37733 9.44114 4.55851 9.4645 4.73716C6.52677 4.48513 3.79561 3.07422 1.92014 0.826269C1.8533 0.746145 1.75033 0.702962 1.64491 0.71122C1.53943 0.71929 1.44465 0.777413 1.39136 0.866741C1.01098 1.50452 0.809882 2.23396 0.809882 2.97613C0.809882 3.98698 1.17924 4.94608 1.83169 5.6955C1.6333 5.62836 1.44078 5.54446 1.25704 5.44479C1.1584 5.39114 1.03801 5.39196 0.940011 5.44687C0.841946 5.50178 0.780463 5.60277 0.777882 5.71315C0.77743 5.73175 0.777431 5.75035 0.777431 5.76919C0.777431 7.27806 1.60852 8.63652 2.87917 9.37693C2.77 9.36627 2.66091 9.35083 2.55252 9.33059C2.44078 9.30972 2.32588 9.34799 2.25052 9.43127C2.17504 9.51448 2.15007 9.63047 2.18485 9.73638C2.65517 11.1712 3.86607 12.2265 5.32993 12.5483C4.11581 13.2913 2.72736 13.6806 1.26982 13.6806C0.965688 13.6806 0.659818 13.6631 0.360464 13.6285C0.211755 13.6112 0.0695618 13.697 0.0189168 13.8352C-0.0317281 13.9734 0.0219491 14.1276 0.148465 14.2068C2.02091 15.3799 4.186 16 6.40954 16C10.7808 16 13.5153 13.9859 15.0394 12.2962C16.9401 10.1893 18.0301 7.40061 18.0301 4.64519C18.0301 4.53007 18.0283 4.41383 18.0247 4.29796C18.7746 3.74592 19.4202 3.07782 19.9456 2.30992C20.0254 2.1933 20.0167 2.03916 19.9243 1.93181C19.832 1.82439 19.6781 1.78965 19.5467 1.84651Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -1,3 +0,0 @@
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.2129 13.7512C19.8763 14.4052 20.5765 15.0206 21.1715 15.7406C21.4344 16.0605 21.6832 16.3907 21.8736 16.762C22.1433 17.2898 21.899 17.8707 21.4303 17.9022L18.5166 17.9009C17.7652 17.9638 17.1657 17.6584 16.6616 17.1395C16.2582 16.7246 15.8846 16.2831 15.4967 15.8542C15.3377 15.6789 15.1713 15.5139 14.9724 15.3835C14.5747 15.1228 14.2294 15.2026 14.0021 15.6215C13.7706 16.0476 13.7181 16.5193 13.6954 16.9942C13.6642 17.687 13.4568 17.8691 12.7676 17.9008C11.2947 17.9709 9.89691 17.7459 8.59838 16.9957C7.45355 16.3343 6.56579 15.4006 5.79308 14.3435C4.28861 12.2852 3.13649 10.0234 2.10101 7.6983C1.86793 7.17444 2.03838 6.89324 2.6108 6.88329C3.56132 6.86464 4.5117 6.86597 5.46334 6.88196C5.84966 6.88767 6.10541 7.11141 6.25458 7.47993C6.76884 8.75675 7.39809 9.97154 8.18794 11.0975C8.39829 11.3973 8.61277 11.6971 8.9182 11.9081C9.25609 12.1417 9.51335 12.0643 9.6723 11.6842C9.77317 11.4432 9.81733 11.1837 9.84007 10.9256C9.91537 10.0376 9.92529 9.15109 9.79321 8.26621C9.71213 7.71396 9.40407 7.35645 8.85833 7.25194C8.57985 7.19866 8.62131 7.09402 8.75615 6.93352C8.99035 6.65669 9.21061 6.48438 9.6497 6.48438H12.9426C13.461 6.58769 13.5761 6.82284 13.6471 7.34955L13.6499 11.0429C13.6442 11.2468 13.7508 11.8519 14.1145 11.9869C14.4056 12.0829 14.5975 11.8478 14.7721 11.6614C15.5605 10.8165 16.1232 9.81793 16.6259 8.78396C16.849 8.32931 17.0408 7.85714 17.2267 7.38538C17.3644 7.0353 17.5806 6.86305 17.9711 6.87068L21.1403 6.87353C21.2343 6.87353 21.3294 6.87493 21.4204 6.89065C21.9544 6.98255 22.1007 7.21452 21.9358 7.74109C21.6759 8.56725 21.1703 9.25572 20.6759 9.94738C20.1473 10.6858 19.5821 11.399 19.058 12.1418C18.5764 12.8201 18.6147 13.162 19.2129 13.7512Z" fill="#141414"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,3 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.0001 10.2321L18.1873 4.04492L19.9551 5.81269L13.7679 11.9999L19.9551 18.1871L18.1873 19.9548L12.0001 13.7676L5.81293 19.9548L4.04517 18.1871L10.2324 11.9999L4.04517 5.81269L5.81293 4.04492L12.0001 10.2321Z" fill="#141414"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.1512 4.42386L4.42326 17.1518L6.84763 19.5761L19.5756 6.84822L17.1512 4.42386Z" fill="#393840"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.5755 17.1518L6.84763 4.42386L4.42326 6.84822L17.1512 19.5761L19.5755 17.1518Z" fill="#393840"/>
</svg>

Before

Width:  |  Height:  |  Size: 380 B

After

Width:  |  Height:  |  Size: 401 B

View File

@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 6.73787L19.2621 4L9.78964 13.4725L5.73787 9.42071L3 12.1586L9.78964 18.9482L22 6.73787Z" fill="#141414"/>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 6.73787L19.2621 4L9.78964 13.4725L5.73787 9.42071L3 12.1586L9.78964 18.9482L22 6.73787Z" fill="#393840"/>
</svg>

Before

Width:  |  Height:  |  Size: 264 B

After

Width:  |  Height:  |  Size: 262 B

View File

@ -1,3 +0,0 @@
<svg width="18" height="10" viewBox="0 0 18 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.1042 9.90633C16.9063 9.90633 16.7136 9.82821 16.5626 9.67716L8.98965 1.91675L1.43236 9.66154C1.1459 9.95841 0.671944 9.96362 0.375069 9.67716C0.0781948 9.3855 0.0729868 8.91154 0.359444 8.61467L8.45319 0.322998C8.73965 0.0313314 9.24486 0.0313314 9.53132 0.322998L17.6407 8.63029C17.9272 8.92716 17.9219 9.40112 17.6251 9.69279C17.4792 9.83342 17.2917 9.90633 17.1042 9.90633Z" fill="#9FA1A7"/>
</svg>

Before

Width:  |  Height:  |  Size: 511 B

View File

@ -1,8 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon">
<path d="M2.70423 20.2826H18.9296C19.6763 20.2826 20.2817 19.6773 20.2817 18.9305V15.9658C20.2817 15.6139 20.1445 15.2759 19.8992 15.0235L16.5187 11.5449C15.9878 10.9986 15.1106 10.9985 14.5796 11.5447L13.0829 13.084C12.5107 13.6726 11.5502 13.6196 11.0462 12.9716L8.31344 9.45821C7.77885 8.77092 6.74349 8.76071 6.19546 9.43733L1.65353 15.0449C1.45852 15.2857 1.35211 15.5861 1.35211 15.896V18.9305C1.35211 19.6773 1.95747 20.2826 2.70423 20.2826Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5775 2.70423H2.70423V17.5775H17.5775V2.70423ZM0 0V20.2817H20.2817V0H0Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24 5.40845H21.9718V21.9718H5.40845V24H24V5.40845Z" fill="currentColor"/>
<path d="M14.8732 6.08451C14.8732 7.20463 13.9652 8.11268 12.8451 8.11268C11.7249 8.11268 10.8169 7.20463 10.8169 6.08451C10.8169 4.96438 11.7249 4.05634 12.8451 4.05634C13.9652 4.05634 14.8732 4.96438 14.8732 6.08451Z" fill="currentColor"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,8 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Icon">
<path d="M2.70423 20.2826H18.9296C19.6763 20.2826 20.2817 19.6773 20.2817 18.9305V15.9658C20.2817 15.6139 20.1445 15.2759 19.8992 15.0235L16.5187 11.5449C15.9878 10.9986 15.1106 10.9985 14.5796 11.5447L13.0829 13.084C12.5107 13.6726 11.5502 13.6196 11.0462 12.9716L8.31344 9.45821C7.77885 8.77092 6.74349 8.76071 6.19546 9.43733L1.65353 15.0449C1.45852 15.2857 1.35211 15.5861 1.35211 15.896V18.9305C1.35211 19.6773 1.95747 20.2826 2.70423 20.2826Z" fill="#9FA1A7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5775 2.70423H2.70423V17.5775H17.5775V2.70423ZM0 0V20.2817H20.2817V0H0Z" fill="#9FA1A7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24 5.40845H21.9718V21.9718H5.40845V24H24V5.40845Z" fill="#9FA1A7"/>
<path d="M14.8732 6.08451C14.8732 7.20463 13.9652 8.11268 12.8451 8.11268C11.7249 8.11268 10.8169 7.20463 10.8169 6.08451C10.8169 4.96438 11.7249 4.05634 12.8451 4.05634C13.9652 4.05634 14.8732 4.96438 14.8732 6.08451Z" fill="#9FA1A7"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"> <path d="M19.417 32c-6.792 0-9.375-5.010-9.375-8.547v-10.458h-3.219v-4.13c4.839-1.75 6.016-6.13 6.276-8.625 0.021-0.172 0.156-0.24 0.234-0.24h4.688v8.151h6.401v4.844h-6.427v9.964c0.021 1.333 0.5 3.161 2.943 3.161h0.12c0.844-0.031 1.984-0.276 2.583-0.563l1.542 4.568c-0.583 0.849-3.203 1.833-5.542 1.87h-0.24z"/> </svg>

Before

Width:  |  Height:  |  Size: 403 B

View File

@ -0,0 +1,436 @@
{
"...subscribing": "...subscribing",
"About": "About",
"About the project": "About the project",
"Add a few topics so that the reader knows what your content is about and can find it on pages of topics that interest them. Topics can be swapped, the first topic becomes the title": "Add a few topics so that the reader knows what your content is about and can find it on pages of topics that interest them. Topics can be swapped, the first topic becomes the title",
"Add a link or click plus to embed media": "Add a link or click plus to embed media",
"Add an embed widget": "Add an embed widget",
"Add another image": "Add another image",
"Add audio": "Add audio",
"Add blockquote": "Add blockquote",
"Add comment": "Comment",
"Add cover": "Add cover",
"Add image": "Add image",
"Add images": "Add images",
"Add intro": "Add intro",
"Add link": "Add link",
"Add rule": "Add rule",
"Add signature": "Add signature",
"Add subtitle": "Add subtitle",
"Add url": "Add url",
"Address on Discourse": "Address on Discourse",
"Album name": "Название aльбома",
"Alignment center": "Alignment center",
"Alignment left": "Alignment left",
"Alignment right": "Alignment right",
"All": "All",
"All authors": "All authors",
"All posts": "All posts",
"All topics": "All topics",
"Almost done! Check your email.": "Almost done! Just checking your email.",
"Are you sure you want to delete this comment?": "Are you sure you want to delete this comment?",
"Are you sure you want to delete this draft?": "Are you sure you want to delete this draft?",
"Are you sure you want to to proceed the action?": "Are you sure you want to to proceed the action?",
"Art": "Art",
"Artist": "Artist",
"Artworks": "Artworks",
"Audio": "Audio",
"Author": "Author",
"Authors": "Authors",
"Autotypograph": "Autotypograph",
"Back": "Back",
"Back to editor": "Back to editor",
"Back to main page": "Back to main page",
"Become an author": "Become an author",
"Bold": "Bold",
"Bookmarked": "Saved",
"Bookmarks": "Bookmarks",
"Bullet list": "Bullet list",
"By alphabet": "By alphabet",
"By authors": "By authors",
"By name": "By name",
"By popularity": "By popularity",
"By rating": "By popularity",
"By relevance": "By relevance",
"By shouts": "By publications",
"By signing up you agree with our": "By signing up you agree with our",
"By time": "By time",
"By title": "By title",
"By updates": "By updates",
"By views": "By views",
"Cancel": "Cancel",
"Cancel changes": "Cancel changes",
"Characters": "Знаков",
"Chat Title": "Chat Title",
"Choose a post type": "Choose a post type",
"Choose a title image for the article. You can immediately see how the publication card will look like.": "Choose a title image for the article. You can immediately see how the publication card will look like.",
"Choose who you want to write to": "Choose who you want to write to",
"Collaborate": "Help Edit",
"Collections": "Collections",
"Come up with a subtitle for your story": "Come up with a subtitle for your story",
"Come up with a title for your story": "Come up with a title for your story",
"Comment successfully deleted": "Comment successfully deleted",
"Comments": "Comments",
"Communities": "Communities",
"Community Principles": "Community Principles",
"Confirm": "Confirm",
"Cooperate": "Cooperate",
"Copy": "Copy",
"Copy link": "Copy link",
"Corrections history": "Corrections history",
"Create Chat": "Create Chat",
"Create Group": "Create a group",
"Create account": "Create an account",
"Create an account to add to your bookmarks": "Create an account to add to your bookmarks",
"Create an account to participate in discussions": "Create an account to participate in discussions",
"Create an account to publish articles": "Create an account to publish articles",
"Create an account to subscribe": "Create an account to subscribe",
"Create an account to subscribe to new publications": "Create an account to subscribe to new publications",
"Create an account to vote": "Create an account to vote",
"Create gallery": "Create gallery",
"Create post": "Create post",
"Create video": "Create video",
"Date of Birth": "Date of Birth",
"Decline": "Decline",
"Delete": "Delete",
"Delete cover": "Delete cover",
"Description": "Description",
"Discours": "Discours",
"Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects": "Discours is&#160;an&#160;intellectual environment, a&#160;web space and tools that allows authors to&#160;collaborate with readers and come together to&#160;co-create publications and media projects.<br/><em>We&#160;are convinced that one voice is&#160;good, but many is&#160;better. We&#160;create the most amazing stories together</em>",
"Discours is created with our common effort": "Discours exists because of our common effort",
"Discussing": "Discussing",
"Discussion rules": "Discussion rules",
"Discussions": "Discussions",
"Dogma": "Dogma",
"Draft successfully deleted": "Draft successfully deleted",
"Drafts": "Drafts",
"Drag the image to this area": "Drag the image to this area",
"Each image must be no larger than 5 MB.": "Each image must be no larger than 5 MB.",
"Edit": "Edit",
"Edit profile": "Edit profile",
"Editing": "Editing",
"Email": "Mail",
"Enter": "Enter",
"Enter URL address": "Enter URL address",
"Enter footnote text": "Enter footnote text",
"Enter image description": "Enter image description",
"Enter image title": "Enter image title",
"Enter text": "Enter text",
"Enter the code or click the link from email to confirm": "Enter the code from the email or follow the link in the email to confirm registration",
"Enter your new password": "Enter your new password",
"Error": "Error",
"Everything is ok, please give us your email address": "It's okay, just enter your email address to receive a password reset link.",
"FAQ": "Tips and suggestions",
"Favorite": "Favorites",
"Favorite topics": "Favorite topics",
"Feed settings": "Feed settings",
"Feedback": "Feedback",
"Fill email": "Fill email",
"Fixed": "Fixed",
"Follow": "Follow",
"Follow the topic": "Follow the topic",
"Followers": "Followers",
"Following": "Following",
"Forgot password?": "Forgot your password?",
"Forward": "Forward",
"Full name": "First and last name",
"Gallery": "Gallery",
"Gallery name": "Gallery name",
"Get to know the most intelligent people of our time, edit and discuss the articles, share your expertise, rate and decide what to publish in the magazine": "Get to know the most intelligent people of our time, edit and discuss the articles, share your expertise, rate and decide what to publish in the magazine",
"Go to main page": "Go to main page",
"Group Chat": "Group Chat",
"Groups": "Groups",
"Header 1": "Header 1",
"Header 2": "Header 2",
"Header 3": "Header 3",
"Headers": "Headers",
"Help": "Помощь",
"Help to edit": "Help to edit",
"Here you can customize your profile the way you want.": "Here you can customize your profile the way you want.",
"Here you can manage all your Discourse subscriptions": "Here you can manage all your Discourse subscriptions",
"Hide table of contents": "Hide table of contents",
"Highlight": "Highlight",
"Hooray! Welcome!": "Hooray! Welcome!",
"Horizontal collaborative journalistic platform": "Horizontal collaborative journalism platform",
"Hot topics": "Hot topics",
"Hotkeys": "Горячие клавиши",
"How can I help/skills": "How can I help/skills",
"How it works": "How it works",
"How to help": "How to help?",
"How to write a good article": "Как написать хорошую статью",
"How to write an article": "How to write an article",
"I have an account": "I have an account!",
"I have no account yet": "I don't have an account yet",
"I know the password": "I know the password",
"Image format not supported": "Image format not supported",
"In&nbsp;bookmarks, you can save favorite discussions and&nbsp;materials that you want to return to": "In&nbsp;bookmarks, you can save favorite discussions and&nbsp;materials that you want to return to",
"Incut": "Incut",
"Independant magazine with an open horizontal cooperation about culture, science and society": "Independant magazine with an open horizontal cooperation about culture, science and society",
"Insert footnote": "Insert footnote",
"Insert video link": "Insert video link",
"Introduce": "Introduction",
"Invalid email": "Check if your email is correct",
"Invalid image URL": "Invalid image URL",
"Invalid url format": "Invalid url format",
"Invite co-authors": "Invite co-authors",
"Invite to collab": "Invite to Collab",
"It does not look like url": "It doesn't look like a link",
"Italic": "Italic",
"Join": "Join",
"Join our maillist": "To receive the best postings, just enter your email",
"Join the community": "Join the community",
"Join the global community of authors!": "Join the global community of authors from all over the world!",
"Just start typing...": "Just start typing...",
"Knowledge base": "Knowledge base",
"Last rev.": "Посл. изм.",
"Let's log in": "Let's log in",
"Link copied": "Link copied",
"Link sent, check your email": "Link sent, check your email",
"Lists": "Lists",
"Literature": "Literature",
"Load more": "Show more",
"Loading": "Loading",
"Logout": "Logout",
"Looks like you forgot to upload the video": "Looks like you forgot to upload the video",
"Manifest": "Manifest",
"Manifesto": "Manifesto",
"Many files, choose only one": "Many files, choose only one",
"Material card": "Material card",
"Message": "Message",
"More": "More",
"Most commented": "Commented",
"Most read": "Readable",
"Move down": "Move down",
"Move up": "Move up",
"Music": "Music",
"My feed": "My feed",
"My subscriptions": "Subscriptions",
"Name": "Name",
"New literary work": "New literary work",
"New only": "New only",
"New password": "New password",
"New stories every day and even more!": "New stories and more are waiting for you every day!",
"NotificationNewCommentText1": "{commentsCount, plural, one {New comment} other {{commentsCount} comments}} to your publication",
"NotificationNewCommentText2": "from",
"NotificationNewCommentText3": "{restUsersCount, plural, =0 {} one { one more user} other { and more {restUsersCount} users}}",
"NotificationNewReplyText1": "{commentsCount, plural, one {New reply} other {{commentsCount} replays}} to your publication",
"NotificationNewReplyText2": "from",
"NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { and one more user} other { and more {restUsersCount} users}}",
"Newsletter": "Newsletter",
"Night mode": "Night mode",
"No notifications yet": "No notifications yet",
"Write good articles, comment\nand it won't be so empty here": "Write good articles, comment\nand it won't be so empty here",
"Nothing here yet": "There's nothing here yet",
"Nothing is here": "There is nothing here",
"Notifications": "Notifications",
"Or paste a link to an image": "Or paste a link to an image",
"Ordered list": "Ordered list",
"Our regular contributor": "Our regular contributor",
"Paragraphs": "Абзацев",
"Participating": "Participating",
"Partners": "Partners",
"Password": "Password",
"Password again": "Password again",
"Password should be at least 8 characters": "Password should be at least 8 characters",
"Password should contain at least one number": "Password should contain at least one number",
"Password should contain at least one special character: !@#$%^&*": "Password should contain at least one special character: !@#$%^&*",
"Passwords are not equal": "Passwords are not equal",
"Paste Embed code": "Paste Embed code",
"Personal": "Personal",
"Pin": "Pin",
"Platform Guide": "Platform Guide",
"Please check your email address": "Please check your email address",
"Please confirm your email to finish": "Confirm your email and the action will complete",
"Please enter a name to sign your comments and publication": "Please enter a name to sign your comments and publication",
"Please enter email": "Please enter your email",
"Please enter password": "Please enter a password",
"Please enter password again": "Please enter password again",
"Please, confirm email": "Please confirm email",
"Popular": "Popular",
"Popular authors": "Popular authors",
"Principles": "Community principles",
"Profile": "Profile",
"Profile settings": "Profile settings",
"Publications": "Publications",
"PublicationsWithCount": "{count, plural, =0 {no publications} one {{count} publication} other {{count} publications}}",
"Publish Album": "Publish Album",
"Publish Settings": "Publish Settings",
"Punchline": "Punchline",
"Quit": "Quit",
"Quote": "Quote",
"Quotes": "Quotes",
"Reason uknown": "Reason unknown",
"Recent": "Fresh",
"Registered since {date}": "Registered since {date}",
"Remove link": "Remove link",
"Reply": "Reply",
"Report": "Complain",
"Required": "Required",
"Resend code": "Send confirmation",
"Restore password": "Restore password",
"Save draft": "Save draft",
"Save settings": "Save settings",
"Saving...": "Saving...",
"Scroll up": "Scroll up",
"Search": "Search",
"Search author": "Search author",
"Search topic": "Search topic",
"Sections": "Sections",
"Security": "Security",
"Select": "Select",
"Send": "Send",
"Send link again": "Send link again",
"Settings": "Settings",
"Share": "Share",
"Show": "Show",
"Show lyrics": "Show lyrics",
"Show more": "Show more",
"Show table of contents": "Show table of contents",
"Slug": "Slug",
"Social networks": "Social networks",
"Something went wrong, check email and password": "Something went wrong. Check your email and password",
"Something went wrong, please try again": "Something went wrong, please try again",
"Song lyrics": "Song lyrics...",
"Song title": "Song title",
"Sorry, this address is already taken, please choose another one.": "Sorry, this address is already taken, please choose another one",
"Special projects": "Special projects",
"Specify the source and the name of the author": "Specify the source and the name of the author",
"Start conversation": "Start a conversation",
"Subsccriptions": "Subscriptions",
"Subscribe": "Subscribe",
"Subscribe us": "Subscribe us",
"Subscribe what you like to tune your personal feed": "Subscribe to topics that interest you to customize your personal feed and get instant updates on new posts and discussions",
"Subscribe who you like to tune your personal feed": "Subscribe to authors you're interested in to customize your personal feed and get instant updates on new posts and discussions",
"SubscriberWithCount": "{count, plural, =0 {no followers} one {{count} follower} other {{count} followers}",
"Subscription": "Subscription",
"SubscriptionWithCount": "{count, plural, =0 {no subscriptions} one {{count} subscription} other {{count} subscriptions}",
"Subscriptions": "Subscriptions",
"Substrate": "Substrate",
"Success": "Success",
"Successfully authorized": "Authorization successful",
"Suggest an idea": "Suggest an idea",
"Support us": "Help the magazine",
"Terms of use": "Site rules",
"Text checking": "Text checking",
"Thank you": "Thank you",
"There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?",
"There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?",
"This comment has not yet been rated": "This comment has not yet been rated",
"This email is already taken. If it's you": "This email is already taken. If it's you",
"This functionality is currently not available, we would like to work on this issue. Use the download link.": "This functionality is currently not available, we would like to work on this issue. Use the download link.",
"This post has not been rated yet": "This post has not been rated yet",
"This way we&nbsp;ll realize that you&nbsp;re a real person and&nbsp;ll take your vote into account. And&nbsp;you&nbsp;ll see how others voted": "This way we&nbsp;ll realize that you&nbsp;re a real person and&nbsp;ll take your vote into account. And&nbsp;you&nbsp;ll see how others voted",
"This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed": "This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed",
"To leave a comment please": "To leave a comment please",
"To write a comment, you must": "To write a comment, you must",
"Top authors": "Authors rating",
"Top commented": "Most commented",
"Top discussed": "Top discussed",
"Top month articles": "Top of the month",
"Top rated": "Popular",
"Top recent": "Most recent",
"Top topics": "Interesting topics",
"Top viewed": "Most viewed",
"Topic is supported by": "Topic is supported by",
"Topics": "Topics",
"Topics which supported by author": "Topics which supported by author",
"Try to find another way": "Try to find another way",
"Unfollow": "Unfollow",
"Unfollow the topic": "Unfollow the topic",
"Unnamed draft": "Unnamed draft",
"Upload": "Upload",
"Upload error": "Upload error",
"Upload video": "Upload video",
"Uploading image": "Uploading image",
"Username": "Username",
"Userpic": "Userpic",
"Users": "Users",
"Video": "Video",
"Video format not supported": "Video format not supported",
"Views": "Views",
"We can't find you, check email or": "We can't find you, check email or",
"We know you, please try to login": "This email address is already registered, please try to login",
"We've sent you a message with a link to enter our website.": "We've sent you an email with a link to your email. Follow the link in the email to enter our website.",
"Welcome to Discours": "Welcome to Discours",
"Welcome to Discours to add to your bookmarks": "Welcome to Discours to add to your bookmarks",
"Welcome to Discours to participate in discussions": "Welcome to Discours to participate in discussions",
"Welcome to Discours to publish articles": "Welcome to Discours to publish articles",
"Welcome to Discours to subscribe": "Welcome to Discours to subscribe",
"Welcome to Discours to subscribe to new publications": "Welcome to Discours to subscribe to new publications",
"Welcome to Discours to vote": "Welcome to Discours to vote",
"Where": "From",
"Words": "Слов",
"Work with us": "Cooperate with Discourse",
"Write a comment...": "Write a comment...",
"Write a short introduction": "Write a short introduction",
"Write about the topic": "Write about the topic",
"Write an article": "Write an article",
"Write comment": "Write comment",
"Write message": "Write a message",
"Write to us": "Write to us",
"You can download multiple tracks at once in .mp3, .wav or .flac formats": "You can download multiple tracks at once in .mp3, .wav or .flac formats",
"You were successfully authorized": "You were successfully authorized",
"You&nbsp;ll be able to participate in&nbsp;discussions, rate others' comments and&nbsp;learn about&nbsp;new responses": "You&nbsp;ll be able to participate in&nbsp;discussions, rate others' comments and&nbsp;learn about&nbsp;new responses",
"You've confirmed email": "You've confirmed email",
"You've reached a non-existed page": "You've reached a non-existed page",
"Your email": "Your email",
"Your name will appear on your profile page and as your signature in publications, comments and responses.": "Your name will appear on your profile page and as your signature in publications, comments and responses",
"accomplices": "accomplices",
"actions": "actions",
"add link": "add link",
"all topics": "all topics",
"article": "article",
"author": "author",
"authors": "authors",
"back to menu": "back to menu",
"bold": "bold",
"bookmarks": "bookmarks",
"cancel": "cancel",
"collections": "collections",
"community": "community",
"contents": "contents",
"delimiter": "delimiter",
"discussion": "discourse",
"drafts": "drafts",
"email not confirmed": "email not confirmed",
"enter": "enter",
"feed": "feed",
"follower": "follower",
"general feed": "general tape",
"header 1": "header 1",
"header 2": "header 2",
"header 3": "header 3",
"images": "images",
"invalid password": "invalid password",
"italic": "italic",
"journal": "journal",
"jpg, .png, max. 10 mb.": "jpg, .png, макс. 10 мб.",
"literature": "literature",
"marker list": "marker list",
"min. 1400×1400 pix": "мин. 1400×1400 пикс.",
"music": "music",
"my feed": "my ribbon",
"number list": "number list",
"or sign in with social networks": "or sign in with social networks",
"personal data usage and email notifications": "to process personal data and receive email notifications",
"post": "post",
"register": "register",
"repeat": "repeat",
"shout": "post",
"sign up or sign in": "sign up or sign in",
"slug is used by another user": "Slug is already taken by another user",
"subscriber": "subscriber",
"subscriber_rp": "subscriber",
"subscribers": "subscribers",
"subscription": "subscription",
"subscription_rp": "subscription",
"subscriptions": "subscriptions",
"terms of use": "terms of use",
"topics": "topics",
"user already exist": "user already exists",
"video": "video",
"view": "view"
}

View File

@ -1,10 +1,9 @@
{
"A guide to horizontal editorial: how an open journal works": "Гид по горизонтальной редакции: как работает открытый журнал",
"...subscribing": "...подписываем",
"A short introduction to keep the reader interested": "Добавьте вступление, чтобы заинтересовать читателя",
"About": "О себе",
"About the project": "О проекте",
"actions": "действия",
"Add": "Добавить",
"Accomplices": "Соучастники",
"Add a few topics so that the reader knows what your content is about and can find it on pages of topics that interest them. Topics can be swapped, the first topic becomes the title": "Добавьте несколько тем, чтобы читатель знал, о чем ваш материал, и мог найти его на страницах интересных ему тем. Темы можно менять местами, первая тема становится заглавной",
"Add a link or click plus to embed media": "Добавьте ссылку или нажмите плюс для вставки медиа",
"Add an embed widget": "Добавить embed-виджет",
@ -22,37 +21,30 @@
"Add subtitle": "Добавить подзаголовок",
"Add to bookmarks": "Добавить в закладки",
"Add url": "Добавить ссылку",
"Address on Discours": "Адрес на Дискурсе",
"Address on Discourse": "Адрес на Дискурсе",
"Album name": "Название альбома",
"Alignment center": "По центру",
"Alignment left": "По левому краю",
"Alignment right": "По правому краю",
"All": "Все",
"All articles": "Все материалы",
"All authors": "Все авторы",
"All posts": "Все публикации",
"All posts rating": "Рейтинг всех постов",
"All topics": "Все темы",
"Almost done! Check your email.": "Почти готово! Осталось подтвердить вашу почту.",
"and some more authors": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}",
"Are you sure you want to delete this comment?": "Уверены, что хотите удалить этот комментарий?",
"Are you sure you want to delete this draft?": "Уверены, что хотите удалить этот черновик?",
"Are you sure you want to to proceed the action?": "Вы уверены, что хотите продолжить?",
"Art": "Искусство",
"Article": "Статья",
"Artist": "Исполнитель",
"Artist...": "Исполнитель...",
"Artworks": "Артворки",
"Audio": "Аудио",
"Author": "Автор",
"author profile was not found": "не удалось найти профиль автора",
"Authors": "Авторы",
"Autotypograph": "Автотипограф",
"Back": "Назад",
"Back to editor": "Вернуться в редактор",
"Back to main page": "Вернуться на главную",
"back to menu": "назад в меню",
"Be the first to rate": "Оцените первым",
"Become an author": "Стать автором",
"Bold": "Жирный",
"Bookmarked": "Сохранено",
@ -70,120 +62,82 @@
"By title": "По названию",
"By updates": "По обновлениям",
"By views": "По просмотрам",
"Can make any changes, accept or reject suggestions, and share access with others": "Может вносить любые изменения, принимать и отклонять предложения, а также делиться доступом с другими",
"Can offer edits and comments, but cannot edit the post or share access with others": "Может предлагать правки и комментарии, но не может изменять пост и делиться доступом с другими",
"Can write and edit text directly, and accept or reject suggestions from others": "Может писать и редактировать текст напрямую, а также принимать или отклонять предложения других",
"Cancel": "Отмена",
"Cancel changes": "Отменить изменения",
"Change password": "Сменить пароль",
"Characters": "Знаков",
"Chat Title": "Тема дискурса",
"Choose a post type": "Выберите тип публикации",
"Choose a title image for the article. You can immediately see how the publication card will look like.": "Выберите заглавное изображение для статьи. Тут же сразу можно увидеть как будет выглядеть карточка публикации.",
"Choose who you want to write to": "Выберите кому хотите написать",
"Close": "Закрыть",
"Co-author": "Соавтор",
"Collaborate": "Помочь редактировать",
"Collaborators": "Соавторы",
"Collections": "Коллекции",
"Come up with a subtitle for your story": "Придумайте подзаголовок вашей истории",
"Come up with a title for your story": "Придумайте заголовок вашей истории",
"Coming soon": "Уже скоро",
"Comment": "Комментировать",
"Comment successfully deleted": "Комментарий успешно удален",
"Commentator": "Комментатор",
"Commented": "Комментируемое",
"Commenting": "Комментирование",
"Comments": "Комментарии",
"Communities": "Сообщества",
"community": "сообщество",
"Community Discussion Rules": "Правила дискуссий в сообществе",
"Community values and rules of engagement for the open editorial team": "Ценности сообщества и правила взаимодействия открытой редакции",
"Community Principles": "Принципы сообщества",
"Confirm": "Подтвердить",
"Confirm your email and the action will complete": "Подтвердите почту и действие совершится",
"Confirm your new password": "Подтвердите новый пароль",
"Connect": "Привязать",
"Contents": "Оглавление",
"Contribute to free samizdat. Support Discours - an independent non-profit publication that works only for you. Become a pillar of the open newsroom": "Внесите вклад в свободный самиздат. Поддержите Дискурс — независимое некоммерческое издание, которое работает только для вас. Станьте опорой открытой редакции",
"Cooperate": "Соучаствовать",
"Cooperate with Discours": "Сотрудничать с Дискурсом",
"Copy": "Скопировать",
"Copy link": "Скопировать ссылку",
"Corrections history": "История правок",
"Create Chat": "Создать чат",
"Create Group": "Создать группу",
"Create account": "Создать аккаунт",
"Create an account to add to your bookmarks": "Создайте аккаунт, чтобы добавить в закладки",
"Create an account to participate in discussions": "Создайте аккаунт для участия в дискуссиях",
"Create an account to publish articles": "Создайте аккаунт, чтобы публиковать статьи",
"Create an account to subscribe": "Создайте аккаунт, чтобы подписаться",
"Create an account to subscribe to new publications": "Создайте аккаунт для подписки на новые публикации",
"Create an account to vote": "Создайте аккаунт, чтобы голосовать",
"Create chat": "Создать чат",
"Create gallery": "Создать галерею",
"Create group": "Создать группу",
"Create post": "Создать публикацию",
"Create video": "Создать видео",
"Crop image": "Кадрировать изображение",
"Culture": "Культура",
"Current password": "Текущий пароль",
"Date of Birth": "Дата рождения",
"Decline": "Отмена",
"Delete": "Удалить",
"Delete cover": "Удалить обложку",
"Delete userpic": "Удалить аватар",
"delimiter": "разделитель",
"Description": "Описание",
"Discours": "Дискурс",
"Discours an open magazine about culture, science and society": "Дискурс открытый журнал о культуре, науке и обществе",
"Discours exists because of our common effort": "Дискурс существует благодаря нашему общему вкладу",
"Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects": "Дискурс&#160;&#8212; это интеллектуальная среда, веб-пространство и&#160;инструменты, которые позволяют авторам сотрудничать&#160;с&#160;читателями и&#160;объединяться для совместного создания публикаций и&#160;медиапроектов.<br/>Мы&#160;убеждены, один голос хорошо, а&#160;много&#160;&#8212; лучше. Самые потрясающиe истории мы создаём вместе.",
"Discours Manifest": "Манифест Дискурса",
"Discours Partners": "Партнеры Дискурса",
"Discours theme": "Тема дискурса",
"Discours is created with our common effort": "Дискурс существует благодаря нашему общему вкладу",
"Discussing": "Обсуждаемое",
"discussion": "дискурс",
"Discussion rules": "Правила дискуссий",
"Discussion rules": "Правила сообществ самиздата в&nbsp;соцсетях",
"Discussions": "Дискуссии",
"Do you really want to reset all changes?": "Вы действительно хотите сбросить все изменения?",
"Dogma": "Догма",
"dogma keywords": "Discours.io, догма, редакционные принципы, этический кодекс, журналистика, сообщество",
"Draft successfully deleted": "Черновик успешно удален",
"Drafts": "Черновики",
"Drag the image to this area": "Перетащите изображение в эту область",
"Each image must be no larger than 5 MB.": "Каждое изображение должно быть размером не больше 5 мб.",
"earlier": "ранее",
"Edit": "Редактировать",
"Edit profile": "Редактировать профиль",
"Edited": "Отредактирован",
"Editing": "Редактирование",
"Editor": "Редактор",
"Email": "Почта",
"email not confirmed": "email не подтвержден",
"Enter": "Войти",
"Enter a new password": "Введите новый пароль",
"Enter URL address": "Введите адрес ссылки",
"Enter footnote text": "Введите текст сноски",
"Enter image description": "Введите описание изображения",
"Enter image title": "Введите название изображения",
"Enter text": "Введите текст",
"Enter the code or click the link from email to confirm": "Введите код из письма или пройдите по ссылке в письме для подтверждения регистрации",
"Enter URL address": "Введите адрес ссылки",
"Enter your new password": "Введите новый пароль",
"Error": "Ошибка",
"Experience": "Личный опыт",
"Failed to delete comment": "Не удалось удалить комментарий",
"Everything is ok, please give us your email address": "Ничего страшного, просто укажите свою почту, чтобы получить ссылку для сброса пароля.",
"FAQ": "Советы и предложения",
"Favorite": "Избранное",
"Favorite topics": "Избранные темы",
"Feed": "Лента",
"Feed settings": "Настроить ленту",
"Feed settings": "Настройки ленты",
"Feedback": "Обратная связь",
"Fill email": "Введите почту",
"Fixed": "Все поправлено",
"Follow": "Подписаться",
"Follow the topic": "Подписаться на тему",
"follower": "подписчик",
"Followers": "Подписчики",
"Following": "Вы подписаны",
"Forgot password?": "Забыли пароль?",
"Forward": "Переслать",
"from": "от",
"Full name": "Имя и фамилия",
"Gallery": "Галерея",
"Gallery name": "Название галереи",
@ -201,8 +155,7 @@
"Help": "Помощь",
"Help to edit": "Помочь редактировать",
"Here you can customize your profile the way you want.": "Здесь можно настроить свой профиль так, как вы хотите.",
"Here you can manage all your Discours subscriptions": "Здесь можно управлять всеми своими подписками на Дискурсе",
"Here you can upload your photo": "Здесь вы можете загрузить свою фотографию",
"Here you can manage all your Discourse subscriptions": "Здесь можно управлять всеми своими подписками на Дискурсе",
"Hide table of contents": "Скрыть главление",
"Highlight": "Подсветка",
"Hooray! Welcome!": "Ура! Добро пожаловать!",
@ -210,76 +163,52 @@
"Hot topics": "Горячие темы",
"Hotkeys": "Горячие клавиши",
"How can I help/skills": "Чем могу помочь/навыки",
"How Discours works": "Как устроен Дискурс",
"How it works": "Как это работает",
"How to help": "Как помочь?",
"How to write a good article": "Как написать хорошую статью",
"How to write an article": "Как написать статью",
"Hundreds of people from different countries and cities share their knowledge and art on the Discours. Join us!": "Сотни людей из разных стран и городов делятся своими знаниями и искусством на Дискурсе. Присоединяйтесь!",
"I have an account": "У меня есть аккаунт!",
"I have no account yet": "У меня еще нет аккаунта",
"I know the password": "Я знаю пароль!",
"Image format not supported": "Тип изображения не поддерживается",
"Image": "Изображение",
"In bookmarks, you can save favorite discussions and materials that you want to return to": "В закладках можно сохранять избранные дискуссии и материалы, к которым хочется вернуться",
"Inbox": "Входящие",
"Incorrect new password confirm": "Неверное подтверждение нового пароля",
"Incorrect old password": "Старый пароль не верен",
"In&nbsp;bookmarks, you can save favorite discussions and&nbsp;materials that you want to return to": "В&nbsp;закладках можно сохранять избранные дискуссии и&nbsp;материалы, к&nbsp;которым хочется вернуться",
"Incut": "Подверстка",
"Independant magazine with an open horizontal cooperation about culture, science and society": "Независимый журнал с открытой горизонтальной редакцией о культуре, науке и обществе",
"Independent media project about culture, science, art and society with horizontal editing": "Независимый медиапроект о культуре, науке, искусстве и обществе с горизонтальной редакцией",
"Insert footnote": "Вставить сноску",
"Insert video link": "Вставить ссылку на видео",
"Interview": "Интервью",
"Introduce": "Представление",
"Invalid email": "Проверьте правильность ввода почты",
"Invalid image URL": "Некорректная ссылка на изображение",
"invalid password": "некорректный пароль",
"Invalid url format": "Неверный формат ссылки",
"Invite": "Пригласить",
"Invite co-authors": "Пригласить соавторов",
"Invite collaborators": "Пригласить соавторов",
"Invite experts": "Пригласить экспертов",
"Invite to collab": "Пригласить к участию",
"It does not look like url": "Это не похоже на ссылку",
"It's OK. Just enter your email to receive a link to change your password": "Ничего страшного. Просто укажите свою почту, чтобы получить ссылку для смены пароля",
"Italic": "Курсив",
"Join": "Присоединиться",
"Join our maillist": "Чтобы получать рассылку лучших публикаций, просто укажите свою почту",
"Join the community": "Присоединиться к сообществу",
"Join the global community of authors!": "Присоединятесь к глобальному сообществу авторов со всего мира!",
"Journal": "Журнал",
"jpg, .png, max. 10 mb.": "jpg, .png, макс. 10 мб.",
"Just start typing...": "Просто начните печатать...",
"Karma": "Карма",
"keywords": "Discours.io, журнал Дискурс, Дискурс, культура, наука, искусство, общество, независимая журналистика, литература, музыка, кино, видео, фотографии",
"Knowledge base": "База знаний",
"Language": "Язык",
"Last rev.": "Посл. изм.",
"Let's log in": "Давайте авторизуемся",
"Liked": "Популярное",
"Link copied": "Ссылка скопирована",
"Link copied to clipboard": "Ссылка скопирована в буфер обмена",
"Link sent, check your email": "Ссылка отправлена, проверьте почту",
"List of authors of the open editorial community": "Список авторов сообщества открытой редакции",
"Lists": "Списки",
"Literature": "Литература",
"Load more": "Показать ещё",
"loaded": "загружено",
"Loading": "Загрузка",
"Login and security": "Вход и безопасность",
"Logout": "Выход",
"Looks like you forgot to upload the video": "Похоже, что вы забыли загрузить видео",
"Manifest of samizdat: principles and mission of an open magazine with a horizontal editorial board": "Манифест самиздата: принципы и миссия открытого журнала с горизонтальной редакцией",
"Manifest": "Манифест",
"Manifesto": "Манифест",
"Many files, choose only one": "Много файлов, выберете один",
"Mark as read": "Отметить прочитанным",
"marker list": "маркир. список",
"Material card": "Карточка материала",
"Message": "Написать",
"Message text": "Текст сообщения",
"min. 1400×1400 pix": "мин. 1400×1400 пикс.",
"More": "Ещё",
"Most commented": "Комментируемое",
"Most read": "Читаемое",
"Move down": "Переместить вниз",
"Move up": "Переместить вверх",
@ -287,90 +216,77 @@
"My feed": "Моя лента",
"My subscriptions": "Подписки",
"Name": "Имя",
"New group": "Новая группа",
"New literary work": "Новое произведение",
"New message": "Новое сообщение",
"New only": "Только новые",
"New password": "Новый пароль",
"New stories and more are waiting for you every day!": "Каждый день вас ждут новые истории и ещё много всего интересного!",
"New stories every day and even more!": "Каждый день вас ждут новые истории и ещё много всего интересного!",
"NotificationNewCommentText1": "{commentsCount, plural, one {Новый комментарий} few {{commentsCount} новых комментария} other {{commentsCount} новых комментариев}} к вашей публикации",
"NotificationNewCommentText2": "от",
"NotificationNewCommentText3": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}",
"NotificationNewReplyText1": "{commentsCount, plural, one {Новый ответ} few {{commentsCount} новых ответа} other {{commentsCount} новых ответов}} на ваш комментарий к публикации",
"NotificationNewReplyText2": "от",
"NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}",
"Newsletter": "Рассылка",
"Night mode": "Ночная тема",
"No drafts": "Нет черновиков",
"No notifications yet": "Уведомлений пока нет",
"Write good articles, comment\nand it won't be so empty here": "Пишите хорошие статьи, комментируйте,\nи здесь станет не так пусто",
"No such account, please try to register": "Такой адрес не найден, попробуйте зарегистрироваться",
"not verified": "ещё не подтверждён",
"Nothing here yet": "Здесь пока ничего нет",
"Nothing is here": "Здесь ничего нет",
"Notifications": "Уведомления",
"number list": "нумер. список",
"or": "или",
"Or paste a link to an image": "Или вставьте ссылку на изображение",
"or sign in with social networks": "или войдите через соцсеть",
"Ordered list": "Нумерованный список",
"Our principles": "Принципы сообщества",
"Our regular contributor": "Наш постоянный автор",
"Paragraphs": "Абзацев",
"Participate in the Discours: share information, join the editorial team": "Participate in the Discours: share information, join the editorial team",
"Participating": "Участвовать",
"Participation": "Соучастие",
"Partners": "Партнёры",
"Password": "Пароль",
"Password again": "Пароль ещё раз",
"Password should be at least 8 characters": "Пароль должен быть не менее 8 символов",
"Password should contain at least one number": "Пароль должен содержать хотя бы одну цифру",
"Password should contain at least one special character: !@#$%^&*": "Пароль должен содержать хотя бы один спецсимвол: !@#$%^&*",
"Password updated!": "Пароль обновлен!",
"Passwords are not equal": "Пароли не совпадают",
"Paste Embed code": "Вставьте embed код",
"Personal": "Личные",
"Pin": "Закрепить",
"Platform Guide": "Гид по дискурсу",
"Please check your email address": "Пожалуйста, проверьте введенный адрес почты",
"Please check your inbox! We have sent a password reset link.": "Пожалуйста, проверьте свою почту, мы отправили вам письмо со ссылкой для сброса пароля",
"Please confirm email": "Пожалуйста, подтвердите электронную почту",
"Please confirm your email to finish": "Подтвердите почту и действие совершится",
"Please enter a name to sign your comments and publication": "Пожалуйста, введите имя, которое будет отображаться на сайте",
"Please enter email": "Пожалуйста, введите почту",
"Please enter password": "Пожалуйста, введите пароль",
"Please enter password again": "Пожалуйста, введите пароль ещё рез",
"Please, set the article title": "Пожалуйста, задайте заголовок статьи",
"Please, set the main topic first": "Пожалуйста, сначала выберите главную тему",
"Podcasts": "Подкасты",
"Poetry": "Поэзия",
"Please, confirm email": "Пожалуйста, подтвердите электронную почту",
"Popular": "Популярное",
"Popular authors": "Популярные авторы",
"post": "пост",
"Preview": "Предпросмотр",
"principles keywords": "Discours.io, сообщества, ценности, правила редакции, многоголосие, созидание",
"Professional principles that the open editorial team follows in its work": "Профессиональные принципы, которым открытая редакция следует в работе",
"Principles": "Принципы сообщества",
"Profile": "Профиль",
"Profile settings": "Настройки профиля",
"Profile successfully saved": "Профиль успешно сохранён",
"Publication settings": "Настройки публикации",
"Publications": "Публикации",
"PublicationsWithCount": "{count, plural, =0 {нет публикаций} one {{count} публикация} few {{count} публикации} other {{count} публикаций}}",
"Publish": "Опубликовать",
"Publish Album": "Опубликовать альбом",
"Publish Settings": "Настройки публикации",
"Published": "Опубликованные",
"Punchline": "Панчлайн",
"Quit": "Выйти",
"Quote": "Цитата",
"Quotes": "Цитаты",
"Reason unknown": "Причина неизвестна",
"Reason uknown": "Причина неизвестна",
"Recent": "Свежее",
"register": "зарегистрируйтесь",
"registered": "уже зарегистрирован",
"Registered since {date}": "На сайте c {date}",
"Release date...": "Дата выхода...",
"Remove link": "Убрать ссылку",
"Repeat": "Повторить",
"Repeat new password": "Повторите новый пароль",
"Reply": "Ответить",
"Report": "Пожаловаться",
"Reports": "Репортажи",
"Required": "Поле обязательно для заполнения",
"Resend code": "Выслать подтверждение",
"resend confirmation link": "отправить ссылку ещё раз",
"Restore password": "Восстановить пароль",
"Rules of the journal Discours": "Правила журнала Дискурс",
"Save": "Сохранить",
"Save draft": "Сохранить черновик",
"Save settings": "Сохранить настройки",
@ -382,135 +298,79 @@
"Sections": "Разделы",
"Security": "Безопасность",
"Select": "Выбрать",
"Self-publishing exists thanks to the help of wonderful people from all over the world. Thank you!": "Самиздат существуют благодаря помощи замечательных людей со всего мира. Спасибо Вам!",
"Send": "Отправить",
"Send link again": "Прислать ссылку ещё раз",
"Settings": "Настройки",
"Settings for account, email, password and login methods.": "Настройки аккаунта, почты, пароля и способов входа.",
"Share": "Поделиться",
"Share publication": "Поделиться публикацией",
"Short opening": "Расскажите вашу историю...",
"shout": "пост",
"shout not found": "публикация не найдена",
"Show": "Показать",
"Show lyrics": "Текст песни",
"Show more": "Читать дальше",
"Show table of contents": "Показать главление",
"sign in": "войти",
"sign up": "зарегистрироваться",
"Sign up": "Создать аккаунт",
"sign up or sign in": "зарегистрироваться или войти",
"Site search": "Поиск по сайту",
"Slug": "Постоянная ссылка",
"slug is used by another user": "Имя уже занято другим пользователем",
"Social networks": "Социальные сети",
"Society": "Общество",
"some authors": "{count} {count, plural, one {автор} few {автора} other {авторов}}",
"some comments": "{count, plural, =0 {{count} комментариев} one {{count} комментарий} few {{count} комментария} other {{count} комментариев}}",
"some followers": "{count} {count, plural, one {подписчик} few {подписчика} other {подписчиков}}",
"some followings": "{count, plural, =0 {нет подписок} one {{count} подписка} few {{count} подписки} other {{count} подписок}}",
"Some new comments to your publication": "{commentsCount, plural, one {Новый комментарий} few {{commentsCount} новых комментария} other {{commentsCount} новых комментариев}} к вашей публикации",
"Some new replies to your comment": "{commentsCount, plural, one {Новый ответ} few {{commentsCount} новых ответа} other {{commentsCount} новых ответов}} на ваш комментарий к публикации",
"some posts": "{count, plural, =0 {нет публикаций} one {{count} публикация} few {{count} публикации} other {{count} публикаций}}",
"some shouts": "{count} {count, plural, one {публикация} few {публикации} other {публикаций}}",
"some views": "{count} {count, plural, one {просмотр} few {просмотрa} other {просмотров}}",
"Something went wrong, check email and password": "Что-то пошло не так. Проверьте адрес электронной почты и пароль",
"Something went wrong, please try again": "Что-то пошло не так, попробуйте еще раз",
"Song lyrics": "Текст песни...",
"Song title": "Название песни",
"Soon": "Скоро",
"Sorry, this address is already taken, please choose another one.": "Увы, этот адрес уже занят, выберите другой",
"Special projects": "Спецпроекты",
"Specify the source and the name of the author": "Укажите источник и имя автора",
"squib": "Подверстка",
"Start conversation": "Начать беседу",
"Start dialog": "Начать диалог",
"Subheader": "Подзаголовок",
"Subscribe": "Подписаться",
"Subscribe to comments": "Подписаться на комментарии",
"Subscribe to the best publications newsletter": "Подпишитесь на рассылку лучших публикаций",
"Subscribe us": "Подпишитесь на нас",
"Subscribe us": "Подпишитесь на&nbsp;нас",
"Subscribe what you like to tune your personal feed": "Подпишитесь на интересующие вас темы, чтобы настроить вашу персональную ленту и моментально узнавать о новых публикациях и обсуждениях",
"Subscribe who you like to tune your personal feed": "Подпишитесь на интересующих вас авторов, чтобы настроить вашу персональную ленту и моментально узнавать о новых публикациях и обсуждениях",
"subscriber": "подписчик",
"subscribers": "подписчиков",
"Subscribing...": "Подписываем...",
"SubscriberWithCount": "{count, plural, =0 {нет подписчиков} one {{count} подписчик} few {{count} подписчика} other {{count} подписчиков}}",
"Subscription": "Подписка",
"SubscriptionWithCount": "{count, plural, =0 {нет подписок} one {{count} подписка} few {{count} подписки} other {{count} подписок}}",
"Subscriptions": "Подписки",
"Substrate": "Подложка",
"Success": "Успешно",
"Successfully authorized": "Авторизация успешна",
"Suggest an idea": "Предложить идею",
"Support Discours": "Поддержите Дискурс",
"Support the project": "Поддержать проект",
"Support us": "Помочь журналу",
"terms of use": "правилами пользования сайтом",
"Terms of use": "Правила сайта",
"Text checking": "Проверка текста",
"Thank you": "Благодарности",
"Thank you!": "Спасибо Вам!",
"The address is already taken": "Адрес уже занят",
"The most interesting publications on the topic": "Самые интересные публикации по теме {topicName}",
"Thematic table of contents of the magazine. Here you can find all the topics that community authors have written about.": "Тематическое оглавление журнала. Здесь можно найти все темы, о которых писали авторы сообщества.",
"Thematic table of contents of the magazine. Here you can find all the topics that the community authors wrote about": "Тематическое оглавление журнала. Здесь можно найти все темы, о которых писали авторы сообщества",
"Themes and plots": "Темы и сюжеты",
"Theory": "Теории",
"There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?": "В настройках вашего профиля есть несохраненные изменения. Уверены, что хотите покинуть страницу без сохранения?",
"There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?": "В настройках публикации есть несохраненные изменения. Уверены, что хотите покинуть страницу без сохранения?",
"This comment has not yet been rated": "Этот комментарий еще пока никто не оценил",
"This content is not published yet": "Содержимое ещё не опубликовано",
"This email is": "Этот email",
"This email is not verified": "Этот email не подтвержден",
"This email is registered": "Этот email уже зарегистрирован",
"This email is verified": "Этот email подтвержден",
"This email is already taken. If it's you": "Такой email уже зарегистрирован. Если это вы",
"This functionality is currently not available, we would like to work on this issue. Use the download link.": "В данный момент этот функционал не доступен, бы работаем над этой проблемой. Воспользуйтесь загрузкой по ссылке.",
"This month": "За месяц",
"This post has not been rated yet": "Эту публикацию еще пока никто не оценил",
"This way we ll realize that you re a real person and ll take your vote into account. And you ll see how others voted": "Так мы поймем, что вы реальный человек, и учтем ваш голос. А вы увидите, как проголосовали другие",
"This way you ll be able to subscribe to authors, interesting topics and customize your feed": "Так вы сможете подписаться на авторов, интересные темы и настроить свою ленту",
"This week": "За неделю",
"This year": "За год",
"To find publications, art, comments, authors and topics of interest to you, just start typing your query": "Для поиска публикаций, искусства, комментариев, интересных вам авторов и тем, просто начните вводить ваш запрос",
"This way we&nbsp;ll realize that you&nbsp;re a real person and&nbsp;ll take your vote into account. And&nbsp;you&nbsp;ll see how others voted": "Так мы&nbsp;поймем, что вы&nbsp;реальный человек, и&nbsp;учтем ваш голос. А&nbsp;вы&nbsp;увидите, как проголосовали другие",
"This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed": "Так вы&nbsp;сможете подписаться на&nbsp;авторов, интересные темы и&nbsp;настроить свою ленту",
"To leave a comment please": "Чтобы оставить комментарий, необходимо",
"to process personal data and receive email notifications": "на обработку персональных данных и на получение почтовых уведомлений",
"To write a comment, you must": "Чтобы написать комментарий, необходимо",
"today": "сегодня",
"Top authors": "Рейтинг авторов",
"Top commented": "Самое комментируемое",
"Top discussed": "Обсуждаемое",
"Top month": "Лучшее за месяц",
"Top month articles": "Лучшие материалы месяца",
"Top rated": "Популярное",
"Top recent": "Самое новое",
"Top topics": "Интересные темы",
"Top viewed": "Самое читаемое",
"Topic is supported by": "Тему поддерживают",
"topicKeywords": "{topic}, Discours.io, статьи, журналистика, исследования",
"Topics": "Темы",
"Topics which supported by author": "Автор поддерживает темы",
"try": "попробуйте",
"Try to find another way": "Попробуйте найти по-другому",
"Unfollow": "Отписаться",
"Unfollow the topic": "Отписаться от темы",
"Unnamed draft": "Черновик без названия",
"UnSubscribing...": "Отписываем...",
"Upload": "Загрузить",
"Upload error": "Ошибка загрузки",
"Upload userpic": "Загрузить аватар",
"Upload video": "Загрузить видео",
"Uploading image": "Загружаем изображение",
"user already exist": "пользователь уже существует",
"User was not found": "Пользователь не найден",
"Username": "Имя пользователя",
"Userpic": "Аватар",
"Users": "Пользователи",
"verified": "уже подтверждён",
"Video": "Видео",
"Video format not supported": "Тип видео не поддерживается",
"view": "просмотр",
"Views": "Просмотры",
"We are working on collaborative editing of articles and in the near future you will have an amazing opportunity - to create together with your colleagues": "Мы работаем над коллаборативным редактированием статей и в ближайшем времени у вас появиться удивительная возможность - творить вместе с коллегами",
"We can't find you, check email or": "Не можем вас найти, проверьте адрес электронной почты или",
"We couldn't find anything for your request": "Мы не смогли ничего найти по вашему запросу",
"We know you, please try to login": "Такой адрес почты уже зарегистрирован, попробуйте залогиниться",
"We've sent you a message with a link to enter our website.": "Мы выслали вам письмо с ссылкой на почту. Перейдите по ссылке в письме, чтобы войти на сайт.",
"Welcome to Discours": "Добро пожаловать в Дискурс",
@ -522,27 +382,79 @@
"Welcome to Discours to vote": "Войдите в Дискурс, чтобы голосовать",
"Welcome!": "Добро пожаловать!",
"Where": "Откуда",
"Why you can earn a hole in your karma and how to receive rays of gratitude for your contribution to discussions in samizdat communities": "За что можно заслужить дырку в карме и как получить лучи благодарности за вклад в дискуссии в сообществах самиздата",
"Words": "Слов",
"Work with us": "Сотрудничать с Дискурсом",
"Write a comment...": "Написать комментарий...",
"Write a short introduction": "Напишите краткое вступление",
"Write about the topic": "Написать в тему",
"Write an article": "Написать статью",
"Write comment": "Написать комментарий",
"Write good articles, comment\nand it won't be so empty here": "Пишите хорошие статьи, комментируйте,\nи здесь станет не так пусто",
"Write message": "Написать сообщение",
"Write to us": "Напишите нам",
"Write your colleagues name or email": "Напишите имя или e-mail коллеги",
"yesterday": "вчера",
"You can": "Вы можете",
"You can download multiple tracks at once in .mp3, .wav or .flac formats": "Можно загрузить сразу несколько треков в форматах .mp3, .wav или .flac",
"You can now login using your new password": "Теперь вы можете входить с помощью нового пароля",
"You can't edit this post": "Вы не можете редактировать этот материал",
"You ll be able to participate in discussions, rate others' comments and learn about new responses": "Вы сможете участвовать в обсуждениях, оценивать комментарии других и узнавать о новых ответах",
"You was successfully authorized": "Вы были успешно авторизованы",
"You&nbsp;ll be able to participate in&nbsp;discussions, rate others' comments and&nbsp;learn about&nbsp;new responses": "Вы&nbsp;сможете участвовать в&nbsp;обсуждениях, оценивать комментарии других и&nbsp;узнавать о&nbsp;новых ответах",
"You've confirmed email": "Вы подтвердили почту",
"You've reached a non-existed page": "Вы попали на несуществующую страницу",
"You've successfully logged out": "Вы успешно вышли из аккаунта",
"Your contact for answer": "Ваш контакт для ответа",
"Your email": "Ваш email",
"Your name will appear on your profile page and as your signature in publications, comments and responses.": "Ваше имя появится на странице вашего профиля и как ваша подпись в публикациях, комментариях и откликах"
"Your name will appear on your profile page and as your signature in publications, comments and responses.": "Ваше имя появится на странице вашего профиля и как ваша подпись в публикациях, комментариях и откликах",
"actions": "действия",
"add link": "добавить ссылку",
"all topics": "все темы",
"article": "статья",
"author": "автор",
"authors": "авторы",
"back to menu": "назад в меню",
"bold": "жирный",
"bookmarks": "закладки",
"cancel": "отменить",
"collections": "коллекции",
"community": "сообщество",
"contents": "оглавление",
"create_chat": "Создать чат",
"create_group": "Создать группу",
"delimiter": "разделитель",
"discourse_theme": "Тема дискурса",
"discussion": "дискурс",
"drafts": "черновики",
"email not confirmed": "email не подтвержден",
"enter": "войдите",
"feed": "лента",
"follower": "подписчик",
"general feed": "Общая лента",
"header 1": "заголовок 1",
"header 2": "заголовок 2",
"header 3": "заголовок 3",
"images": "изображения",
"invalid password": "некорректный пароль",
"italic": "курсив",
"journal": "журнал",
"jpg, .png, max. 10 mb.": "jpg, .png, макс. 10 мб.",
"literature": "литература",
"marker list": "маркир. список",
"min. 1400×1400 pix": "мин. 1400×1400 пикс.",
"music": "музыка",
"my feed": "моя лента",
"number list": "нумер. список",
"or": "или",
"or sign in with social networks": "или войдите через соцсеть",
"personal data usage and email notifications": "на обработку персональных данных и на получение почтовых уведомлений",
"post": "пост",
"register": "зарегистрируйтесь",
"repeat": "повторить",
"shout": "пост",
"sign in": "войти",
"sign up": "зарегистрироваться",
"sign up or sign in": "зарегистрироваться или войти",
"slug is used by another user": "Имя уже занято другим пользователем",
"squib": "Подверстка",
"subscriber": "подписчик",
"subscriber_rp": "подписчика",
"subscribers": "подписчиков",
"terms of use": "правилами пользования сайтом",
"topics": "темы",
"user already exist": "пользователь уже существует",
"video": "видео",
"view": "просмотр"
}

18
public/manifest.json Normal file
View File

@ -0,0 +1,18 @@
{
"theme_color": "#111111",
"background_color": "#ffffff",
"display": "standalone",
"scope": "/",
"start_url": "/",
"name": "discours.io",
"short_name": "discours.io",
"description": "Дискурс - коллаборативная журналистика",
"icons": [
{
"src": "/favicon.png",
"sizes": "200x200",
"type": "image/png",
"purpose": "any maskable"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

View File

@ -1,51 +0,0 @@
import { Meta, MetaProvider } from '@solidjs/meta'
import { Router } from '@solidjs/router'
import { FileRoutes } from '@solidjs/start/router'
import { type JSX, Suspense } from 'solid-js'
import { AuthToken } from '@authorizerdev/authorizer-js'
import { Loading } from './components/_shared/Loading'
import { AuthorsProvider } from './context/authors'
import { EditorProvider } from './context/editor'
import { FeedProvider } from './context/feed'
import { LocalizeProvider } from './context/localize'
import { SessionProvider } from './context/session'
import { TopicsProvider } from './context/topics'
import { UIProvider } from './context/ui'
import '~/styles/app.scss'
export const Providers = (props: { children?: JSX.Element }) => {
const sessionStateChanged = (payload: AuthToken) => {
console.debug(payload)
// TODO: maybe load subs here
}
return (
<LocalizeProvider>
<SessionProvider onStateChangeCallback={sessionStateChanged}>
<TopicsProvider>
<FeedProvider>
<MetaProvider>
<Meta name="viewport" content="width=device-width, initial-scale=1" />
<UIProvider>
<EditorProvider>
<AuthorsProvider>
<Suspense fallback={<Loading />}>{props.children}</Suspense>
</AuthorsProvider>
</EditorProvider>
</UIProvider>
</MetaProvider>
</FeedProvider>
</TopicsProvider>
</SessionProvider>
</LocalizeProvider>
)
}
export const App = () => (
<Router root={Providers}>
<FileRoutes />
</Router>
)
export default App

View File

Before

Width:  |  Height:  |  Size: 245 B

After

Width:  |  Height:  |  Size: 245 B

127
src/components/App.tsx Normal file
View File

@ -0,0 +1,127 @@
// FIXME: breaks on vercel, research
// import 'solid-devtools'
import { hideModal, MODALS, showModal } from '../stores/ui'
import { Component, createEffect, createMemo } from 'solid-js'
import { ROUTES, useRouter } from '../stores/router'
import { Dynamic } from 'solid-js/web'
import type { PageProps, RootSearchParams } from '../pages/types'
import { HomePage } from '../pages/index.page'
import { AllTopicsPage } from '../pages/allTopics.page'
import { TopicPage } from '../pages/topic.page'
import { AllAuthorsPage } from '../pages/allAuthors.page'
import { AuthorPage } from '../pages/author.page'
import { FeedPage } from '../pages/feed.page'
import { ArticlePage } from '../pages/article.page'
import { SearchPage } from '../pages/search.page'
import { FourOuFourPage } from '../pages/fourOuFour.page'
import { DiscussionRulesPage } from '../pages/about/discussionRules.page'
import { DogmaPage } from '../pages/about/dogma.page'
import { GuidePage } from '../pages/about/guide.page'
import { HelpPage } from '../pages/about/help.page'
import { ManifestPage } from '../pages/about/manifest.page'
import { PartnersPage } from '../pages/about/partners.page'
import { PrinciplesPage } from '../pages/about/principles.page'
import { ProjectsPage } from '../pages/about/projects.page'
import { TermsOfUsePage } from '../pages/about/termsOfUse.page'
import { ThanksPage } from '../pages/about/thanks.page'
import { CreatePage } from '../pages/create.page'
import { EditPage } from '../pages/edit.page'
import { ConnectPage } from '../pages/connect.page'
import { InboxPage } from '../pages/inbox.page'
import { ExpoPage } from '../pages/expo/expo.page'
import { SessionProvider } from '../context/session'
import { ProfileSettingsPage } from '../pages/profile/profileSettings.page'
import { ProfileSecurityPage } from '../pages/profile/profileSecurity.page'
import { ProfileSubscriptionsPage } from '../pages/profile/profileSubscriptions.page'
import { DraftsPage } from '../pages/drafts.page'
import { SnackbarProvider } from '../context/snackbar'
import { LocalizeProvider } from '../context/localize'
import { ConfirmProvider } from '../context/confirm'
import { EditorProvider } from '../context/editor'
import { NotificationsProvider } from '../context/notifications'
// TODO: lazy load
// const SomePage = lazy(() => import('./Pages/SomePage'))
const pagesMap: Record<keyof typeof ROUTES, Component<PageProps>> = {
author: AuthorPage,
authorComments: AuthorPage,
authorAbout: AuthorPage,
inbox: InboxPage,
expo: ExpoPage,
expoLayout: ExpoPage,
connect: ConnectPage,
create: CreatePage,
edit: EditPage,
editSettings: EditPage,
drafts: DraftsPage,
home: HomePage,
topics: AllTopicsPage,
topic: TopicPage,
authors: AllAuthorsPage,
feed: FeedPage,
feedMy: FeedPage,
feedNotifications: FeedPage,
feedBookmarks: FeedPage,
feedCollaborations: FeedPage,
feedDiscussions: FeedPage,
article: ArticlePage,
search: SearchPage,
discussionRules: DiscussionRulesPage,
dogma: DogmaPage,
guide: GuidePage,
help: HelpPage,
manifest: ManifestPage,
projects: ProjectsPage,
partners: PartnersPage,
principles: PrinciplesPage,
termsOfUse: TermsOfUsePage,
thanks: ThanksPage,
profileSettings: ProfileSettingsPage,
profileSecurity: ProfileSecurityPage,
profileSubscriptions: ProfileSubscriptionsPage,
fourOuFour: FourOuFourPage
}
export const App = (props: PageProps) => {
const { page, searchParams } = useRouter<RootSearchParams>()
createEffect(() => {
if (!searchParams().modal) {
hideModal()
}
const modal = MODALS[searchParams().modal]
if (modal) {
showModal(modal)
}
})
const pageComponent = createMemo(() => {
const result = pagesMap[page()?.route || 'home']
if (!result || page()?.path === '/404') {
return FourOuFourPage
}
return result
})
return (
<LocalizeProvider>
<SnackbarProvider>
<ConfirmProvider>
<SessionProvider>
<NotificationsProvider>
<EditorProvider>
<Dynamic component={pageComponent()} {...props} />
</EditorProvider>
</NotificationsProvider>
</SessionProvider>
</ConfirmProvider>
</SnackbarProvider>
</LocalizeProvider>
)
}

View File

@ -1,13 +1,11 @@
h1 {
@include font-size(4rem);
line-height: 1.1;
margin-top: 0.5em;
}
h2 {
@include font-size(4rem);
line-height: 1.1;
}
@ -16,44 +14,30 @@ img {
}
.shoutHeader {
margin-bottom: 1.5em;
margin-bottom: 2em;
@include media-breakpoint-up(md) {
margin: 0 0 2em;
}
}
.articleContent {
img:not([data-disable-lightbox='true']) {
cursor: zoom-in;
width: 100%;
}
.shoutCover {
background-size: cover;
height: 0;
padding-bottom: 56.2%;
}
.shoutBody {
@include media-breakpoint-up(sm) {
:global(.width-30) {
width: 30%;
}
:global(.width-50) {
width: 50%;
}
}
font-size: 1.6rem;
line-height: 1.6;
details {
text-align: center;
background-color: lightgray;
line-height: 3em;
}
details p {
text-align: left;
line-height: normal;
img {
display: block;
margin-bottom: 0.5em;
}
blockquote,
blockquote[data-type='punchline'] {
clear: both;
font-size: 2.6rem;
font-weight: bold;
line-height: 1.4;
@ -75,19 +59,8 @@ img {
blockquote[data-type='quote'],
ta-quotation {
@include media-breakpoint-up(sm) {
&[data-float='left'] {
margin-right: 1.5em;
}
&[data-float='right'] {
margin-left: 1.5em;
}
}
border: solid #000;
border-width: 0 0 0 2px;
clear: both;
display: block;
font-weight: 500;
line-height: 1.6;
@ -97,12 +70,17 @@ img {
&[data-float='left'],
&[data-float='right'] {
@include font-size(2.2rem);
line-height: 1.4;
}
@include media-breakpoint-up(sm) {
clear: none;
@include media-breakpoint-up(sm) {
&[data-float='left'] {
margin-right: 1.5em;
}
line-height: 1.4;
&[data-float='right'] {
margin-left: 1.5em;
}
}
&::before {
@ -114,28 +92,22 @@ img {
ta-sub,
ta-selection-frame,
ta-border-sub {
background: #f1f2f3;
display: block;
@include font-size(1.4rem);
margin: 3.2rem 0;
padding: 3.2rem;
@include media-breakpoint-up(md) {
margin: 3.2rem -8.3333%;
padding: 3.2rem 8.3333%;
}
background: #f1f2f3;
clear: both;
display: block;
margin: 3.2rem 0;
padding: 3.2rem;
p:last-child {
margin-bottom: 0;
}
}
ta-sub {
font-size: inherit;
}
*[data-bg='black'] {
background: #000;
color: #fff;
@ -201,7 +173,15 @@ img {
:global(.img-align-left) {
float: left;
margin: 0 8.3333% 1.5em 0;
margin: 1em 8.3333% 1.5em 0;
}
:global(.width-30) {
width: 30%;
}
:global(.width-50) {
width: 50%;
}
:global(.img-align-left.width-50) {
@ -211,15 +191,13 @@ img {
}
:global(.img-align-right) {
@include media-breakpoint-up(sm) {
float: right;
margin: 1em 0 1.5em 8.3333%;
}
float: right;
margin: 1em 0 1.5em 8.3333%;
}
:global(.img-align-right.width-50) {
@include media-breakpoint-up(xl) {
margin-right: -8.3333%;
margin-right: -16.6666%;
}
}
@ -262,6 +240,7 @@ img {
.shoutAuthorsList {
border-bottom: 1px solid #e8e8e8;
margin: 2em 0;
padding-bottom: 2em;
h4 {
color: #696969;
@ -313,31 +292,29 @@ img {
}
.shoutStats {
@include media-breakpoint-down(lg) {
flex-wrap: wrap;
}
border-top: 4px solid #000;
display: flex;
justify-content: flex-start;
padding: 3rem 0 0;
position: relative;
@include media-breakpoint-down(sm) {
flex-wrap: wrap;
}
}
.shoutStatsItem {
@include font-size(1.5rem);
@include media-breakpoint-up(xl) {
margin-right: 3.2rem;
}
align-items: center;
font-weight: 500;
display: flex;
margin: 0 2rem 1em 0;
margin: 0 6% 1em 0;
vertical-align: baseline;
cursor: pointer;
@include media-breakpoint-up(sm) {
margin-right: 3.2rem;
}
.icon {
display: inline-block;
margin-right: 0.2em;
@ -378,14 +355,6 @@ img {
}
}
.shoutStatsItemBookmarks {
@include media-breakpoint-up(lg) {
margin-left: 0;
}
margin-left: auto;
}
.shoutStatsItemInner {
cursor: pointer;
@ -408,87 +377,45 @@ img {
}
.shoutStatsItemAdditionalData {
@include media-breakpoint-down(lg) {
flex: 1 100%;
order: 9;
.shoutStatsItemAdditionalDataItem {
margin-left: 0;
}
}
color: rgb(0 0 0 / 40%);
cursor: default;
font-weight: normal;
justify-self: flex-end;
margin-right: 0;
margin-left: auto;
white-space: nowrap;
cursor: default;
.icon {
opacity: 0.4;
height: 2rem;
}
@include media-breakpoint-down(sm) {
flex: 1 100%;
}
}
.shoutStatsItemViews {
@include media-breakpoint-down(lg) {
bottom: 0;
flex: 1 40%;
justify-content: end;
margin-right: 0;
order: 10;
position: absolute;
right: 0;
.icon {
display: none !important;
}
}
color: rgb(0 0 0 / 40%);
cursor: default;
font-weight: normal;
margin-left: auto;
white-space: nowrap;
}
.shoutStatsItemLabel {
font-weight: normal;
margin-left: 0.3em;
}
.commentsTextLabel {
@include media-breakpoint-up(sm) {
display: block;
}
display: none;
}
.shoutStatsItemCount {
@include media-breakpoint-down(lg) {
display: none;
}
}
.shoutStatsItemAdditionalDataItem {
font-weight: normal;
display: inline-block;
margin-left: 2rem;
margin-right: 0;
margin-bottom: 0;
cursor: default;
@include media-breakpoint-down(sm) {
&:first-child {
margin-left: 0;
}
}
font-weight: normal;
display: inline-block;
// margin-left: 2rem;
margin-right: 0;
margin-bottom: 0;
cursor: default;
}
.topicsList {
@include font-size(1.2rem);
border-bottom: 1px solid #e8e8e8;
letter-spacing: 0.08em;
margin-top: 1.6rem;
@ -528,15 +455,12 @@ img {
}
.commentsHeaderWrapper {
@include media-breakpoint-up(sm) {
display: flex;
justify-content: space-between;
}
display: flex;
justify-content: space-between;
}
.commentsHeader {
@include font-size(2.4rem);
margin-bottom: 1em;
.newReactions {
@ -570,7 +494,6 @@ img {
button {
@include font-size(1.5rem);
border-radius: 0.8rem;
margin-right: 1.2rem;
padding: 0.9rem 1.2rem;
@ -652,14 +575,13 @@ a[data-toggle='tooltip'] {
width: 0;
height: 0;
border-style: solid;
border-width: 4px 4px 0;
border-width: 4px 4px 0 4px;
border-color: var(--black-500) transparent transparent transparent;
}
}
.lead {
@include font-size(1.8rem);
font-weight: 600;
b,
@ -667,19 +589,3 @@ a[data-toggle='tooltip'] {
font-weight: 700;
}
}
.articlePopupOpener {
.iconHover {
display: none;
}
&:hover {
.icon {
display: none;
}
.iconHover {
display: inline-block;
}
}
}

View File

@ -36,7 +36,7 @@
width: 200px;
height: 200px;
transition: all 0.2s ease-in-out;
background: var(--placeholder-color-semi) url('/icons/create-audio.svg') no-repeat 50% 50%;
background: var(--placeholder-color-semi) url('/icons/create-music.svg') no-repeat 50% 50%;
.image {
object-fit: cover;

View File

@ -1,13 +1,11 @@
import { clsx } from 'clsx'
import { Show, createSignal } from 'solid-js'
import { Icon } from '~/components/_shared/Icon'
import { Image } from '~/components/_shared/Image'
import { Topic } from '~/graphql/schema/core.gen'
import { MediaItem } from '~/types/mediaitem'
import { CardTopic } from '../../Feed/CardTopic'
import styles from './AudioHeader.module.scss'
import { imageProxy } from '../../../utils/imageProxy'
import { MediaItem } from '../../../pages/types'
import { createSignal, Show } from 'solid-js'
import { Icon } from '../../_shared/Icon'
import { Topic } from '../../../graphql/types.gen'
import { CardTopic } from '../../Feed/CardTopic'
type Props = {
title: string
@ -21,7 +19,7 @@ export const AudioHeader = (props: Props) => {
return (
<div class={clsx(styles.AudioHeader, { [styles.expandedImage]: expandedImage() })}>
<div class={styles.cover}>
<Image class={styles.image} src={props.cover} alt={props.title} width={100} />
<img class={styles.image} src={imageProxy(props.cover)} alt={props.title} />
<Show when={props.cover}>
<button type="button" class={styles.expand} onClick={() => setExpandedImage(!expandedImage())}>
<Icon name="expand-circle" />
@ -30,19 +28,19 @@ export const AudioHeader = (props: Props) => {
</div>
<div class={styles.albumInfo}>
<Show when={props.topic}>
<CardTopic title={props.topic.title || ''} slug={props.topic.slug} />
<CardTopic title={props.topic.title} slug={props.topic.slug} />
</Show>
<h1>{props.title}</h1>
<Show when={props.artistData}>
<div class={styles.artistData}>
<Show when={props.artistData?.artist}>
<div class={styles.item}>{props.artistData?.artist || ''}</div>
<div class={styles.item}>{props.artistData.artist}</div>
</Show>
<Show when={props.artistData?.date}>
<div class={styles.item}>{props.artistData?.date || ''}</div>
<div class={styles.item}>{props.artistData.date}</div>
</Show>
<Show when={props.artistData?.genre}>
<div class={styles.item}>{props.artistData?.genre || ''}</div>
<div class={styles.item}>{props.artistData.genre}</div>
</Show>
</div>
</Show>

View File

@ -3,32 +3,27 @@
}
.playerHeader {
@include media-breakpoint-down(sm) {
flex-direction: column;
}
width: 100%;
display: flex;
justify-content: space-between;
@include media-breakpoint-down(sm) {
flex-direction: column;
}
}
.playerTitle {
@include media-breakpoint-down(sm) {
max-width: 100%;
}
max-width: 50%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@include media-breakpoint-down(sm) {
max-width: 100%;
}
}
.playerControls {
@include media-breakpoint-down(sm) {
margin-top: 20px;
margin-left: 0;
}
display: flex;
min-width: 160px;
align-items: center;
@ -47,10 +42,16 @@
}
}
@include media-breakpoint-down(sm) {
margin-top: 20px;
margin-left: 0;
}
.playButton {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
background: #141414;
@ -107,7 +108,7 @@
position: relative;
width: 100%;
cursor: pointer;
border-bottom: 2px solid #ccc;
border-bottom: 2px solid #cccccc;
}
.progressFilled {
@ -125,6 +126,7 @@
position: absolute;
bottom: -10px;
right: -8px;
width: 8px;
height: 8px;
border-radius: 50%;
@ -138,6 +140,7 @@
padding-top: 14px;
display: flex;
justify-content: space-between;
font-weight: 500;
font-size: 12px;
line-height: 16px;
@ -154,7 +157,7 @@ $vendors-track: ('::-webkit-slider-runnable-track', '::-moz-range-track', '::-ms
$vendors-thumb: ('::-webkit-slider-thumb', '::-moz-moz-range-thumb', '::-ms-thumb');
.volume {
appearance: none;
-webkit-appearance: none;
height: 19px;
float: left;
outline: none;
@ -179,7 +182,7 @@ $vendors-thumb: ('::-webkit-slider-thumb', '::-moz-moz-range-thumb', '::-ms-thum
@each $vendor in $vendors-thumb {
&#{$vendor} {
position: relative;
appearance: none;
-webkit-appearance: none;
box-sizing: content-box;
width: 8px;
height: 8px;
@ -187,7 +190,7 @@ $vendors-thumb: ('::-webkit-slider-thumb', '::-moz-moz-range-thumb', '::-ms-thum
border: 4px solid var(--default-color);
background-color: var(--background-color);
cursor: pointer;
margin: -7px 0 0;
margin: -7px 0 0 0;
}
&:active#{$vendor} {
transform: scale(1.2);
@ -198,7 +201,6 @@ $vendors-thumb: ('::-webkit-slider-thumb', '::-moz-moz-range-thumb', '::-ms-thum
&::-moz-range-progress {
background-color: var(--background-color);
}
&::-moz-focus-outer {
border: 0;
}
@ -207,6 +209,7 @@ $vendors-thumb: ('::-webkit-slider-thumb', '::-moz-moz-range-thumb', '::-ms-thum
.playlist {
display: flex;
flex-direction: column;
list-style-type: none;
margin: 32px 0 16px;
padding: 0;
@ -219,6 +222,7 @@ $vendors-thumb: ('::-webkit-slider-thumb', '::-moz-moz-range-thumb', '::-ms-thum
.playlistItem {
display: flex;
align-items: center;
min-height: 56px;
padding: 16px 0;
}
@ -252,10 +256,10 @@ $vendors-thumb: ('::-webkit-slider-thumb', '::-moz-moz-range-thumb', '::-ms-thum
@include font-size(1.6rem);
overflow: hidden;
max-width: calc(50% - 16px);
text-overflow: ellipsis;
padding: 0;
margin: 0;
max-width: calc(50% - 16px);
border: none;
&:focus {
@ -315,7 +319,6 @@ $vendors-thumb: ('::-webkit-slider-thumb', '::-moz-moz-range-thumb', '::-ms-thum
&:not([disabled]):hover {
border-color: var(--background-color-invert);
background: var(--background-color-invert);
img {
filter: var(--icon-filter-hover);
}
@ -331,7 +334,7 @@ $vendors-thumb: ('::-webkit-slider-thumb', '::-moz-moz-range-thumb', '::-ms-thum
display: flex;
flex-direction: column;
gap: 16px;
padding: 8px 0 24px;
padding: 8px 0 24px 0;
.description,
.lyrics {

View File

@ -1,33 +1,27 @@
import { Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js'
import { MediaItem } from '~/types/mediaitem'
import { createEffect, createMemo, createSignal, on, onMount, Show } from 'solid-js'
import { PlayerHeader } from './PlayerHeader'
import { PlayerPlaylist } from './PlayerPlaylist'
import styles from './AudioPlayer.module.scss'
import { MediaItem } from '../../../pages/types'
import { imageProxy } from '../../../utils/imageProxy'
type Props = {
media: MediaItem[]
articleSlug?: string
body?: string
editorMode?: boolean
onMediaItemFieldChange?: (
index: number,
field: keyof MediaItem | string | number | symbol,
value: string
) => void
onChangeMediaIndex?: (direction: 'up' | 'down', index: number) => void
onMediaItemFieldChange?: (index: number, field: keyof MediaItem, value: string) => void
onChangeMediaIndex?: (direction: 'up' | 'down', index) => void
}
const getFormattedTime = (point: number) => new Date(point * 1000).toISOString().slice(14, -5)
const getFormattedTime = (point) => new Date(point * 1000).toISOString().slice(14, -5)
export const AudioPlayer = (props: Props) => {
let audioRef: HTMLAudioElement | undefined
let gainNodeRef: GainNode | undefined
let progressRef: HTMLDivElement | undefined
let audioContextRef: AudioContext | undefined
let mouseDownRef: boolean | undefined
const audioRef: { current: HTMLAudioElement } = { current: null }
const gainNodeRef: { current: GainNode } = { current: null }
const progressRef: { current: HTMLDivElement } = { current: null }
const audioContextRef: { current: AudioContext } = { current: null }
const mouseDownRef: { current: boolean } = { current: false }
const [currentTrackDuration, setCurrentTrackDuration] = createSignal(0)
const [currentTime, setCurrentTime] = createSignal(0)
@ -35,25 +29,34 @@ export const AudioPlayer = (props: Props) => {
const [isPlaying, setIsPlaying] = createSignal(false)
const currentTack = createMemo(() => props.media[currentTrackIndex()])
createEffect(on(currentTrackIndex, () => setCurrentTrackDuration(0), { defer: true }))
createEffect(
on(
() => currentTrackIndex(),
() => {
setCurrentTrackDuration(0)
},
{ defer: true }
)
)
const handlePlayMedia = async (trackIndex: number) => {
setIsPlaying(!isPlaying() || trackIndex !== currentTrackIndex())
setCurrentTrackIndex(trackIndex)
if (audioContextRef?.state === 'suspended') {
await audioContextRef?.resume()
if (audioContextRef.current.state === 'suspended') {
await audioContextRef.current.resume()
}
if (isPlaying()) {
await audioRef?.play()
await audioRef.current.play()
} else {
audioRef?.pause()
audioRef.current.pause()
}
}
const handleVolumeChange = (volume: number) => {
if (gainNodeRef) gainNodeRef.gain.value = volume
gainNodeRef.current.gain.value = volume
}
const handleAudioEnd = () => {
@ -62,22 +65,21 @@ export const AudioPlayer = (props: Props) => {
return
}
if (audioRef) audioRef.currentTime = 0
audioRef.current.currentTime = 0
setIsPlaying(false)
setCurrentTrackIndex(0)
}
const handleAudioTimeUpdate = () => {
setCurrentTime(audioRef?.currentTime || 0)
setCurrentTime(audioRef.current.currentTime)
}
onMount(() => {
audioContextRef = new AudioContext()
gainNodeRef = audioContextRef.createGain()
if (audioRef) {
const track = audioContextRef?.createMediaElementSource(audioRef)
track.connect(gainNodeRef).connect(audioContextRef?.destination)
}
audioContextRef.current = new AudioContext()
gainNodeRef.current = audioContextRef.current.createGain()
const track = audioContextRef.current.createMediaElementSource(audioRef.current)
track.connect(gainNodeRef.current).connect(audioContextRef.current.destination)
})
const playPrevTrack = () => {
@ -98,18 +100,13 @@ export const AudioPlayer = (props: Props) => {
setCurrentTrackIndex(newCurrentTrackIndex)
}
const handleMediaItemFieldChange = (
index: number,
field: keyof MediaItem | string | number | symbol,
value: string
) => {
props.onMediaItemFieldChange?.(index, field, value)
const handleMediaItemFieldChange = (index: number, field: keyof MediaItem, value) => {
props.onMediaItemFieldChange(index, field, value)
}
const scrub = (event: MouseEvent | undefined) => {
if (progressRef && audioRef) {
audioRef.currentTime = (event?.offsetX || 0 / progressRef.offsetWidth) * currentTrackDuration()
}
const scrub = (event) => {
audioRef.current.currentTime =
(event.offsetX / progressRef.current.offsetWidth) * currentTrackDuration()
}
return (
@ -126,11 +123,11 @@ export const AudioPlayer = (props: Props) => {
<div class={styles.timeline}>
<div
class={styles.progress}
ref={(el) => (progressRef = el)}
onClick={scrub}
onMouseMove={(e) => mouseDownRef && scrub(e)}
onMouseDown={() => (mouseDownRef = true)}
onMouseUp={() => (mouseDownRef = false)}
ref={(el) => (progressRef.current = el)}
onClick={(e) => scrub(e)}
onMouseMove={(e) => mouseDownRef.current && scrub(e)}
onMouseDown={() => (mouseDownRef.current = true)}
onMouseUp={() => (mouseDownRef.current = false)}
>
<div
class={styles.progressFilled}
@ -146,13 +143,14 @@ export const AudioPlayer = (props: Props) => {
</Show>
</div>
<audio
ref={(el) => (audioRef = el)}
ref={(el) => (audioRef.current = el)}
onTimeUpdate={handleAudioTimeUpdate}
src={currentTack().url.replace('images.discours.io', 'cdn.discours.io')}
// TEMP SOLUTION for http/https
src={currentTack().url.startsWith('https') ? currentTack().url : imageProxy(currentTack().url)}
onCanPlay={() => {
// start to play the next track on src change
if (isPlaying() && audioRef) {
audioRef.play()
if (isPlaying()) {
audioRef.current.play()
}
}}
onLoadedMetadata={({ currentTarget }) => setCurrentTrackDuration(currentTarget.duration)}
@ -163,7 +161,7 @@ export const AudioPlayer = (props: Props) => {
<PlayerPlaylist
editorMode={props.editorMode}
onPlayMedia={handlePlayMedia}
onChangeMediaIndex={(direction, index) => props.onChangeMediaIndex?.(direction, index)}
onChangeMediaIndex={(direction, index) => props.onChangeMediaIndex(direction, index)}
isPlaying={isPlaying()}
media={props.media}
currentTrackIndex={currentTrackIndex()}

View File

@ -1,10 +1,11 @@
import { createSignal, Show } from 'solid-js'
import { clsx } from 'clsx'
import { Show, createSignal } from 'solid-js'
import { Icon } from '~/components/_shared/Icon'
import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler'
import { MediaItem } from '~/types/mediaitem'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import { Icon } from '../../_shared/Icon'
import styles from './AudioPlayer.module.scss'
import { MediaItem } from '../../../pages/types'
type Props = {
onPlayMedia: () => void
@ -16,7 +17,10 @@ type Props = {
}
export const PlayerHeader = (props: Props) => {
let volumeContainerRef: HTMLDivElement | undefined
const volumeContainerRef: { current: HTMLDivElement } = {
current: null
}
const [isVolumeBarOpened, setIsVolumeBarOpened] = createSignal(false)
const toggleVolumeBar = () => {
@ -61,7 +65,7 @@ export const PlayerHeader = (props: Props) => {
>
<Icon name="player-arrow" />
</button>
<div ref={(el) => (volumeContainerRef = el)} class={styles.volumeContainer}>
<div ref={(el) => (volumeContainerRef.current = el)} class={styles.volumeContainer}>
<Show when={isVolumeBarOpened()}>
<input
type="range"
@ -74,7 +78,7 @@ export const PlayerHeader = (props: Props) => {
onChange={({ target }) => props.onVolumeChange(Number(target.value))}
/>
</Show>
<button onClick={toggleVolumeBar} class={styles.volumeButton} aria-label="Volume">
<button onClick={toggleVolumeBar} class={styles.volumeButton} role="button" aria-label="Volume">
<Icon name="volume" />
</button>
</div>

View File

@ -1,16 +1,14 @@
import { For, Show, createSignal, lazy } from 'solid-js'
import { Icon } from '~/components/_shared/Icon'
import { Popover } from '~/components/_shared/Popover'
import { useLocalize } from '~/context/localize'
import { MediaItem } from '~/types/mediaitem'
import { descFromBody } from '~/utils/meta'
import { createSignal, For, Show } from 'solid-js'
import { SharePopup, getShareUrl } from '../SharePopup'
import { getDescription } from '../../../utils/meta'
import { useLocalize } from '../../../context/localize'
import { Popover } from '../../_shared/Popover'
import { Icon } from '../../_shared/Icon'
import styles from './AudioPlayer.module.scss'
const MicroEditor = lazy(() => import('../../Editor/MicroEditor'))
const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea'))
import { GrowingTextarea } from '../../_shared/GrowingTextarea'
import MD from '../MD'
import { MediaItem } from '../../../pages/types'
import SimplifiedEditor from '../../Editor/SimplifiedEditor'
type Props = {
media: MediaItem[]
@ -21,31 +19,20 @@ type Props = {
body?: string
editorMode?: boolean
onMediaItemFieldChange?: (index: number, field: keyof MediaItem, value: string) => void
onChangeMediaIndex?: (direction: 'up' | 'down', index: number) => void
onChangeMediaIndex?: (direction: 'up' | 'down', index) => void
}
const _getMediaTitle = (itm: MediaItem, idx: number) => `${idx}. ${itm.artist} - ${itm.title}`
export const PlayerPlaylist = (props: Props) => {
const { t } = useLocalize()
const [activeEditIndex, setActiveEditIndex] = createSignal(-1)
const toggleDropDown = (index: number) => {
const toggleDropDown = (index) => {
setActiveEditIndex(activeEditIndex() === index ? -1 : index)
}
const handleMediaItemFieldChange = (field: keyof MediaItem, value: string) => {
props.onMediaItemFieldChange?.(activeEditIndex(), field, value)
props.onMediaItemFieldChange(activeEditIndex(), field, value)
}
const play = (index: number) => {
props.onPlayMedia(index)
//const mi = props.media[index]
//gtag('event', 'select_item', {
//item_list_id: props.articleSlug,
//item_list_name: getMediaTitle(mi, index),
//items: props.media.map((it, ix) => getMediaTitle(it, ix)),
//})
}
return (
<ul class={styles.playlist}>
<For each={props.media}>
@ -54,7 +41,7 @@ export const PlayerPlaylist = (props: Props) => {
<div class={styles.playlistItem}>
<button
class={styles.playlistItemPlayButton}
onClick={() => play(index())}
onClick={() => props.onPlayMedia(index())}
type="button"
aria-label="Play"
>
@ -65,8 +52,8 @@ export const PlayerPlaylist = (props: Props) => {
when={activeEditIndex() === index() && props.editorMode}
fallback={
<>
<div class={styles.title}>{mi.title || index()}</div>
<div class={styles.artist}>{mi.artist || ''}</div>
<div class={styles.title}>{mi.title || t('Song title')}</div>
<div class={styles.artist}>{mi.artist || t('Artist')}</div>
</>
}
>
@ -89,26 +76,26 @@ export const PlayerPlaylist = (props: Props) => {
<div class={styles.actions}>
<Show when={props.editorMode}>
<Popover content={t('Move up')}>
{(triggerRef: (el: HTMLElement) => void) => (
{(triggerRef: (el) => void) => (
<button
type="button"
ref={triggerRef}
class={styles.action}
disabled={index() === 0}
onClick={() => props.onChangeMediaIndex?.('up', index())}
onClick={() => props.onChangeMediaIndex('up', index())}
>
<Icon name="up-button" />
</button>
)}
</Popover>
<Popover content={t('Move down')}>
{(triggerRef: (el: HTMLElement) => void) => (
{(triggerRef: (el) => void) => (
<button
type="button"
ref={triggerRef}
class={styles.action}
disabled={index() === props.media.length - 1}
onClick={() => props.onChangeMediaIndex?.('down', index())}
onClick={() => props.onChangeMediaIndex('down', index())}
>
<Icon name="up-button" class={styles.moveIconDown} />
</button>
@ -117,7 +104,7 @@ export const PlayerPlaylist = (props: Props) => {
</Show>
<Show when={(mi.lyrics || mi.body) && !props.editorMode}>
<Popover content={t('Show lyrics')}>
{(triggerRef: (el: HTMLElement) => void) => (
{(triggerRef: (el) => void) => (
<button ref={triggerRef} type="button" onClick={() => toggleDropDown(index())}>
<Icon name="list" />
</button>
@ -125,7 +112,7 @@ export const PlayerPlaylist = (props: Props) => {
</Popover>
</Show>
<Popover content={props.editorMode ? t('Edit') : t('Share')}>
{(triggerRef: (el: HTMLElement) => void) => (
{(triggerRef: (el) => void) => (
<div ref={triggerRef}>
<Show
when={!props.editorMode}
@ -137,8 +124,8 @@ export const PlayerPlaylist = (props: Props) => {
>
<SharePopup
title={mi.title}
description={descFromBody(props.body || '')}
imageUrl={mi.pic || ''}
description={getDescription(props.body)}
imageUrl={mi.pic}
shareUrl={getShareUrl({ pathname: `/${props.articleSlug}` })}
trigger={
<div>
@ -159,22 +146,23 @@ export const PlayerPlaylist = (props: Props) => {
<div class={styles.descriptionBlock}>
<Show when={mi.body}>
<div class={styles.description}>
<div innerHTML={mi.body} />
<MD body={mi.body} />
</div>
</Show>
<Show when={mi.lyrics}>
<div class={styles.lyrics}>
<div innerHTML={mi.lyrics} />
<MD body={mi.lyrics} />
</div>
</Show>
</div>
}
>
<div class={styles.descriptionBlock}>
<MicroEditor
content={mi.body}
<SimplifiedEditor
initialContent={mi.body}
placeholder={`${t('Description')}...`}
onChange={(value: string) => handleMediaItemFieldChange('body', value)}
smallHeight={true}
onChange={(value) => handleMediaItemFieldChange('body', value)}
/>
<GrowingTextarea
allowEnterKey={true}

View File

@ -1,10 +1,6 @@
.comment {
@include media-breakpoint-down(sm) {
padding-right: 0;
}
margin: 0 0 0.5em;
padding: 0 1rem;
padding: 1rem;
transition: background-color 0.3s;
position: relative;
list-style: none;
@ -14,13 +10,15 @@
background: rgb(38 56 217 / 5%);
}
.comment {
margin-right: -1rem;
@include media-breakpoint-down(sm) {
margin-right: -1.2rem;
}
.comment {
&::before,
&::after {
content: '';
left: -14px;
left: 0;
position: absolute;
}
@ -28,9 +26,9 @@
border-bottom: 2px solid #ccc;
border-left: 2px solid #ccc;
border-radius: 0 0 0 1.2rem;
top: -24px;
height: 50px;
width: 12px;
top: -1rem;
height: 2.4rem;
width: 1.2rem;
}
&::after {
@ -59,29 +57,24 @@
align-items: center;
margin-bottom: 1.4rem;
}
.commentControl:not(.commentControlReply) {
opacity: 0;
}
}
.commentContent {
padding: 0 1rem 1rem 0;
&:hover {
.commentControlReply,
.commentControlShare,
.commentControlDelete,
.commentControlEdit,
.commentControlComplain,
.commentControl {
.commentControlComplain {
opacity: 1;
}
}
}
p:last-child {
margin-bottom: 0;
}
.commentControls {
@include font-size(1.2rem);
margin-bottom: 0.5em;
}
.commentControlReply,
@ -111,7 +104,7 @@
.commentControl {
border: none;
color: var(--secondary-color);
color: #696969;
cursor: pointer;
display: inline-flex;
line-height: 1.2;
@ -124,8 +117,8 @@
vertical-align: top;
&:hover {
background: var(--background-color-invert);
color: var(--default-color-invert);
background: #000;
color: #fff;
.icon {
filter: invert(1);
@ -179,20 +172,18 @@
@include font-size(1.2rem);
}
.commentAuthor {
margin-right: 2rem;
}
.articleAuthor {
@include font-size(1.2rem);
color: var(--blue-500);
margin: 0.3rem 1rem 0;
color: #2638d9;
font-size: 12px;
margin-right: 12px;
}
.articleLink {
@include font-size(1.2rem);
flex: 0 0 50%;
margin-right: 2em;
@include media-breakpoint-down(md) {
margin: 0.3em 0 0.5em;
}
@ -205,16 +196,9 @@
overflow: hidden;
white-space: nowrap;
}
flex: 0 0 50%;
margin-right: 2em;
}
.articleLinkIcon {
@include media-breakpoint-up(md) {
margin-left: 1em;
}
display: inline-block;
margin-right: 1em;
vertical-align: middle;
@ -222,8 +206,6 @@
}
.commentDates {
@include font-size(1.2rem);
flex: 1;
display: flex;
gap: 1rem;
@ -233,6 +215,8 @@
margin: 0 1em 4px 0;
color: rgb(0 0 0 / 30%);
@include font-size(1.2rem);
.date {
.icon {
line-height: 1;
@ -246,13 +230,13 @@
}
.commentDetails {
padding: 1rem 0.2rem 0;
margin-bottom: 1.2rem;
@include media-breakpoint-up(md) {
align-items: center;
display: flex;
}
padding: 1rem 0.2rem 0;
margin-bottom: 1.2rem;
}
.compactUserpic {

View File

@ -1,39 +1,38 @@
import { A } from '@solidjs/router'
import { Show, createMemo, createSignal, For, lazy, Suspense } from 'solid-js'
import { clsx } from 'clsx'
import { For, Show, Suspense, createMemo, createSignal, lazy } from 'solid-js'
import { Icon } from '~/components/_shared/Icon'
import { ShowIfAuthenticated } from '~/components/_shared/ShowIfAuthenticated'
import { useLocalize } from '~/context/localize'
import { useReactions } from '~/context/reactions'
import { useSession } from '~/context/session'
import { useSnackbar, useUI } from '~/context/ui'
import deleteReactionMutation from '~/graphql/mutation/core/reaction-destroy'
import {
Author,
MutationCreate_ReactionArgs,
MutationUpdate_ReactionArgs,
Reaction,
ReactionKind
} from '~/graphql/schema/core.gen'
import { AuthorLink } from '../../Author/AuthorLink'
import { Userpic } from '../../Author/Userpic'
import { CommentDate } from '../CommentDate'
import { CommentRatingControl } from '../CommentRatingControl'
import { getPagePath } from '@nanostores/router'
import MD from './MD'
import { AuthorCard } from '../Author/AuthorCard'
import { Userpic } from '../Author/Userpic'
import { CommentRatingControl } from './CommentRatingControl'
import { CommentDate } from './CommentDate'
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
import { Icon } from '../_shared/Icon'
import { useSession } from '../../context/session'
import { useLocalize } from '../../context/localize'
import { useReactions } from '../../context/reactions'
import { useSnackbar } from '../../context/snackbar'
import { useConfirm } from '../../context/confirm'
import { Author, Reaction, ReactionKind } from '../../graphql/types.gen'
import { router } from '../../stores/router'
import styles from './Comment.module.scss'
const MiniEditor = lazy(() => import('../../Editor/MiniEditor'))
const SimplifiedEditor = lazy(() => import('../Editor/SimplifiedEditor'))
type Props = {
comment: Reaction
compact?: boolean
isArticleAuthor?: boolean
sortedComments?: Reaction[]
lastSeen?: number
lastSeen?: Date
class?: string
showArticleLink?: boolean
clickedReply?: (id: number) => void
clickedReplyId?: number
onDelete?: (id: number) => void
}
export const Comment = (props: Props) => {
@ -41,22 +40,27 @@ export const Comment = (props: Props) => {
const [isReplyVisible, setIsReplyVisible] = createSignal(false)
const [loading, setLoading] = createSignal(false)
const [editMode, setEditMode] = createSignal(false)
const [editedBody, setEditedBody] = createSignal<string>()
const { session, client } = useSession()
const author = createMemo<Author>(() => session()?.user?.app_data?.profile as Author)
const { createShoutReaction, updateShoutReaction } = useReactions()
const { showConfirm } = useUI()
const { showSnackbar } = useSnackbar()
const canEdit = createMemo(
() =>
Boolean(author()?.id) &&
(props.comment?.created_by?.slug === author()?.slug || session()?.user?.roles?.includes('editor'))
)
const [clearEditor, setClearEditor] = createSignal(false)
const { session } = useSession()
const body = createMemo(() => (editedBody() ? editedBody()?.trim() : props.comment.body?.trim() || ''))
const {
actions: { createReaction, deleteReaction, updateReaction }
} = useReactions()
const {
actions: { showConfirm }
} = useConfirm()
const {
actions: { showSnackbar }
} = useSnackbar()
const isCommentAuthor = createMemo(() => props.comment.createdBy?.slug === session()?.user?.slug)
const comment = createMemo(() => props.comment)
const body = createMemo(() => (comment().body || '').trim())
const remove = async () => {
if (props.comment?.id) {
if (comment()?.id) {
try {
const isConfirmed = await showConfirm({
confirmBody: t('Are you sure you want to delete this comment?'),
@ -66,68 +70,46 @@ export const Comment = (props: Props) => {
})
if (isConfirmed) {
const resp = await client()
?.mutation(deleteReactionMutation, { id: props.comment.id })
.toPromise()
const result = resp?.data?.delete_reaction
const { error } = result
const notificationType = error ? 'error' : 'success'
const notificationMessage = error
? t('Failed to delete comment')
: t('Comment successfully deleted')
await showSnackbar({
type: notificationType,
body: notificationMessage,
duration: 3
})
await deleteReaction(comment().id)
if (!error && props.onDelete) {
props.onDelete(props.comment.id)
}
await showSnackbar({ body: t('Comment successfully deleted') })
}
} catch (error) {
await showSnackbar({ body: 'error' })
console.error('[deleteReaction]', error)
}
}
}
const handleCreate = async (value: string) => {
const handleCreate = async (value) => {
try {
setLoading(true)
await createShoutReaction({
reaction: {
kind: ReactionKind.Comment,
reply_to: props.comment.id,
body: value,
shout: props.comment.shout.id
}
} as MutationCreate_ReactionArgs)
await createReaction({
kind: ReactionKind.Comment,
replyTo: props.comment.id,
body: value,
shout: props.comment.shout.id
})
setClearEditor(true)
setIsReplyVisible(false)
setLoading(false)
} catch (error) {
console.error('[handleCreate reaction]:', error)
}
setClearEditor(false)
}
const toggleEditMode = () => {
setEditMode((oldEditMode) => !oldEditMode)
}
const handleUpdate = async (value: string) => {
const handleUpdate = async (value) => {
setLoading(true)
try {
const reaction = await updateShoutReaction({
reaction: {
id: props.comment.id || 0,
kind: ReactionKind.Comment,
body: value,
shout: props.comment.shout.id
}
} as MutationUpdate_ReactionArgs)
if (reaction) {
setEditedBody(value)
}
await updateReaction(props.comment.id, {
kind: ReactionKind.Comment,
body: value,
shout: props.comment.shout.id
})
setEditMode(false)
setLoading(false)
} catch (error) {
@ -135,12 +117,13 @@ export const Comment = (props: Props) => {
}
}
const createdAt = new Date(comment()?.createdAt)
return (
<li
id={`comment_${props.comment.id}`}
id={`comment_${comment().id}`}
class={clsx(styles.comment, props.class, {
[styles.isNew]:
(props.lastSeen || Date.now()) > (props.comment.updated_at || props.comment.created_at)
[styles.isNew]: !isCommentAuthor() && createdAt > props.lastSeen
})}
>
<Show when={!!body()}>
@ -150,21 +133,28 @@ export const Comment = (props: Props) => {
fallback={
<div>
<Userpic
name={props.comment.created_by.name || ''}
userpic={props.comment.created_by.pic || ''}
name={comment().createdBy.name}
userpic={comment().createdBy.userpic}
isBig={false}
class={clsx({
[styles.compactUserpic]: props.compact
})}
/>
<small>
<a href={`#comment_${props.comment?.id}`}>{props.comment?.shout.title || ''}</a>
<a href={`#comment_${comment()?.id}`}>{comment()?.shout.title || ''}</a>
</small>
</div>
}
>
<div class={styles.commentDetails}>
<div class={styles.commentAuthor}>
<AuthorLink author={props.comment?.created_by as Author} />
<AuthorCard
author={comment()?.createdBy as Author}
hideDescription={true}
hideFollow={true}
isComments={true}
hasLink={true}
/>
</div>
<Show when={props.isArticleAuthor}>
@ -174,36 +164,46 @@ export const Comment = (props: Props) => {
<Show when={props.showArticleLink}>
<div class={styles.articleLink}>
<Icon name="arrow-right" class={styles.articleLinkIcon} />
<A href={`${props.comment.shout.slug}?commentId=${props.comment.id}`}>
{props.comment.shout.title}
</A>
<a
href={`${getPagePath(router, 'article', { slug: comment().shout.slug })}?commentId=${
comment().id
}`}
>
{comment().shout.title}
</a>
</div>
</Show>
<CommentDate showOnHover={true} comment={props.comment} isShort={true} />
<CommentRatingControl comment={props.comment} />
<CommentDate comment={comment()} isShort={true} />
<CommentRatingControl comment={comment()} />
</div>
</Show>
<div class={styles.commentBody}>
<Show when={editMode()} fallback={<div innerHTML={body()} />}>
<Show when={editMode()} fallback={<MD body={body()} />}>
<Suspense fallback={<p>{t('Loading')}</p>}>
<MiniEditor
content={editedBody() || props.comment.body || ''}
<SimplifiedEditor
initialContent={comment().body}
submitButtonText={t('Save')}
quoteEnabled={true}
imageEnabled={true}
placeholder={t('Write a comment...')}
onSubmit={(value) => handleUpdate(value)}
onCancel={() => setEditMode(false)}
submitByCtrlEnter={true}
setClear={clearEditor()}
/>
</Suspense>
</Show>
</div>
<Show when={!props.compact}>
<div>
<div class={styles.commentControls}>
<ShowIfAuthenticated>
<button
disabled={loading()}
onClick={() => {
setIsReplyVisible(!isReplyVisible())
props.clickedReply?.(props.comment.id)
props.clickedReply(props.comment.id)
}}
class={clsx(styles.commentControl, styles.commentControlReply)}
>
@ -211,7 +211,7 @@ export const Comment = (props: Props) => {
{loading() ? t('Loading') : t('Reply')}
</button>
</ShowIfAuthenticated>
<Show when={canEdit()}>
<Show when={isCommentAuthor()}>
<button
class={clsx(styles.commentControl, styles.commentControlEdit)}
onClick={toggleEditMode}
@ -229,7 +229,7 @@ export const Comment = (props: Props) => {
</Show>
{/*<SharePopup*/}
{/* title={'article.title'}*/}
{/* title={'artile.title'}*/}
{/* description={getDescription(body())}*/}
{/* containerCssClass={stylesHeader.control}*/}
{/* trigger={*/}
@ -244,15 +244,18 @@ export const Comment = (props: Props) => {
{/* class={clsx(styles.commentControl, styles.commentControlComplain)}*/}
{/* onClick={() => showModal('reportComment')}*/}
{/*>*/}
{/* {t('Complain')}*/}
{/* {t('Report')}*/}
{/*</button>*/}
</div>
<Show when={isReplyVisible() && props.clickedReplyId === props.comment.id}>
<Suspense fallback={<p>{t('Loading')}</p>}>
<MiniEditor
<SimplifiedEditor
quoteEnabled={true}
imageEnabled={true}
placeholder={t('Write a comment...')}
onSubmit={(value) => handleCreate(value)}
submitByCtrlEnter={true}
/>
</Suspense>
</Show>
@ -261,7 +264,7 @@ export const Comment = (props: Props) => {
</Show>
<Show when={props.sortedComments}>
<ul>
<For each={props.sortedComments?.filter((r) => r.reply_to === props.comment.id)}>
<For each={props.sortedComments.filter((r) => r.replyTo === props.comment.id)}>
{(c) => (
<Comment
sortedComments={props.sortedComments}

View File

@ -1 +0,0 @@
export { Comment } from './Comment'

View File

@ -0,0 +1,31 @@
.commentDates {
color: #9fa1a7;
align-items: center;
align-self: center;
display: flex;
flex: 1;
flex-wrap: wrap;
@include font-size(1.2rem);
font-size: 1.2rem;
justify-content: flex-start;
margin: 0 1em 0 0;
.date {
font-weight: 500;
margin-right: 1rem;
.icon {
line-height: 1;
width: 1rem;
display: inline-block;
opacity: 0.6;
margin-right: 0.5rem;
vertical-align: middle;
}
}
}
.commentDatesLastInRow {
margin-right: 0;
white-space: nowrap;
}

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