diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml index 9678fb55..af227b9a 100644 --- a/.gitea/workflows/main.yml +++ b/.gitea/workflows/main.yml @@ -36,6 +36,7 @@ jobs: run: npm run e2e env: BASE_URL: ${{ github.event.deployment_status.target_url }} + DEBUG: pw:api email-templates: runs-on: ubuntu-latest diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index 4725a703..5e819c27 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -1,28 +1,54 @@ -name: "deploy" +name: "CI and E2E Tests" -on: [push] +on: + push: + deployment_status: + types: [success] jobs: - test: + ci: + if: github.event_name == 'push' 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 + 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://testing3.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://testing3.discours.io \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5e15e32c..a8ed6c74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ +.devcontainer dist/ node_modules/ npm-debug.log* pnpm-debug.log* -.vscode .env .env.production .DS_Store @@ -22,6 +22,9 @@ bun.lockb /blob-report/ /playwright/.cache/ /plawright-report/ +target +.github/dependabot.yml .output .vinxi +*.pem diff --git a/.stylelintrc.json b/.stylelintrc.json index 55ff0d41..abbdd2b7 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,34 +1,73 @@ { - "extends": ["stylelint-config-standard-scss"], + "defaultSeverity": "warning", + "extends": ["stylelint-config-standard-scss", "stylelint-config-recommended"], "plugins": ["stylelint-order", "stylelint-scss"], "rules": { - "keyframes-name-pattern": null, - "declaration-block-no-redundant-longhand-properties": null, - "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": [ + "annotation-no-unknown": [ true, { - "ignorePseudoClasses": ["global", "export"] + "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"] + } ] - }, - "defaultSeverity": "warning" + } } diff --git a/.vscode/extension.json b/.vscode/extension.json new file mode 100644 index 00000000..685ef5aa --- /dev/null +++ b/.vscode/extension.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["biomejs.biome", "stylelint.vscode-stylelint", "wayou.vscode-todo-highlight"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..4651d64a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "editor.codeActionsOnSave": { + "source.organizeImports.biome": "always" + } +} diff --git a/README.en.md b/README.en.md new file mode 100644 index 00000000..901e6d42 --- /dev/null +++ b/README.en.md @@ -0,0 +1,57 @@ +## 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. diff --git a/README.md b/README.md index 1929e18d..8f3f4157 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,61 @@ -## How to start +[English](README.en.md) -Use Bun to manage packages. +## Рекомендации по настройке разработки -``` -bun i -``` +### Как начать -## Useful commands +Используйте `bun i`, `npm i`, `pnpm i` или `yarn`, чтобы установить пакеты. -run checks with your favorite package manager: npm, yarn, pnpm or bun +### Настройка переменных + +- Используйте файл `.env` для настройки переменных собственной среды разработки. +- Переменные окружения с префиксом `PUBLIC_` широко используются в `/src/utils/config.ts`. + +### Полезные команды + +Запуск проверки соответствия типов и автоматически исправить ошибки стилей, порядок импорта, форматирование: ``` bun run typecheck -``` -fix styles, imports, formatting and autofixable linting errors: -``` bun run fix ``` + + +## End-to-End (E2E) тесты + +End-to-end тесты написаны с использованием [Playwright](https://playwright.dev/). + +### Структура + +- `/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`: Содержит тесты, которые не требуют аутентификации. +- `tests-with-auth`: Содержит тесты, которые взаимодействуют с аутентифицированными частями приложения. + +🔧 **Конфигурация:** + +Playwright настроен на использование переменной окружения `BASE_URL`. Убедитесь, что она правильно установлена в вашей конфигурации CI для указания на правильную среду. + +📝 **Примечание:** + +После того как страницы были настроены для работы с аутентификацией, все тесты должны быть перемещены в директорию `tests` для упрощения процесса тестирования. diff --git a/app.config.ts b/app.config.ts index 5c758d8d..860366d2 100644 --- a/app.config.ts +++ b/app.config.ts @@ -1,15 +1,33 @@ import { SolidStartInlineConfig, defineConfig } from '@solidjs/start/config' -import { nodePolyfills } from 'vite-plugin-node-polyfills' +import { CSSOptions } from 'vite' +// import { visualizer } from 'rollup-plugin-visualizer' +import mkcert from 'vite-plugin-mkcert' +import { PolyfillOptions, nodePolyfills } from 'vite-plugin-node-polyfills' import sassDts from 'vite-plugin-sass-dts' const isVercel = Boolean(process?.env.VERCEL) const isBun = Boolean(process.env.BUN) +console.info(`[app.config] build for ${isVercel ? 'vercel' : isBun ? 'bun' : 'node'}!`) + +const polyfillOptions = { + include: ['path', 'stream', 'util'], + exclude: ['http'], + globals: { + Buffer: true + }, + overrides: { + fs: 'memfs' + }, + protocolImports: true +} as PolyfillOptions + export default defineConfig({ ssr: true, server: { preset: isVercel ? 'vercel_edge' : isBun ? 'bun' : 'node', - port: 3000 + port: 3000, + https: true }, devOverlay: true, build: { @@ -18,31 +36,14 @@ export default defineConfig({ }, vite: { envPrefix: 'PUBLIC_', - plugins: [ - nodePolyfills({ - include: ['path', 'stream', 'util'], - exclude: ['http'], - globals: { - Buffer: true - }, - overrides: { - fs: 'memfs' - }, - protocolImports: true - }), - sassDts() - ], + plugins: [!isVercel && mkcert(), nodePolyfills(polyfillOptions), sassDts()], css: { preprocessorOptions: { scss: { additionalData: '@import "src/styles/imports";\n', includePaths: ['./public', './src/styles'] } - } - }, - build: { - chunkSizeWarningLimit: 1024, - target: 'esnext' + } as CSSOptions['preprocessorOptions'] } } } as SolidStartInlineConfig) diff --git a/deploy.sh b/deploy.sh deleted file mode 100644 index dbcafce1..00000000 --- a/deploy.sh +++ /dev/null @@ -1,7 +0,0 @@ -if [ "$VERCEL_GIT_COMMIT_REF" = "router-upgrade" ] || [ "$VERCEL_GIT_COMMIT_REF" = "feature/rating" ]; then - echo "Building on solid start" - exit 1 -else - echo "Not on solid start" - exit 0 -fi diff --git a/docs/article.puml b/docs/article.puml deleted file mode 100644 index 3897e1e6..00000000 --- a/docs/article.puml +++ /dev/null @@ -1,66 +0,0 @@ -@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 -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 -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 -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 - diff --git a/docs/i18n.puml b/docs/i18n.puml deleted file mode 100644 index 6c04fce1..00000000 --- a/docs/i18n.puml +++ /dev/null @@ -1,40 +0,0 @@ -@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 - diff --git a/docs/routing.puml b/docs/routing.puml deleted file mode 100644 index 7ace8f8e..00000000 --- a/docs/routing.puml +++ /dev/null @@ -1,24 +0,0 @@ -@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 - diff --git a/package-lock.json b/package-lock.json index 1cfe29f4..850685af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,60 +11,60 @@ "dependencies": { "form-data": "^4.0.0", "idb": "^8.0.0", - "mailgun.js": "^10.2.1" + "mailgun.js": "^10.2.3" }, "devDependencies": { "@authorizerdev/authorizer-js": "^2.0.3", - "@biomejs/biome": "^1.8.2", + "@biomejs/biome": "^1.8.3", "@graphql-codegen/cli": "^5.0.2", - "@graphql-codegen/typescript": "^4.0.7", - "@graphql-codegen/typescript-operations": "^4.2.1", + "@graphql-codegen/typescript": "^4.0.9", + "@graphql-codegen/typescript-operations": "^4.2.3", "@graphql-codegen/typescript-urql": "^4.0.0", - "@hocuspocus/provider": "^2.13.2", - "@playwright/test": "^1.44.1", + "@hocuspocus/provider": "^2.13.5", + "@playwright/test": "^1.45.2", "@popperjs/core": "^2.11.8", - "@solid-devtools/transform": "^0.10.4", "@solid-primitives/media": "^2.2.9", - "@solid-primitives/memo": "^1.3.8", + "@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": "^3.7.1", + "@solid-primitives/storage": "^3.8.0", "@solid-primitives/upload": "^0.0.117", "@solidjs/meta": "^0.29.4", - "@solidjs/router": "^0.13.6", - "@solidjs/start": "^1.0.2", - "@tiptap/core": "^2.4.0", - "@tiptap/extension-blockquote": "^2.4.0", - "@tiptap/extension-bold": "^2.4.0", - "@tiptap/extension-bubble-menu": "^2.4.0", - "@tiptap/extension-bullet-list": "^2.4.0", - "@tiptap/extension-character-count": "^2.4.0", - "@tiptap/extension-collaboration": "^2.4.0", - "@tiptap/extension-collaboration-cursor": "^2.4.0", - "@tiptap/extension-document": "^2.4.0", - "@tiptap/extension-dropcursor": "^2.4.0", - "@tiptap/extension-floating-menu": "^2.4.0", - "@tiptap/extension-focus": "^2.4.0", - "@tiptap/extension-gapcursor": "^2.4.0", - "@tiptap/extension-hard-break": "^2.4.0", - "@tiptap/extension-heading": "^2.4.0", - "@tiptap/extension-highlight": "^2.4.0", - "@tiptap/extension-history": "^2.4.0", - "@tiptap/extension-horizontal-rule": "^2.4.0", - "@tiptap/extension-image": "^2.4.0", - "@tiptap/extension-italic": "^2.4.0", - "@tiptap/extension-link": "^2.4.0", - "@tiptap/extension-list-item": "^2.4.0", - "@tiptap/extension-ordered-list": "^2.4.0", - "@tiptap/extension-paragraph": "^2.4.0", - "@tiptap/extension-placeholder": "^2.4.0", - "@tiptap/extension-strike": "^2.4.0", - "@tiptap/extension-text": "^2.4.0", - "@tiptap/extension-underline": "^2.4.0", - "@tiptap/extension-youtube": "^2.4.0", + "@solidjs/router": "^0.14.1", + "@solidjs/start": "^1.0.6", + "@tiptap/core": "^2.5.4", + "@tiptap/extension-blockquote": "^2.5.4", + "@tiptap/extension-bold": "^2.5.4", + "@tiptap/extension-bubble-menu": "^2.5.4", + "@tiptap/extension-bullet-list": "^2.5.4", + "@tiptap/extension-character-count": "^2.5.4", + "@tiptap/extension-collaboration": "^2.5.4", + "@tiptap/extension-collaboration-cursor": "^2.5.4", + "@tiptap/extension-document": "^2.5.4", + "@tiptap/extension-dropcursor": "^2.5.4", + "@tiptap/extension-floating-menu": "^2.5.4", + "@tiptap/extension-focus": "^2.5.4", + "@tiptap/extension-gapcursor": "^2.5.4", + "@tiptap/extension-hard-break": "^2.5.4", + "@tiptap/extension-heading": "^2.5.4", + "@tiptap/extension-highlight": "^2.5.4", + "@tiptap/extension-history": "^2.5.4", + "@tiptap/extension-horizontal-rule": "^2.5.4", + "@tiptap/extension-image": "^2.5.4", + "@tiptap/extension-italic": "^2.5.4", + "@tiptap/extension-link": "^2.5.4", + "@tiptap/extension-list-item": "^2.5.4", + "@tiptap/extension-ordered-list": "^2.5.4", + "@tiptap/extension-paragraph": "^2.5.4", + "@tiptap/extension-placeholder": "^2.5.4", + "@tiptap/extension-strike": "^2.5.4", + "@tiptap/extension-text": "^2.5.4", + "@tiptap/extension-underline": "^2.5.4", + "@tiptap/extension-youtube": "^2.5.4", "@types/cookie": "^0.6.0", "@types/cookie-signature": "^1.1.2", - "@types/node": "^20.14.8", + "@types/node": "^20.14.11", "@types/throttle-debounce": "^5.0.2", "@urql/core": "^5.0.4", "bootstrap": "^5.3.3", @@ -75,38 +75,41 @@ "extended-eventsource": "^1.4.9", "fast-deep-equal": "^3.1.3", "graphql": "^16.9.0", - "i18next": "^23.11.5", + "i18next": "^23.12.2", "i18next-http-backend": "^2.5.2", "i18next-icu": "^2.3.0", "intl-messageformat": "^10.5.14", "javascript-time-ago": "^2.5.10", "patch-package": "^8.0.0", - "prosemirror-history": "^1.4.0", - "prosemirror-trailing-node": "^2.0.8", - "prosemirror-view": "^1.33.8", - "sass": "^1.77.6", - "solid-js": "1.8.17", + "prosemirror-history": "^1.4.1", + "prosemirror-trailing-node": "^2.0.9", + "prosemirror-view": "^1.33.9", + "rollup-plugin-visualizer": "^5.12.0", + "sass": "1.76.0", + "solid-js": "^1.8.18", "solid-popper": "^0.3.0", "solid-tiptap": "0.7.0", "solid-transition-group": "^0.2.3", - "stylelint": "^16.6.1", + "stylelint": "^16.7.0", + "stylelint-config-recommended": "^14.0.1", "stylelint-config-standard-scss": "^13.1.0", "stylelint-order": "^6.0.4", - "stylelint-scss": "^6.3.2", - "swiper": "^11.1.4", + "stylelint-scss": "^6.4.1", + "swiper": "^11.1.5", "throttle-debounce": "^5.0.2", "tslib": "^2.6.3", - "typescript": "^5.5.2", + "typescript": "^5.5.3", "typograf": "^7.4.1", "uniqolor": "^1.1.1", - "vinxi": "^0.3.12", + "vinxi": "^0.4.1", + "vite-plugin-mkcert": "^1.17.5", "vite-plugin-node-polyfills": "^0.22.0", - "vite-plugin-sass-dts": "^1.3.22", + "vite-plugin-sass-dts": "^1.3.24", "y-prosemirror": "1.2.9", "yjs": "13.6.18" }, "engines": { - "node": "20.x" + "node": ">= 20" } }, "node_modules/@0no-co/graphql.web": { @@ -346,9 +349,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", - "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.9.tgz", + "integrity": "sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==", "dev": true, "license": "MIT", "engines": { @@ -356,22 +359,22 @@ } }, "node_modules/@babel/core": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", - "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", + "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helpers": "^7.24.7", - "@babel/parser": "^7.24.7", + "@babel/generator": "^7.24.9", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-module-transforms": "^7.24.9", + "@babel/helpers": "^7.24.8", + "@babel/parser": "^7.24.8", "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.9", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -387,13 +390,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", - "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "version": "7.24.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.10.tgz", + "integrity": "sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7", + "@babel/types": "^7.24.9", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -416,15 +419,15 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", - "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz", + "integrity": "sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -433,16 +436,16 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz", - "integrity": "sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.8.tgz", + "integrity": "sha512-4f6Oqnmyp2PP3olgUMmOwC3akxSm5aBYraQ6YDdKy7NcAMkDECHWG0DEnV6M2UAkERgIBhYt8S27rURPg7SxWA==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-function-name": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", "@babel/helper-optimise-call-expression": "^7.24.7", "@babel/helper-replace-supers": "^7.24.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", @@ -497,14 +500,14 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz", - "integrity": "sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -525,9 +528,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", - "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz", + "integrity": "sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==", "dev": true, "license": "MIT", "dependencies": { @@ -558,9 +561,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", - "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "dev": true, "license": "MIT", "engines": { @@ -627,9 +630,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "dev": true, "license": "MIT", "engines": { @@ -647,9 +650,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", - "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "dev": true, "license": "MIT", "engines": { @@ -657,14 +660,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", - "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.8.tgz", + "integrity": "sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/types": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -755,9 +758,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", + "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", "dev": true, "license": "MIT", "bin": { @@ -945,17 +948,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz", - "integrity": "sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.8.tgz", + "integrity": "sha512-VXy91c47uujj758ud9wx+OMgheXm4qJfyhj1P18YvlrQkNOSrwsteHk+EFS3OMGfhMhpZa0A+81eE7G4QC+3CA==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.8", "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-function-name": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", "@babel/helper-replace-supers": "^7.24.7", "@babel/helper-split-export-declaration": "^7.24.7", "globals": "^11.1.0" @@ -985,13 +988,13 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz", - "integrity": "sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1085,14 +1088,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz", - "integrity": "sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", "@babel/helper-simple-access": "^7.24.7" }, "engines": { @@ -1237,9 +1240,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", - "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz", + "integrity": "sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==", "dev": true, "license": "MIT", "dependencies": { @@ -1265,20 +1268,20 @@ } }, "node_modules/@babel/traverse": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", - "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz", + "integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", + "@babel/generator": "^7.24.8", "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-function-name": "^7.24.7", "@babel/helper-hoist-variables": "^7.24.7", "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/parser": "^7.24.8", + "@babel/types": "^7.24.8", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1287,13 +1290,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz", + "integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-string-parser": "^7.24.8", "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, @@ -1492,9 +1495,9 @@ } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.6.3.tgz", - "integrity": "sha512-xI/tL2zxzEbESvnSxwFgwvy5HS00oCXxL4MLs6HUiDcYfwowsoQaABKxUElp1ARITrINzBnsECOc1q0eg2GOrA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz", + "integrity": "sha512-2SJS42gxmACHgikc1WGesXLIT8d/q2l0UFM7TaEeIzdFCE/FPMtTiizcPGGJtlPo2xuQzY09OhrLTzRxqJqwGw==", "dev": true, "funding": [ { @@ -1511,13 +1514,13 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-tokenizer": "^2.3.1" + "@csstools/css-tokenizer": "^2.4.1" } }, "node_modules/@csstools/css-tokenizer": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.3.1.tgz", - "integrity": "sha512-iMNHTyxLbBlWIfGtabT157LH9DUx9X8+Y3oymFEuMj8HNc+rpE3dPFGFgHjpKfjeFDjLjYIAIhXPGvS2lKxL9g==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.4.1.tgz", + "integrity": "sha512-eQ9DIktFJBhGjioABJRtUucoWR2mwllurfnM8LuNGAqX3ViZXaUchqk+1s7jjtkFiT9ySdACsFEA3etErkALUg==", "dev": true, "funding": [ { @@ -1535,9 +1538,9 @@ } }, "node_modules/@csstools/media-query-list-parser": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.11.tgz", - "integrity": "sha512-uox5MVhvNHqitPP+SynrB1o8oPxPMt2JLgp5ghJOWf54WGQ5OKu47efne49r1SWqs3wRP8xSWjnO9MBKxhB1dA==", + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.13.tgz", + "integrity": "sha512-XaHr+16KRU9Gf8XLi3q8kDlI18d5vzKSKCY510Vrtc9iNR0NJzbY9hhTmwhzYZj/ZwGL4VmB3TA9hJW0Um2qFA==", "dev": true, "funding": [ { @@ -1554,8 +1557,8 @@ "node": "^14 || ^16 || >=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1" + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1" } }, "node_modules/@csstools/selector-specificity": { @@ -2165,21 +2168,21 @@ } }, "node_modules/@graphql-codegen/client-preset": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-4.3.1.tgz", - "integrity": "sha512-FHszBKhubbJkrZHwzUNfMUp9IkzufCfn/riTpIy5yA84Wq0AJSPFL7nWkG+h3azFPeznLfqo3KJmfzRb+xeFEA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@graphql-codegen/client-preset/-/client-preset-4.3.2.tgz", + "integrity": "sha512-42jHyG6u2uFDIVNvzue8zR529aPT16EYIJQmvMk8XuYHo3PneQVlWmQ3j2fBy+RuWCBzpJKPKm7IGSKiw19nmg==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", "@babel/template": "^7.20.7", "@graphql-codegen/add": "^5.0.3", - "@graphql-codegen/gql-tag-operations": "4.0.8", + "@graphql-codegen/gql-tag-operations": "4.0.9", "@graphql-codegen/plugin-helpers": "^5.0.4", - "@graphql-codegen/typed-document-node": "^5.0.8", - "@graphql-codegen/typescript": "^4.0.8", - "@graphql-codegen/typescript-operations": "^4.2.2", - "@graphql-codegen/visitor-plugin-common": "^5.3.0", + "@graphql-codegen/typed-document-node": "^5.0.9", + "@graphql-codegen/typescript": "^4.0.9", + "@graphql-codegen/typescript-operations": "^4.2.3", + "@graphql-codegen/visitor-plugin-common": "^5.3.1", "@graphql-tools/documents": "^1.0.0", "@graphql-tools/utils": "^10.0.0", "@graphql-typed-document-node/core": "3.2.0", @@ -2206,14 +2209,14 @@ } }, "node_modules/@graphql-codegen/gql-tag-operations": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-4.0.8.tgz", - "integrity": "sha512-slCICQOFbMfdL7mAZ6XUiOhcJl0yOKfqHFiULIlQJKpo8ey6NHsrtc8Q02ZF417BfTfZ/Qj7rmXhkc/dwY94ag==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@graphql-codegen/gql-tag-operations/-/gql-tag-operations-4.0.9.tgz", + "integrity": "sha512-lVgu1HClel896HqZAEjynatlU6eJrYOw+rh05DPgM150xvmb7Gz5TnRHA2vfwlDNIXDaToAIpz5RFfkjjnYM1Q==", "dev": true, "license": "MIT", "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.4", - "@graphql-codegen/visitor-plugin-common": "5.3.0", + "@graphql-codegen/visitor-plugin-common": "5.3.1", "@graphql-tools/utils": "^10.0.0", "auto-bind": "~4.0.0", "tslib": "~2.6.0" @@ -2241,9 +2244,9 @@ } }, "node_modules/@graphql-codegen/schema-ast": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/schema-ast/-/schema-ast-4.0.2.tgz", - "integrity": "sha512-5mVAOQQK3Oz7EtMl/l3vOQdc2aYClUzVDHHkMvZlunc+KlGgl81j8TLa+X7ANIllqU4fUEsQU3lJmk4hXP6K7Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@graphql-codegen/schema-ast/-/schema-ast-4.1.0.tgz", + "integrity": "sha512-kZVn0z+th9SvqxfKYgztA6PM7mhnSZaj4fiuBWvMTqA+QqQ9BBed6Pz41KuD/jr0gJtnlr2A4++/0VlpVbCTmQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2256,14 +2259,14 @@ } }, "node_modules/@graphql-codegen/typed-document-node": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-5.0.8.tgz", - "integrity": "sha512-ImJd1KwS0vYZiPVZzs8EOZ79V96zN0p1A1MJNpk/8CiJWpIi4FupLLfTMMYq5Rr0AZET+O/A+udw4LDjDrAWvg==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typed-document-node/-/typed-document-node-5.0.9.tgz", + "integrity": "sha512-Wx6fyA4vpfIbfNTMiWUECGnjqzKkJdEbZHxVMIegiCBPzBYPAJV4mZZcildLAfm2FtZcgW4YKtFoTbnbXqPB3w==", "dev": true, "license": "MIT", "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.4", - "@graphql-codegen/visitor-plugin-common": "5.3.0", + "@graphql-codegen/visitor-plugin-common": "5.3.1", "auto-bind": "~4.0.0", "change-case-all": "1.0.15", "tslib": "~2.6.0" @@ -2273,15 +2276,15 @@ } }, "node_modules/@graphql-codegen/typescript": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-4.0.8.tgz", - "integrity": "sha512-kYS3SjGNnC9vgFS8N3vaxzRFkdXX2umMi1SOpHjMFCPjMe8NR0uNdW4nP9T0YEq+DvWgj+XojjpFy2oyz9q12w==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-4.0.9.tgz", + "integrity": "sha512-0O35DMR4d/ctuHL1Zo6mRUUzp0BoszKfeWsa6sCm/g70+S98+hEfTwZNDkQHylLxapiyjssF9uw/F+sXqejqLw==", "dev": true, "license": "MIT", "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.4", "@graphql-codegen/schema-ast": "^4.0.2", - "@graphql-codegen/visitor-plugin-common": "5.3.0", + "@graphql-codegen/visitor-plugin-common": "5.3.1", "auto-bind": "~4.0.0", "tslib": "~2.6.0" }, @@ -2290,15 +2293,15 @@ } }, "node_modules/@graphql-codegen/typescript-operations": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-4.2.2.tgz", - "integrity": "sha512-8FJHIAubM4r9ElLuuDAKhdOjainSwRHEmGIrtEgEwHARKhMk1Ttj6bpOQDisYlbDl4ZTHWEJCdNa9o9rgcl+9g==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@graphql-codegen/typescript-operations/-/typescript-operations-4.2.3.tgz", + "integrity": "sha512-6z7avSSOr03l5SyKbeDs7MzRyGwnQFSCqQm8Om5wIuoIgXVu2gXRmcJAY/I7SLdAy9xbF4Sho7XNqieFM2CAFQ==", "dev": true, "license": "MIT", "dependencies": { "@graphql-codegen/plugin-helpers": "^5.0.4", - "@graphql-codegen/typescript": "^4.0.8", - "@graphql-codegen/visitor-plugin-common": "5.3.0", + "@graphql-codegen/typescript": "^4.0.9", + "@graphql-codegen/visitor-plugin-common": "5.3.1", "auto-bind": "~4.0.0", "tslib": "~2.6.0" }, @@ -2473,9 +2476,9 @@ } }, "node_modules/@graphql-codegen/visitor-plugin-common": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.3.0.tgz", - "integrity": "sha512-+kUk7gRD/72Wfkjd7D96Lonh9k4lFw9d3O1+I07Jyja4QN9H42kdFEO0hM/b4Q9lLkI1yJ66Oym7lWz2Ikj3aw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-5.3.1.tgz", + "integrity": "sha512-MktoBdNZhSmugiDjmFl1z6rEUUaqyxtFJYWnDilE7onkPgyw//O0M+TuPBJPBWdyV6J2ond0Hdqtq+rkghgSIQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2538,9 +2541,9 @@ } }, "node_modules/@graphql-tools/apollo-engine-loader/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.11.tgz", - "integrity": "sha512-LS8tSomZa3YHnntpWt3PP43iFEEl6YeIsvDakczHBKlay5LdkXFr8w7v8H6akpG5nRrzydyB0k1iE2eoL6aKIQ==", + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.12.tgz", + "integrity": "sha512-3wBgVdAOlS5jnITT6fbzQPfRs+rgYhyjwEeALlXfgg/0GvokNK5rPuh60iraS/x5UQXNcHSI3Vb+37B9RPSjrA==", "dev": true, "license": "MIT", "dependencies": { @@ -2548,7 +2551,7 @@ "@whatwg-node/events": "^0.1.0", "busboy": "^1.6.0", "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "tslib": "^2.6.3" }, "engines": { "node": ">=16.0.0" @@ -2601,16 +2604,16 @@ } }, "node_modules/@graphql-tools/delegate": { - "version": "10.0.11", - "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-10.0.11.tgz", - "integrity": "sha512-+sKeecdIVXhFB/66e5yjeKYZ3Lpn52yNG637ElVhciuLGgFc153rC6l6zcuNd9yx5wMrNx35U/h3HsMIEI3xNw==", + "version": "10.0.14", + "resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-10.0.14.tgz", + "integrity": "sha512-mYrLtwVKTHg5F4OFrJbiL5F7dzopzGiac5ezkVrnlGNPBQ8GNCr1zo32c1rYyIbsa8fJSUvAJfJfFj6ipnutnw==", "dev": true, "license": "MIT", "dependencies": { "@graphql-tools/batch-execute": "^9.0.4", - "@graphql-tools/executor": "^1.2.1", + "@graphql-tools/executor": "^1.2.8", "@graphql-tools/schema": "^10.0.4", - "@graphql-tools/utils": "^10.2.1", + "@graphql-tools/utils": "^10.2.3", "dataloader": "^2.2.2", "tslib": "^2.5.0" }, @@ -2639,13 +2642,13 @@ } }, "node_modules/@graphql-tools/executor": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.2.7.tgz", - "integrity": "sha512-oyIw69QA+PuS/g7ttZZeEpIPS5CCGiIYitGtNxaChuiK7NPb7FD1dwOEXyekQt9/2FOEqZoYNpRY0NFfx/tO9Q==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.2.8.tgz", + "integrity": "sha512-0qZs/iuRiYRir7bBkA7oN+21wwmSMPQuFK8WcAcxUYJZRhvnlrJ8Nid++PN4OCzTgHPV70GNFyXOajseVCCffA==", "dev": true, "license": "MIT", "dependencies": { - "@graphql-tools/utils": "^10.1.1", + "@graphql-tools/utils": "^10.2.3", "@graphql-typed-document-node/core": "3.2.0", "@repeaterjs/repeater": "^3.0.4", "tslib": "^2.4.0", @@ -2659,18 +2662,18 @@ } }, "node_modules/@graphql-tools/executor-graphql-ws": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-1.1.2.tgz", - "integrity": "sha512-+9ZK0rychTH1LUv4iZqJ4ESbmULJMTsv3XlFooPUngpxZkk00q6LqHKJRrsLErmQrVaC7cwQCaRBJa0teK17Lg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-graphql-ws/-/executor-graphql-ws-1.2.0.tgz", + "integrity": "sha512-tSYC1QdrabWexLrYV0UI3uRGbde9WCY/bRhq6Jc+VXMZcfq6ea6pP5NEAVTfwbhUQ4xZvJABVVbKXtKb9uTg1w==", "dev": true, "license": "MIT", "dependencies": { - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/utils": "^10.3.0", "@types/ws": "^8.0.0", "graphql-ws": "^5.14.0", "isomorphic-ws": "^5.0.0", "tslib": "^2.4.0", - "ws": "^8.13.0" + "ws": "^8.17.1" }, "engines": { "node": ">=16.0.0" @@ -2680,13 +2683,13 @@ } }, "node_modules/@graphql-tools/executor-http": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor-http/-/executor-http-1.0.9.tgz", - "integrity": "sha512-+NXaZd2MWbbrWHqU4EhXcrDbogeiCDmEbrAN+rMn4Nu2okDjn2MTFDbTIab87oEubQCH4Te1wDkWPKrzXup7+Q==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-http/-/executor-http-1.1.5.tgz", + "integrity": "sha512-ZAsVGUwafPc1GapLA1yoJuRx7ihpVdAv7JDHmlI2eHRQsJnMVQwcxHnjfUb/id9YAEBrP86/s4pgEoRyad3Zng==", "dev": true, "license": "MIT", "dependencies": { - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/utils": "^10.3.2", "@repeaterjs/repeater": "^3.0.4", "@whatwg-node/fetch": "^0.9.0", "extract-files": "^11.0.0", @@ -2726,9 +2729,9 @@ } }, "node_modules/@graphql-tools/executor-http/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.11.tgz", - "integrity": "sha512-LS8tSomZa3YHnntpWt3PP43iFEEl6YeIsvDakczHBKlay5LdkXFr8w7v8H6akpG5nRrzydyB0k1iE2eoL6aKIQ==", + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.12.tgz", + "integrity": "sha512-3wBgVdAOlS5jnITT6fbzQPfRs+rgYhyjwEeALlXfgg/0GvokNK5rPuh60iraS/x5UQXNcHSI3Vb+37B9RPSjrA==", "dev": true, "license": "MIT", "dependencies": { @@ -2736,7 +2739,7 @@ "@whatwg-node/events": "^0.1.0", "busboy": "^1.6.0", "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "tslib": "^2.6.3" }, "engines": { "node": ">=16.0.0" @@ -2750,17 +2753,17 @@ "license": "MIT" }, "node_modules/@graphql-tools/executor-legacy-ws": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@graphql-tools/executor-legacy-ws/-/executor-legacy-ws-1.0.6.tgz", - "integrity": "sha512-lDSxz9VyyquOrvSuCCnld3256Hmd+QI2lkmkEv7d4mdzkxkK4ddAWW1geQiWrQvWmdsmcnGGlZ7gDGbhEExwqg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/executor-legacy-ws/-/executor-legacy-ws-1.1.0.tgz", + "integrity": "sha512-k+6ZyiaAd8SmwuzbEOfA/LVkuI1nqidhoMw+CJ7c41QGOjSMzc0VS0UZbJyeitI0n7a+uP/Meln1wjzJ2ReDtQ==", "dev": true, "license": "MIT", "dependencies": { - "@graphql-tools/utils": "^10.0.13", + "@graphql-tools/utils": "^10.3.0", "@types/ws": "^8.0.0", "isomorphic-ws": "^5.0.0", "tslib": "^2.4.0", - "ws": "^8.15.0" + "ws": "^8.17.1" }, "engines": { "node": ">=16.0.0" @@ -2837,9 +2840,9 @@ } }, "node_modules/@graphql-tools/github-loader/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.11.tgz", - "integrity": "sha512-LS8tSomZa3YHnntpWt3PP43iFEEl6YeIsvDakczHBKlay5LdkXFr8w7v8H6akpG5nRrzydyB0k1iE2eoL6aKIQ==", + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.12.tgz", + "integrity": "sha512-3wBgVdAOlS5jnITT6fbzQPfRs+rgYhyjwEeALlXfgg/0GvokNK5rPuh60iraS/x5UQXNcHSI3Vb+37B9RPSjrA==", "dev": true, "license": "MIT", "dependencies": { @@ -2847,7 +2850,7 @@ "@whatwg-node/events": "^0.1.0", "busboy": "^1.6.0", "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "tslib": "^2.6.3" }, "engines": { "node": ">=16.0.0" @@ -3047,9 +3050,9 @@ } }, "node_modules/@graphql-tools/prisma-loader/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.11.tgz", - "integrity": "sha512-LS8tSomZa3YHnntpWt3PP43iFEEl6YeIsvDakczHBKlay5LdkXFr8w7v8H6akpG5nRrzydyB0k1iE2eoL6aKIQ==", + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.12.tgz", + "integrity": "sha512-3wBgVdAOlS5jnITT6fbzQPfRs+rgYhyjwEeALlXfgg/0GvokNK5rPuh60iraS/x5UQXNcHSI3Vb+37B9RPSjrA==", "dev": true, "license": "MIT", "dependencies": { @@ -3057,7 +3060,7 @@ "@whatwg-node/events": "^0.1.0", "busboy": "^1.6.0", "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "tslib": "^2.6.3" }, "engines": { "node": ">=16.0.0" @@ -3160,9 +3163,9 @@ } }, "node_modules/@graphql-tools/url-loader/node_modules/@whatwg-node/node-fetch": { - "version": "0.5.11", - "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.11.tgz", - "integrity": "sha512-LS8tSomZa3YHnntpWt3PP43iFEEl6YeIsvDakczHBKlay5LdkXFr8w7v8H6akpG5nRrzydyB0k1iE2eoL6aKIQ==", + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/@whatwg-node/node-fetch/-/node-fetch-0.5.12.tgz", + "integrity": "sha512-3wBgVdAOlS5jnITT6fbzQPfRs+rgYhyjwEeALlXfgg/0GvokNK5rPuh60iraS/x5UQXNcHSI3Vb+37B9RPSjrA==", "dev": true, "license": "MIT", "dependencies": { @@ -3170,7 +3173,7 @@ "@whatwg-node/events": "^0.1.0", "busboy": "^1.6.0", "fast-querystring": "^1.1.1", - "tslib": "^2.3.1" + "tslib": "^2.6.3" }, "engines": { "node": ">=16.0.0" @@ -3184,9 +3187,9 @@ "license": "MIT" }, "node_modules/@graphql-tools/utils": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.2.2.tgz", - "integrity": "sha512-ueoplzHIgFfxhFrF4Mf/niU/tYHuO6Uekm2nCYU72qpI+7Hn9dA2/o5XOBvFXDk27Lp5VSvQY5WfmRbqwVxaYQ==", + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-10.3.2.tgz", + "integrity": "sha512-iaqOHS4f90KNADBHqVsRBjKpM6iSvsUg1q5GhWMK03loYLaDzftrEwcsl0OkSSnRhJvAsT7q4q3r3YzRoV0v1g==", "dev": true, "license": "MIT", "dependencies": { @@ -3232,43 +3235,10 @@ "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@grpc/grpc-js": { - "version": "1.10.10", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.10.tgz", - "integrity": "sha512-HPa/K5NX6ahMoeBv15njAc/sfF4/jmiXLar9UlC2UfHFKZzsCVLc3wbe7+7qua7w9VPh2/L6EBxyAV7/E8Wftg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@grpc/proto-loader": "^0.7.13", - "@js-sdsl/ordered-map": "^4.4.2" - }, - "engines": { - "node": ">=12.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.7.13", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", - "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@hocuspocus/common": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/@hocuspocus/common/-/common-2.13.2.tgz", - "integrity": "sha512-NMsXx/Dl9xu1KlhNbGzLYLOjKtUOVmU+zA/br+EA4DhNDEtGWvHAmqY5r9SBTr2mGtGJagt+GEqZydSzBUFj4g==", + "version": "2.13.5", + "resolved": "https://registry.npmjs.org/@hocuspocus/common/-/common-2.13.5.tgz", + "integrity": "sha512-8D9FzhZFlt0WsgXw5yT2zwSxi6z9d4V2vUz6co2vo3Cj+Y2bvGZsdDiTvU/MerGcCLME5k/w6PwLPojLYH/4pg==", "dev": true, "license": "MIT", "dependencies": { @@ -3276,16 +3246,16 @@ } }, "node_modules/@hocuspocus/provider": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/@hocuspocus/provider/-/provider-2.13.2.tgz", - "integrity": "sha512-Pi+b8gcXHomSDRzohbmVW4dwo5OIqEBoLVrsuEG/pCCS13gBAeUCKCxfrl0q5XCNhK1nu3/j6UW9lxuExqiY+g==", + "version": "2.13.5", + "resolved": "https://registry.npmjs.org/@hocuspocus/provider/-/provider-2.13.5.tgz", + "integrity": "sha512-G3S0OiFSYkmbOwnbhV7FyJs4OBqB/+1YT9c44Ujux1RKowGm5H8+0p3FUHfXwd/3v9V0jE+E1FnFKoGonJSQwA==", "dev": true, "license": "MIT", "dependencies": { - "@hocuspocus/common": "^2.13.2", + "@hocuspocus/common": "^2.13.5", "@lifeomic/attempt": "^3.0.2", "lib0": "^0.2.87", - "ws": "^8.14.2" + "ws": "^8.17.1" }, "peerDependencies": { "y-protocols": "^1.0.6", @@ -3449,9 +3419,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "dev": true, "license": "MIT" }, @@ -3466,17 +3436,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@js-sdsl/ordered-map": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", - "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/@kamilkisiela/fast-url-parser": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@kamilkisiela/fast-url-parser/-/fast-url-parser-1.1.4.tgz", @@ -3567,9 +3526,9 @@ } }, "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "license": "ISC", "bin": { @@ -3580,13 +3539,13 @@ } }, "node_modules/@netlify/functions": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-2.8.0.tgz", - "integrity": "sha512-kHInQKtMuFlqD7vxaJ8tjd7spv6DTrRuTovvWNDmvwTfkubVfF7KYiypsPR5wkKvSz76GHv86RBCLkjIxvwgDg==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-2.8.1.tgz", + "integrity": "sha512-+6wtYdoz0yE06dSa9XkP47tw5zm6g13QMeCwM3MmHx1vn8hzwFa51JtmfraprdkL7amvb7gaNM+OOhQU1h6T8A==", "dev": true, "license": "MIT", "dependencies": { - "@netlify/serverless-functions-api": "1.18.4" + "@netlify/serverless-functions-api": "1.19.1" }, "engines": { "node": ">=14.0.0" @@ -3603,18 +3562,13 @@ } }, "node_modules/@netlify/serverless-functions-api": { - "version": "1.18.4", - "resolved": "https://registry.npmjs.org/@netlify/serverless-functions-api/-/serverless-functions-api-1.18.4.tgz", - "integrity": "sha512-5R0kOKrOqhlFFrA7oduzJS+LQRjnX2CX8kJaYI9PQKIpNvzF18n+LNGWTS42YxPfIpAE64yaHbppeAigms2QTw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@netlify/serverless-functions-api/-/serverless-functions-api-1.19.1.tgz", + "integrity": "sha512-2KYkyluThg1AKfd0JWI7FzpS4A/fzVVGYIf6AM4ydWyNj8eI/86GQVLeRgDoH7CNOxt243R5tutWlmHpVq0/Ew==", "dev": true, "license": "MIT", "dependencies": { "@netlify/node-cookies": "^0.1.0", - "@opentelemetry/core": "^1.23.0", - "@opentelemetry/otlp-transformer": "^0.52.0", - "@opentelemetry/resources": "^1.23.0", - "@opentelemetry/sdk-node": "^0.52.0", - "@opentelemetry/sdk-trace-node": "^1.24.1", "urlpattern-polyfill": "8.0.2" }, "engines": { @@ -3659,403 +3613,171 @@ "node": ">= 8" } }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": ">= 18" } }, - "node_modules/@opentelemetry/api-logs": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.52.1.tgz", - "integrity": "sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A==", + "node_modules/@octokit/core": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", + "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@opentelemetry/api": "^1.0.0" + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">=14" + "node": ">= 18" } }, - "node_modules/@opentelemetry/context-async-hooks": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.25.1.tgz", - "integrity": "sha512-UW/ge9zjvAEmRWVapOP0qyCvPulWU6cQxGxDbWEFfGOj1VBBZAuOqTo3X6yWmDTD3Xe15ysCZChHncr2xFMIfQ==", + "node_modules/@octokit/endpoint": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.5.tgz", + "integrity": "sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, "engines": { - "node": ">=14" + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.0.tgz", + "integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.3.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.1.tgz", + "integrity": "sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "@octokit/core": "5" } }, - "node_modules/@opentelemetry/core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", - "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", + "node_modules/@octokit/plugin-request-log": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz", + "integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.25.1" - }, + "license": "MIT", "engines": { - "node": ">=14" + "node": ">= 18" }, "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" + "@octokit/core": "5" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.52.1.tgz", - "integrity": "sha512-pVkSH20crBwMTqB3nIN4jpQKUEoB0Z94drIHpYyEqs7UBr+I0cpYyOR3bqjA/UasQUMROb3GX8ZX4/9cVRqGBQ==", + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.2.2.tgz", + "integrity": "sha512-EI7kXWidkt3Xlok5uN43suK99VWqc8OaIMktY9d9+RNKl69juoTyxmLoWPIZgJYzi41qj/9zU7G/ljnNOJ5AFA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.25.1", - "@opentelemetry/otlp-grpc-exporter-base": "0.52.1", - "@opentelemetry/otlp-transformer": "0.52.1", - "@opentelemetry/resources": "1.25.1", - "@opentelemetry/sdk-trace-base": "1.25.1" + "@octokit/types": "^13.5.0" }, "engines": { - "node": ">=14" + "node": ">= 18" }, "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "@octokit/core": "^5" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-http": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.52.1.tgz", - "integrity": "sha512-05HcNizx0BxcFKKnS5rwOV+2GevLTVIRA0tRgWYyw4yCgR53Ic/xk83toYKts7kbzcI+dswInUg/4s8oyA+tqg==", + "node_modules/@octokit/request": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz", + "integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/otlp-exporter-base": "0.52.1", - "@opentelemetry/otlp-transformer": "0.52.1", - "@opentelemetry/resources": "1.25.1", - "@opentelemetry/sdk-trace-base": "1.25.1" + "@octokit/endpoint": "^9.0.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "node": ">= 18" } }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.52.1.tgz", - "integrity": "sha512-pt6uX0noTQReHXNeEslQv7x311/F1gJzMnp1HD2qgypLRPbXDeMzzeTngRTUaUbP6hqWNtPxuLr4DEoZG+TcEQ==", + "node_modules/@octokit/request-error": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz", + "integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/otlp-exporter-base": "0.52.1", - "@opentelemetry/otlp-transformer": "0.52.1", - "@opentelemetry/resources": "1.25.1", - "@opentelemetry/sdk-trace-base": "1.25.1" + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "node": ">= 18" } }, - "node_modules/@opentelemetry/exporter-zipkin": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-1.25.1.tgz", - "integrity": "sha512-RmOwSvkimg7ETwJbUOPTMhJm9A9bG1U8s7Zo3ajDh4zM7eYcycQ0dM7FbLD6NXWbI2yj7UY4q8BKinKYBQksyw==", + "node_modules/@octokit/rest": { + "version": "20.1.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.1.tgz", + "integrity": "sha512-MB4AYDsM5jhIHro/dq4ix1iWTLGToIGk6cWF5L6vanFaMble5jTX/UBQyiv05HsWnwUtY8JrfHy2LWfKwihqMw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1", - "@opentelemetry/sdk-trace-base": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" + "@octokit/core": "^5.0.2", + "@octokit/plugin-paginate-rest": "11.3.1", + "@octokit/plugin-request-log": "^4.0.0", + "@octokit/plugin-rest-endpoint-methods": "13.2.2" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" + "node": ">= 18" } }, - "node_modules/@opentelemetry/instrumentation": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.52.1.tgz", - "integrity": "sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw==", + "node_modules/@octokit/types": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", + "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@opentelemetry/api-logs": "0.52.1", - "@types/shimmer": "^1.0.2", - "import-in-the-middle": "^1.8.1", - "require-in-the-middle": "^7.1.1", - "semver": "^7.5.2", - "shimmer": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/instrumentation/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.52.1.tgz", - "integrity": "sha512-z175NXOtX5ihdlshtYBe5RpGeBoTXVCKPPLiQlD6FHvpM4Ch+p2B0yWKYSrBfLH24H9zjJiBdTrtD+hLlfnXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/otlp-transformer": "0.52.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.52.1.tgz", - "integrity": "sha512-zo/YrSDmKMjG+vPeA9aBBrsQM9Q/f2zo6N04WMB3yNldJRsgpRBeLLwvAt/Ba7dpehDLOEFBd1i2JCoaFtpCoQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.7.1", - "@opentelemetry/core": "1.25.1", - "@opentelemetry/otlp-exporter-base": "0.52.1", - "@opentelemetry/otlp-transformer": "0.52.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.52.1.tgz", - "integrity": "sha512-I88uCZSZZtVa0XniRqQWKbjAUm73I8tpEy/uJYPPYw5d7BRdVk0RfTBQw8kSUl01oVWEuqxLDa802222MYyWHg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.52.1", - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1", - "@opentelemetry/sdk-logs": "0.52.1", - "@opentelemetry/sdk-metrics": "1.25.1", - "@opentelemetry/sdk-trace-base": "1.25.1", - "protobufjs": "^7.3.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-b3": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.25.1.tgz", - "integrity": "sha512-p6HFscpjrv7//kE+7L+3Vn00VEDUJB0n6ZrjkTYHrJ58QZ8B3ajSJhRbCcY6guQ3PDjTbxWklyvIN2ojVbIb1A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-jaeger": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.25.1.tgz", - "integrity": "sha512-nBprRf0+jlgxks78G/xq72PipVK+4or9Ypntw0gVZYNTCSK8rg5SeaGV19tV920CMqBD/9UIOiFr23Li/Q8tiA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/resources": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", - "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-logs": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.52.1.tgz", - "integrity": "sha512-MBYh+WcPPsN8YpRHRmK1Hsca9pVlyyKd4BxOC4SsgHACnl/bPp4Cri9hWhVm5+2tiQ9Zf4qSc1Jshw9tOLGWQA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.52.1", - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-metrics": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.25.1.tgz", - "integrity": "sha512-9Mb7q5ioFL4E4dDrc4wC/A3NTHDat44v4I3p2pLPSxRvqUbDIQyMVr9uK+EU69+HWhlET1VaSrRzwdckWqY15Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1", - "lodash.merge": "^4.6.2" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-node": { - "version": "0.52.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-node/-/sdk-node-0.52.1.tgz", - "integrity": "sha512-uEG+gtEr6eKd8CVWeKMhH2olcCHM9dEK68pe0qE0be32BcCRsvYURhHaD1Srngh1SQcnQzZ4TP324euxqtBOJA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.52.1", - "@opentelemetry/core": "1.25.1", - "@opentelemetry/exporter-trace-otlp-grpc": "0.52.1", - "@opentelemetry/exporter-trace-otlp-http": "0.52.1", - "@opentelemetry/exporter-trace-otlp-proto": "0.52.1", - "@opentelemetry/exporter-zipkin": "1.25.1", - "@opentelemetry/instrumentation": "0.52.1", - "@opentelemetry/resources": "1.25.1", - "@opentelemetry/sdk-logs": "0.52.1", - "@opentelemetry/sdk-metrics": "1.25.1", - "@opentelemetry/sdk-trace-base": "1.25.1", - "@opentelemetry/sdk-trace-node": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.25.1.tgz", - "integrity": "sha512-C8k4hnEbc5FamuZQ92nTOp8X/diCY56XUTnMiv9UTuJitCzaNNHAVsdm5+HLCdI8SLQsLWIrG38tddMxLVoftw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-node": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.25.1.tgz", - "integrity": "sha512-nMcjFIKxnFqoez4gUmihdBrbpsEnAX/Xj16sGvZm+guceYE0NE00vLhpDVK6f3q8Q4VFI5xG8JjlXKMB/SkTTQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/context-async-hooks": "1.25.1", - "@opentelemetry/core": "1.25.1", - "@opentelemetry/propagator-b3": "1.25.1", - "@opentelemetry/propagator-jaeger": "1.25.1", - "@opentelemetry/sdk-trace-base": "1.25.1", - "semver": "^7.5.2" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-node/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", - "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14" + "@octokit/openapi-types": "^22.2.0" } }, "node_modules/@parcel/watcher": { @@ -4426,13 +4148,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.0.tgz", - "integrity": "sha512-TVYsfMlGAaxeUllNkywbwek67Ncf8FRGn8ZlRdO291OL3NjG9oMbfVhyP82HQF0CZLMrYsvesqoUekxdWuF9Qw==", + "version": "1.45.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.2.tgz", + "integrity": "sha512-JxG9eq92ET75EbVi3s+4sYbcG7q72ECeZNbdBlaMkGcNbiDQ4cAi8U2QP5oKkOx+1gpaiL1LDStmzCaEM1Z6fQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.45.0" + "playwright": "1.45.2" }, "bin": { "playwright": "cli.js" @@ -4459,80 +4181,6 @@ "url": "https://opencollective.com/popperjs" } }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/@remirror/core-constants": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-2.0.2.tgz", @@ -4790,9 +4438,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", - "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.0.tgz", + "integrity": "sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w==", "cpu": [ "arm" ], @@ -4804,9 +4452,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", - "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.0.tgz", + "integrity": "sha512-RDxUSY8D1tWYfn00DDi5myxKgOk6RvWPxhmWexcICt/MEC6yEMr4HNCu1sXXYLw8iAsg0D44NuU+qNq7zVWCrw==", "cpu": [ "arm64" ], @@ -4818,9 +4466,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", - "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.0.tgz", + "integrity": "sha512-emvKHL4B15x6nlNTBMtIaC9tLPRpeA5jMvRLXVbl/W9Ie7HhkrE7KQjvgS9uxgatL1HmHWDXk5TTS4IaNJxbAA==", "cpu": [ "arm64" ], @@ -4832,9 +4480,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", - "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.0.tgz", + "integrity": "sha512-fO28cWA1dC57qCd+D0rfLC4VPbh6EOJXrreBmFLWPGI9dpMlER2YwSPZzSGfq11XgcEpPukPTfEVFtw2q2nYJg==", "cpu": [ "x64" ], @@ -4846,9 +4494,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", - "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.0.tgz", + "integrity": "sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw==", "cpu": [ "arm" ], @@ -4860,9 +4508,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", - "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.0.tgz", + "integrity": "sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ==", "cpu": [ "arm" ], @@ -4874,9 +4522,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", - "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.0.tgz", + "integrity": "sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug==", "cpu": [ "arm64" ], @@ -4888,9 +4536,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", - "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.0.tgz", + "integrity": "sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ==", "cpu": [ "arm64" ], @@ -4902,9 +4550,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", - "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.0.tgz", + "integrity": "sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ==", "cpu": [ "ppc64" ], @@ -4916,9 +4564,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", - "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.0.tgz", + "integrity": "sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg==", "cpu": [ "riscv64" ], @@ -4930,9 +4578,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", - "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.0.tgz", + "integrity": "sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA==", "cpu": [ "s390x" ], @@ -4944,9 +4592,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", - "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.0.tgz", + "integrity": "sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA==", "cpu": [ "x64" ], @@ -4958,9 +4606,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", - "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.0.tgz", + "integrity": "sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A==", "cpu": [ "x64" ], @@ -4972,9 +4620,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", - "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.0.tgz", + "integrity": "sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg==", "cpu": [ "arm64" ], @@ -4986,9 +4634,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", - "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.0.tgz", + "integrity": "sha512-xItlIAZZaiG/u0wooGzRsx11rokP4qyc/79LkAOdznGRAbOFc+SfEdfUOszG1odsHNgwippUJavag/+W/Etc6Q==", "cpu": [ "ia32" ], @@ -5000,9 +4648,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", - "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.0.tgz", + "integrity": "sha512-xNo5fV5ycvCCKqiZcpB65VMR11NJB+StnxHz20jdqRAktfdfzhgjTiJ2doTDQE/7dqGaV5I7ZGqKpgph6lCIag==", "cpu": [ "x64" ], @@ -5026,120 +4674,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@solid-devtools/debugger": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@solid-devtools/debugger/-/debugger-0.18.1.tgz", - "integrity": "sha512-PFbf3+t5ua/v9HpnYOo24NIIM/pf/ZUFYF26YWy8qq2ddRbkKL87nmG5gDGvtgK7lVV3GM3O01fdq4YnSG1SYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@solid-devtools/shared": "^0.10.5", - "@solid-primitives/bounds": "^0.0.105", - "@solid-primitives/cursor": "^0.0.103", - "@solid-primitives/event-bus": "^0.1.3", - "@solid-primitives/event-listener": "^2.2.3", - "@solid-primitives/keyboard": "^1.0.3", - "@solid-primitives/platform": "^0.0.102", - "@solid-primitives/scheduled": "^1.2.0", - "@solid-primitives/utils": "^4.0.0", - "type-fest": "^3.2.0" - }, - "peerDependencies": { - "solid-js": "^1.6.2" - } - }, - "node_modules/@solid-devtools/shared": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/@solid-devtools/shared/-/shared-0.10.6.tgz", - "integrity": "sha512-UfLERQMxULRl2GoNc/Y1fMkRp71uTk/56dksowRLMdsyFpH3vqKr3+AHfm/2tXI/XeRAvXsXhC90zpVff5y+Hg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@solid-primitives/event-bus": "^0.1.3", - "@solid-primitives/event-listener": "^2.2.3", - "@solid-primitives/media": "^2.0.3", - "@solid-primitives/refs": "^0.3.4", - "@solid-primitives/rootless": "^1.2.0", - "@solid-primitives/scheduled": "^1.1.0", - "@solid-primitives/styles": "^0.0.101", - "@solid-primitives/utils": "^4.0.0", - "type-fest": "^3.2.0" - }, - "peerDependencies": { - "solid-js": "^1.6.2" - } - }, - "node_modules/@solid-devtools/transform": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/@solid-devtools/transform/-/transform-0.10.4.tgz", - "integrity": "sha512-r8JzHMmBFgaFy+FQVQdvNpTX8L3zwuiW1/puV3VHyaw1FpODmdmkbOnQgUQgHqN/X1LLPzTMtVGKLcDmJJOQbQ==", - "deprecated": "vite plugin has been moved entirely to 'solid-devtools' pacakge.", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.20.7", - "@babel/plugin-syntax-typescript": "^7.18.6", - "@babel/types": "^7.20.7", - "@solid-devtools/debugger": "^0.18.0", - "@solid-devtools/shared": "^0.10.5" - }, - "peerDependencies": { - "solid-js": "^1.6.2", - "vite": "^2.2.3 || ^3.0.0 || ^4.0.0" - } - }, - "node_modules/@solid-primitives/bounds": { - "version": "0.0.105", - "resolved": "https://registry.npmjs.org/@solid-primitives/bounds/-/bounds-0.0.105.tgz", - "integrity": "sha512-a2ZRuZayXV1/kSKx8cEOR5pIs2zKAF9lS3Gj/r7uHmBQBmn25GYCYOwj4LbLQbqqbumZr2eJO+/wDyi4UOX5pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@solid-primitives/event-listener": "^2.2.4", - "@solid-primitives/resize-observer": "^2.0.6", - "@solid-primitives/utils": "^4.0.0" - }, - "peerDependencies": { - "solid-js": "^1.6.0" - } - }, - "node_modules/@solid-primitives/cursor": { - "version": "0.0.103", - "resolved": "https://registry.npmjs.org/@solid-primitives/cursor/-/cursor-0.0.103.tgz", - "integrity": "sha512-bb5x5lCimBf7R2VqrrMVcP2y/aGTMjNj7fjvY+RvTAC3/WtG/odqeYwka4lCBV27pX9TiJCKtNS6mVTigdfLMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@solid-primitives/utils": "^4.0.0" - }, - "peerDependencies": { - "solid-js": "^1.6.0" - } - }, - "node_modules/@solid-primitives/event-bus": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@solid-primitives/event-bus/-/event-bus-0.1.6.tgz", - "integrity": "sha512-UGtBU5zDyjSYnX0BjaYFcs1XfRD6BDN6VEJi4ydxePaUEKlloOG53BsLZjFgTux8cMEmJAaHjoJQH3/SBt3zcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@solid-primitives/immutable": "^0.1.6", - "@solid-primitives/utils": "^5.0.0" - }, - "peerDependencies": { - "solid-js": "^1.6.0" - } - }, - "node_modules/@solid-primitives/event-bus/node_modules/@solid-primitives/utils": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-5.5.2.tgz", - "integrity": "sha512-L52ig3eHKU6CqbPCKJIb4lweBuINHBOERcE1duApyKozEN8+zCqEKwD1Qo9ljKeEzJTBGWClxNpwEiNTUWTGvg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, "node_modules/@solid-primitives/event-listener": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/@solid-primitives/event-listener/-/event-listener-2.3.3.tgz", @@ -5153,64 +4687,6 @@ "solid-js": "^1.6.12" } }, - "node_modules/@solid-primitives/event-listener/node_modules/@solid-primitives/utils": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz", - "integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, - "node_modules/@solid-primitives/immutable": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/@solid-primitives/immutable/-/immutable-0.1.10.tgz", - "integrity": "sha512-5XkiiBGSuUaoG2HFei1bG2eDyUwsj/b6IBP7ggm4UMU5mM/APz1u7e3rzMyPn5zVqnHT3oRSbj3UDPNYb5Y9Qg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@solid-primitives/utils": "^6.0.0" - }, - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, - "node_modules/@solid-primitives/immutable/node_modules/@solid-primitives/utils": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz", - "integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, - "node_modules/@solid-primitives/keyboard": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@solid-primitives/keyboard/-/keyboard-1.2.8.tgz", - "integrity": "sha512-pJtcbkjozS6L1xvTht9rPpyPpX55nAkfBzbFWdf3y0Suwh6qClTibvvObzKOf7uzQ+8aZRDH4LsoGmbTKXtJjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@solid-primitives/event-listener": "^2.3.3", - "@solid-primitives/rootless": "^1.4.5", - "@solid-primitives/utils": "^6.2.3" - }, - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, - "node_modules/@solid-primitives/keyboard/node_modules/@solid-primitives/utils": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz", - "integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, "node_modules/@solid-primitives/media": { "version": "2.2.9", "resolved": "https://registry.npmjs.org/@solid-primitives/media/-/media-2.2.9.tgz", @@ -5227,20 +4703,10 @@ "solid-js": "^1.6.12" } }, - "node_modules/@solid-primitives/media/node_modules/@solid-primitives/utils": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz", - "integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, "node_modules/@solid-primitives/memo": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@solid-primitives/memo/-/memo-1.3.8.tgz", - "integrity": "sha512-U75pfLFSxFmM2xbx1+2XPPyWbaXrnUFF10spbFuOUgJ7azrC+4y+FnrVi4RKqHw9gftd8aKQuTiyMQq468YLQw==", + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@solid-primitives/memo/-/memo-1.3.9.tgz", + "integrity": "sha512-OIlGcsEPXr6U23fDaSEm8iqezWDT4SNSjLTekWQVOu4qHostnMPlqU8xo1Jf0I7OgRuqA0wTMQ5cu0bYjeR/ZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5251,16 +4717,6 @@ "solid-js": "^1.6.12" } }, - "node_modules/@solid-primitives/memo/node_modules/@solid-primitives/utils": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz", - "integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, "node_modules/@solid-primitives/pagination": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@solid-primitives/pagination/-/pagination-0.3.0.tgz", @@ -5274,77 +4730,19 @@ "solid-js": "^1.6.12" } }, - "node_modules/@solid-primitives/pagination/node_modules/@solid-primitives/utils": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz", - "integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, - "node_modules/@solid-primitives/platform": { - "version": "0.0.102", - "resolved": "https://registry.npmjs.org/@solid-primitives/platform/-/platform-0.0.102.tgz", - "integrity": "sha512-1eZA1/HYOhmlZ9LrrGot+LUi/ypO2NXqfB+9F1WY98dGNDMz9pG9k+X7kg2YDJTUHDGbzY7WrsBRyAE8LurE7Q==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.5.0" - } - }, "node_modules/@solid-primitives/refs": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/@solid-primitives/refs/-/refs-0.3.7.tgz", - "integrity": "sha512-aqidj/Sw5b2FvXgvNP8zH5HC2jEzDbFju+xRUCxZguaBmDJJyzec12fpZ9JV6SiWCyk08nZ/N4rfPNQnt1af1Q==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@solid-primitives/refs/-/refs-1.0.8.tgz", + "integrity": "sha512-+jIsWG8/nYvhaCoG2Vg6CJOLgTmPKFbaCrNQKWfChalgUf9WrVxWw0CdJb3yX15n5lUcQ0jBo6qYtuVVmBLpBw==", "dev": true, "license": "MIT", "dependencies": { - "@solid-primitives/immutable": "^0.1.7", - "@solid-primitives/rootless": "^1.2.5", - "@solid-primitives/utils": "^5.2.1" - }, - "peerDependencies": { - "solid-js": "^1.6.0" - } - }, - "node_modules/@solid-primitives/refs/node_modules/@solid-primitives/utils": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-5.5.2.tgz", - "integrity": "sha512-L52ig3eHKU6CqbPCKJIb4lweBuINHBOERcE1duApyKozEN8+zCqEKwD1Qo9ljKeEzJTBGWClxNpwEiNTUWTGvg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, - "node_modules/@solid-primitives/resize-observer": { - "version": "2.0.25", - "resolved": "https://registry.npmjs.org/@solid-primitives/resize-observer/-/resize-observer-2.0.25.tgz", - "integrity": "sha512-jVDXkt2MiriYRaz4DYs62185d+6jQ+1DCsR+v7f6XMsIJJuf963qdBRFjtZtKXBaxdPNMyuPeDgf5XQe3EoDJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@solid-primitives/event-listener": "^2.3.3", - "@solid-primitives/rootless": "^1.4.5", - "@solid-primitives/static-store": "^0.0.8", "@solid-primitives/utils": "^6.2.3" }, "peerDependencies": { "solid-js": "^1.6.12" } }, - "node_modules/@solid-primitives/resize-observer/node_modules/@solid-primitives/utils": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz", - "integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, "node_modules/@solid-primitives/rootless": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/@solid-primitives/rootless/-/rootless-1.4.5.tgz", @@ -5358,20 +4756,20 @@ "solid-js": "^1.6.12" } }, - "node_modules/@solid-primitives/rootless/node_modules/@solid-primitives/utils": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz", - "integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==", + "node_modules/@solid-primitives/scheduled": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@solid-primitives/scheduled/-/scheduled-1.4.3.tgz", + "integrity": "sha512-HfWN5w7b7FEc6VPLBKnnE302h90jsLMuR28Fcf7neRGGf8jBj6wm6/UFQ00VlKexHFMR6KQ2u4VBh5a1ZcqM8g==", "dev": true, "license": "MIT", "peerDependencies": { "solid-js": "^1.6.12" } }, - "node_modules/@solid-primitives/scheduled": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@solid-primitives/scheduled/-/scheduled-1.4.3.tgz", - "integrity": "sha512-HfWN5w7b7FEc6VPLBKnnE302h90jsLMuR28Fcf7neRGGf8jBj6wm6/UFQ00VlKexHFMR6KQ2u4VBh5a1ZcqM8g==", + "node_modules/@solid-primitives/script-loader": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@solid-primitives/script-loader/-/script-loader-2.2.0.tgz", + "integrity": "sha512-PlQEcdpvhtWUsceDp1Miyn49v74QNx/Xv7bpdqVTL6GvvQNuXF0qtpDrOZ+jZvOQiHLlnzNWrBk6YdWwenqcnw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -5401,20 +4799,10 @@ "solid-js": "^1.6.12" } }, - "node_modules/@solid-primitives/static-store/node_modules/@solid-primitives/utils": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz", - "integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, "node_modules/@solid-primitives/storage": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@solid-primitives/storage/-/storage-3.7.1.tgz", - "integrity": "sha512-tAmZKQg44RjDjrtWO/5hCOrktQspn/yVV0ySb7yKr7B3CVQlTQtldw3W8UetytJSD9podb9cplvvkq75fgpB1Q==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@solid-primitives/storage/-/storage-3.8.0.tgz", + "integrity": "sha512-FNzsl60/zrMA2blDIjh2i6k2fnKECn870UssaKg5y9+wrXTJlce8vv0CxC3Mw7uHZnnPnDQGNyuBqeJ5mRZkOA==", "dev": true, "license": "MIT", "dependencies": { @@ -5433,29 +4821,6 @@ } } }, - "node_modules/@solid-primitives/storage/node_modules/@solid-primitives/utils": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz", - "integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, - "node_modules/@solid-primitives/styles": { - "version": "0.0.101", - "resolved": "https://registry.npmjs.org/@solid-primitives/styles/-/styles-0.0.101.tgz", - "integrity": "sha512-tHkeUMntlS/w+/zDzXJv82hhMy3J3q7tVV3ZTbahRo0hZienAM8ZJrCYZNK/fu2px8NHVSZFRufxv9qhIclPTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@solid-primitives/rootless": "^1.2.0" - }, - "peerDependencies": { - "solid-js": "^1.5.0" - } - }, "node_modules/@solid-primitives/transition-group": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@solid-primitives/transition-group/-/transition-group-1.0.5.tgz", @@ -5479,7 +4844,7 @@ "solid-js": "^1.6.12" } }, - "node_modules/@solid-primitives/upload/node_modules/@solid-primitives/utils": { + "node_modules/@solid-primitives/utils": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz", "integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==", @@ -5489,16 +4854,6 @@ "solid-js": "^1.6.12" } }, - "node_modules/@solid-primitives/utils": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-4.0.1.tgz", - "integrity": "sha512-06fSyBair7ZxCquMjIqJes29aNg65X776TVw4EUN7PBtdWsGUeIZ9F/H4ek7yrDSxaSDaPHeye5knEYsYAq2gA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.0" - } - }, "node_modules/@solidjs/meta": { "version": "0.29.4", "resolved": "https://registry.npmjs.org/@solidjs/meta/-/meta-0.29.4.tgz", @@ -5510,9 +4865,9 @@ } }, "node_modules/@solidjs/router": { - "version": "0.13.6", - "resolved": "https://registry.npmjs.org/@solidjs/router/-/router-0.13.6.tgz", - "integrity": "sha512-CdpFsBYoiJ/FQ4wZIamj3KEFRkmrYu5sVXM6PouNkmSENta1YJamsm9wa/VjaPmkw2RsnDnO0UvZ705v6EgOXQ==", + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@solidjs/router/-/router-0.14.1.tgz", + "integrity": "sha512-GumQ4jbt5xDngLypAndC4EjapY/3DP0G8Az4YWEVQHdCtjHwB8IOm32eEBxE9lKpOffbtXV0r/0X0mofHJ1m5w==", "dev": true, "license": "MIT", "peerDependencies": { @@ -5520,15 +4875,15 @@ } }, "node_modules/@solidjs/start": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@solidjs/start/-/start-1.0.2.tgz", - "integrity": "sha512-SRoFvSnL47O1hg4/Er2es2AVoOWRdGBm0C8h2KsHFjbiRe1r2AxsONHR9job+Hmzbl5icSaGmNoXoQ8qpTy4UA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@solidjs/start/-/start-1.0.6.tgz", + "integrity": "sha512-O5knaeqDBx+nKLJRm5ZJurnXZtIYBOwOreQ10APaVtVjKIKKRC5HxJ1Kwqg7atOQNNDgsF0pzhW218KseaZ1UA==", "dev": true, "license": "MIT", "dependencies": { - "@vinxi/plugin-directives": "^0.3.1", - "@vinxi/server-components": "^0.3.3", - "@vinxi/server-functions": "^0.3.2", + "@vinxi/plugin-directives": "^0.4.1", + "@vinxi/server-components": "^0.4.1", + "@vinxi/server-functions": "^0.4.1", "defu": "^6.1.2", "error-stack-parser": "^2.1.4", "glob": "^10.3.10", @@ -5554,9 +4909,9 @@ } }, "node_modules/@solidjs/start/node_modules/glob": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", - "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { @@ -5570,9 +4925,6 @@ "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } @@ -5604,9 +4956,9 @@ } }, "node_modules/@tiptap/core": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.4.0.tgz", - "integrity": "sha512-YJSahk8pkxpCs8SflCZfTnJpE7IPyUWIylfgXM2DefjRQa5DZ+c6sNY0s/zbxKYFQ6AuHVX40r9pCfcqHChGxQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.5.4.tgz", + "integrity": "sha512-Zs/hShr4+W02+0nOlpmr5cS2YjDRLqd+XMt+jsiQH0QNr3s1Lc82pfF6C3CjgLEZtdUzImZrW2ABtLlpvbogaA==", "dev": true, "license": "MIT", "funding": { @@ -5614,13 +4966,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/pm": "^2.0.0" + "@tiptap/pm": "^2.5.4" } }, "node_modules/@tiptap/extension-blockquote": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.4.0.tgz", - "integrity": "sha512-nJJy4KsPgQqWTTDOWzFRdjCfG5+QExfZj44dulgDFNh+E66xhamnbM70PklllXJgEcge7xmT5oKM0gKls5XgFw==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.5.4.tgz", + "integrity": "sha512-UqeJunZM3IiCQGZE0X5YNUOWYkuIieqrwPgOEghAIjnhDcQizQcouRQ5R7cwwv/scNr2JvZHncOTLrALV3Janw==", "dev": true, "license": "MIT", "funding": { @@ -5628,13 +4980,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-bold": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.4.0.tgz", - "integrity": "sha512-csnW6hMDEHoRfxcPRLSqeJn+j35Lgtt1YRiOwn7DlS66sAECGRuoGfCvQSPij0TCDp4VCR9if5Sf8EymhnQumQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.5.4.tgz", + "integrity": "sha512-H5sjqloFMjq7VOSfE+U4T7dqGoflOiF6RW6/gZm/U6KYeHG2/bG0ktq7mWAnnhbiKiy7gUcxyJCV+ILdGX9C5g==", "dev": true, "license": "MIT", "funding": { @@ -5642,13 +4994,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-bubble-menu": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.4.0.tgz", - "integrity": "sha512-s99HmttUtpW3rScWq8rqk4+CGCwergNZbHLTkF6Rp6TSboMwfp+rwL5Q/JkcAG9KGLso1vGyXKbt1xHOvm8zMw==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.5.4.tgz", + "integrity": "sha512-GHwef912K1yd75pp9JGDnKSp1DvdOHH8BcHQv0no+a3q2ePFPYcgaSwVRR59jHRX9WzdVfoLcqDSAeoNGOrISw==", "dev": true, "license": "MIT", "dependencies": { @@ -5659,14 +5011,14 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0", - "@tiptap/pm": "^2.0.0" + "@tiptap/core": "^2.5.4", + "@tiptap/pm": "^2.5.4" } }, "node_modules/@tiptap/extension-bullet-list": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.4.0.tgz", - "integrity": "sha512-9S5DLIvFRBoExvmZ+/ErpTvs4Wf1yOEs8WXlKYUCcZssK7brTFj99XDwpHFA29HKDwma5q9UHhr2OB2o0JYAdw==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.5.4.tgz", + "integrity": "sha512-aAfpALeD6OxymkbtrzDqbgkAkzVVHudxOb8GsK1N6m42nFL7Q9JzHJ5/8KzB+xi25CcIbS+HmXJkRIQJXgNbSA==", "dev": true, "license": "MIT", "funding": { @@ -5674,13 +5026,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-character-count": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.4.0.tgz", - "integrity": "sha512-IA3Fubvag5N/7m2xS/T8D1nH26UyebBL9CtZ3/4de4faKgvDQLlILsSI2Hefi7j7rUCYCYzgF2S0Gny+Z76ulw==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-character-count/-/extension-character-count-2.5.4.tgz", + "integrity": "sha512-6qwt+81I+y+t3eoFPmCG2ouQce2RccwyiUC0ZOPTG1eUB+5yXmyIwBYI4aOM4TEfxNizyaZtQw32CDdAhMr3YA==", "dev": true, "license": "MIT", "funding": { @@ -5688,14 +5040,14 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0", - "@tiptap/pm": "^2.0.0" + "@tiptap/core": "^2.5.4", + "@tiptap/pm": "^2.5.4" } }, "node_modules/@tiptap/extension-collaboration": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-2.4.0.tgz", - "integrity": "sha512-achU+GU9tqxn3zsU61CbwWrCausf0U23MJIpo8vnywOIx6E955by6okHEHoUazLIGVFXVc5DBzBP7bf+Snzk0Q==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration/-/extension-collaboration-2.5.4.tgz", + "integrity": "sha512-CpQdbr7XpQaVqRFo/A1DchrQZMDb8vrkP+FcUIgvHN0b8hwKDmXRAHDtuk8yTTEatW1EqpX8lx8UxaUTcDNbIg==", "dev": true, "license": "MIT", "funding": { @@ -5703,15 +5055,15 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0", - "@tiptap/pm": "^2.0.0", - "y-prosemirror": "^1.2.5" + "@tiptap/core": "^2.5.4", + "@tiptap/pm": "^2.5.4", + "y-prosemirror": "^1.2.6" } }, "node_modules/@tiptap/extension-collaboration-cursor": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.4.0.tgz", - "integrity": "sha512-BTVy9FCTGdHxYieJ4lteVLrRY5qAPQyfunhMwakVf1NT3iU9quE6CaeaIwt6wEDJPMPPKzOHg1/ltSz9nIDe4A==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-collaboration-cursor/-/extension-collaboration-cursor-2.5.4.tgz", + "integrity": "sha512-M32JChnP5RVdr1n+Tf0gF9bxx0gHvc0uV4SDxCMN3uaNH5YpcofmvKElS60rDGVfCdRTId/aj7P3AtwrvRlYdQ==", "dev": true, "license": "MIT", "funding": { @@ -5719,14 +5071,14 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0", - "y-prosemirror": "^1.2.5" + "@tiptap/core": "^2.5.4", + "y-prosemirror": "^1.2.6" } }, "node_modules/@tiptap/extension-document": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.4.0.tgz", - "integrity": "sha512-3jRodQJZDGbXlRPERaloS+IERg/VwzpC1IO6YSJR9jVIsBO6xC29P3cKTQlg1XO7p6ZH/0ksK73VC5BzzTwoHg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.5.4.tgz", + "integrity": "sha512-4RDrhASxCTOZETYhIhEW1TfZqx3Tm+LQxouvBMFyODmT1PSgsg5Xz1FYpDPr+J49bGAK0Pr9ae0XcGW011L3sA==", "dev": true, "license": "MIT", "funding": { @@ -5734,13 +5086,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-dropcursor": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.4.0.tgz", - "integrity": "sha512-c46HoG2PEEpSZv5rmS5UX/lJ6/kP1iVO0Ax+6JrNfLEIiDULUoi20NqdjolEa38La2VhWvs+o20OviiTOKEE9g==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.5.4.tgz", + "integrity": "sha512-jzSnuuYhlc0SsHvAteWkE9TJy3eRwkxQs4MO2JxALOzJECN4G82nlX8vciihBD6xf7lVgVSBACejK9+rsTHqCg==", "dev": true, "license": "MIT", "funding": { @@ -5748,14 +5100,14 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0", - "@tiptap/pm": "^2.0.0" + "@tiptap/core": "^2.5.4", + "@tiptap/pm": "^2.5.4" } }, "node_modules/@tiptap/extension-floating-menu": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.4.0.tgz", - "integrity": "sha512-vLb9v+htbHhXyty0oaXjT3VC8St4xuGSHWUB9GuAJAQ+NajIO6rBPbLUmm9qM0Eh2zico5mpSD1Qtn5FM6xYzg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.5.4.tgz", + "integrity": "sha512-EqD4rgi3UhnDcV3H1+ndAS4Ue2zpsU7hFKoevOIV6GS7xVnWN70AGt6swH24QzuHKKISFtWoLpKjrwRORNIxuA==", "dev": true, "license": "MIT", "dependencies": { @@ -5766,14 +5118,14 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0", - "@tiptap/pm": "^2.0.0" + "@tiptap/core": "^2.5.4", + "@tiptap/pm": "^2.5.4" } }, "node_modules/@tiptap/extension-focus": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-focus/-/extension-focus-2.4.0.tgz", - "integrity": "sha512-41P51OImJzvp0zPUudsDPOGIIQkFUK9Yew8MVdtA3EIWkHOl8BZN/X3E/fX9sxzY6WPGgZD0UBRk7f9EA9X5JQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-focus/-/extension-focus-2.5.4.tgz", + "integrity": "sha512-/Iq++93f9S+bNJzj3OmgOydCO58VfAhmnsImbGK/GmxV39hHbgJdazxMugwdQlvrY/oe3+Y+WY8ZI1WlWwTJ4g==", "dev": true, "license": "MIT", "funding": { @@ -5781,14 +5133,14 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0", - "@tiptap/pm": "^2.0.0" + "@tiptap/core": "^2.5.4", + "@tiptap/pm": "^2.5.4" } }, "node_modules/@tiptap/extension-gapcursor": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.4.0.tgz", - "integrity": "sha512-F4y/0J2lseohkFUw9P2OpKhrJ6dHz69ZScABUvcHxjznJLd6+0Zt7014Lw5PA8/m2d/w0fX8LZQ88pZr4quZPQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.5.4.tgz", + "integrity": "sha512-wzTh1piODZBS0wmuDgPjjg8PQwclYa5LssnxDIo9pDSnt4l3AfHSAJIJSGIfgt96KnzF1wqRTRpe08qNa1n7/g==", "dev": true, "license": "MIT", "funding": { @@ -5796,14 +5148,14 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0", - "@tiptap/pm": "^2.0.0" + "@tiptap/core": "^2.5.4", + "@tiptap/pm": "^2.5.4" } }, "node_modules/@tiptap/extension-hard-break": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.4.0.tgz", - "integrity": "sha512-3+Z6zxevtHza5IsDBZ4lZqvNR3Kvdqwxq/QKCKu9UhJN1DUjsg/l1Jn2NilSQ3NYkBYh2yJjT8CMo9pQIu776g==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.5.4.tgz", + "integrity": "sha512-nLn6HP9tqgdGGwbMORXVtcY30DTGctYFaWADRthvBjVgacYSeKlhUcsSu3YgaxtbxZp6BhfRvD2kKrxyQsSjnQ==", "dev": true, "license": "MIT", "funding": { @@ -5811,13 +5163,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-heading": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.4.0.tgz", - "integrity": "sha512-fYkyP/VMo7YHO76YVrUjd95Qeo0cubWn/Spavmwm1gLTHH/q7xMtbod2Z/F0wd6QHnc7+HGhO7XAjjKWDjldaw==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.5.4.tgz", + "integrity": "sha512-DuAB58/e7eho1rkyad0Z/SjW+EB+H2hRqHlswEeZZYhBTjzey5UmBwkMWTGC/SQiRisx1xYQYTd8T0fiABi5hw==", "dev": true, "license": "MIT", "funding": { @@ -5825,13 +5177,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-highlight": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.4.0.tgz", - "integrity": "sha512-p2I/CaMrs6hzpj/dSw6UNobOWTV38yTjPK+B4ShJQ7IN2u/C82KOTOeFfJoFd9KykmpVOVW3w3nKG3ad0HXPuQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.5.4.tgz", + "integrity": "sha512-TSYnFBluZu1YQdTCyXl2wuxFuhFUYFzbaV0f1wq2P2Nc8U2OiiuaNz+QggHw5Hf3ILzkRxQCUQnq97Q/5smMwQ==", "dev": true, "license": "MIT", "funding": { @@ -5839,13 +5191,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-history": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.4.0.tgz", - "integrity": "sha512-gr5qsKAXEVGr1Lyk1598F7drTaEtAxqZiuuSwTCzZzkiwgEQsWMWTWc9F8FlneCEaqe1aIYg6WKWlmYPaFwr0w==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.5.4.tgz", + "integrity": "sha512-WB1fZYGIlpahAD6Ba+mj9vIb1tk8S3TsADXDFKxLVpZWZPQ+B7duGJP7g/vRH2XAXEs836JzC2oxjKeaop3k7A==", "dev": true, "license": "MIT", "funding": { @@ -5853,14 +5205,14 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0", - "@tiptap/pm": "^2.0.0" + "@tiptap/core": "^2.5.4", + "@tiptap/pm": "^2.5.4" } }, "node_modules/@tiptap/extension-horizontal-rule": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.4.0.tgz", - "integrity": "sha512-yDgxy+YxagcEsBbdWvbQiXYxsv3noS1VTuGwc9G7ZK9xPmBHJ5y0agOkB7HskwsZvJHoaSqNRsh7oZTkf0VR3g==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.5.4.tgz", + "integrity": "sha512-uXLDe/iyzQbyfDkJ8kE5XaAkY3EOcbTFLjbueqGlkbWtjJgy+3LysGvh8fQj8PAOaIBMaFRFhTq7GMbW2ebRog==", "dev": true, "license": "MIT", "funding": { @@ -5868,14 +5220,14 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0", - "@tiptap/pm": "^2.0.0" + "@tiptap/core": "^2.5.4", + "@tiptap/pm": "^2.5.4" } }, "node_modules/@tiptap/extension-image": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.4.0.tgz", - "integrity": "sha512-NIVhRPMO/ONo8OywEd+8zh0Q6Q7EbFHtBxVsvfOKj9KtZkaXQfUO4MzONTyptkvAchTpj9pIzeaEY5fyU87gFA==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-image/-/extension-image-2.5.4.tgz", + "integrity": "sha512-4ySSP7iPsbbo1SlPJYj546TKettuO6FGY5MQKxH8AGnZWyQGZYl89GpU1iGFAaeHq4dKUemM5D3ikgSynEQLow==", "dev": true, "license": "MIT", "funding": { @@ -5883,13 +5235,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-italic": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.4.0.tgz", - "integrity": "sha512-aaW/L9q+KNHHK+X73MPloHeIsT191n3VLd3xm6uUcFDnUNvzYJ/q65/1ZicdtCaOLvTutxdrEvhbkrVREX6a8g==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.5.4.tgz", + "integrity": "sha512-TAhtl/fNBgv1elzF3HWES8uwVdpKBSYrq1e6yeYfj74mQn//3ksvdhWQrLzc1e+zcoHbk1PeOp/5ODdPuZ6tkg==", "dev": true, "license": "MIT", "funding": { @@ -5897,13 +5249,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-link": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.4.0.tgz", - "integrity": "sha512-r3PjT0bjSKAorHAEBPA0icSMOlqALbxVlWU9vAc+Q3ndzt7ht0CTPNewzFF9kjzARABVt1cblXP/2+c0qGzcsg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.5.4.tgz", + "integrity": "sha512-xTB/+T6SHHCXInJni8WdqOfF40a/MiFUf5OoWW9cPrApx3I7TzJ9j8/WDshM0BOnDDw80w1bl9F2zkUQjC0Y2A==", "dev": true, "license": "MIT", "dependencies": { @@ -5914,14 +5266,14 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0", - "@tiptap/pm": "^2.0.0" + "@tiptap/core": "^2.5.4", + "@tiptap/pm": "^2.5.4" } }, "node_modules/@tiptap/extension-list-item": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.4.0.tgz", - "integrity": "sha512-reUVUx+2cI2NIAqMZhlJ9uK/+zvRzm1GTmlU2Wvzwc7AwLN4yemj6mBDsmBLEXAKPvitfLh6EkeHaruOGymQtg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.5.4.tgz", + "integrity": "sha512-bPxUCFt9HnAfoaZQgwqCfRAZ6L3QlYhIRDDbOvZag7IxCdQuZmeY4k5OZfQIGijNDTag7CN9cdL4fl9rnm6/sQ==", "dev": true, "license": "MIT", "funding": { @@ -5929,13 +5281,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-ordered-list": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.4.0.tgz", - "integrity": "sha512-Zo0c9M0aowv+2+jExZiAvhCB83GZMjZsxywmuOrdUbq5EGYKb7q8hDyN3hkrktVHr9UPXdPAYTmLAHztTOHYRA==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.5.4.tgz", + "integrity": "sha512-cl3cTJitY6yDUmxqgjDUtDWCyX1VVsZNJ6i9yiPeARcxvzFc81KmUJxTGl8WPT5TjqmM+TleRkZjsxgvXX57+Q==", "dev": true, "license": "MIT", "funding": { @@ -5943,13 +5295,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-paragraph": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.4.0.tgz", - "integrity": "sha512-+yse0Ow67IRwcACd9K/CzBcxlpr9OFnmf0x9uqpaWt1eHck1sJnti6jrw5DVVkyEBHDh/cnkkV49gvctT/NyCw==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.5.4.tgz", + "integrity": "sha512-pC1YIkkRPXoU0eDrhfAf8ZrFJQzvw2ftP6KRhLnnSw/Ot1DOjT1r95l7zsFefS9oCDMT/L4HghTAiPZ4rcpPbg==", "dev": true, "license": "MIT", "funding": { @@ -5957,13 +5309,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-placeholder": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.4.0.tgz", - "integrity": "sha512-SmWOjgWpmhFt0BPOnL65abCUH0wS5yksUJgtANn5bQoHF4HFSsyl7ETRmgf0ykxdjc7tzOg31FfpWVH4wzKSYg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.5.4.tgz", + "integrity": "sha512-mcj4j2Z/L1H5dzWHbbWChuAdJK9F2p06fcjqL4iyJtVx38QQFzCdVmGaTAim8CLp/EynbAOYJ5gk9w2PTdv7+w==", "dev": true, "license": "MIT", "funding": { @@ -5971,14 +5323,14 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0", - "@tiptap/pm": "^2.0.0" + "@tiptap/core": "^2.5.4", + "@tiptap/pm": "^2.5.4" } }, "node_modules/@tiptap/extension-strike": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.4.0.tgz", - "integrity": "sha512-pE1uN/fQPOMS3i+zxPYMmPmI3keubnR6ivwM+KdXWOMnBiHl9N4cNpJgq1n2eUUGKLurC2qrQHpnVyGAwBS6Vg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.5.4.tgz", + "integrity": "sha512-OSN6ePbCwEhi3hYZZOPow/P9Ym2Kv3NhVbUvasjZCiqQuk8TGc33xirPWl9DTjb/BLfL66TtJ2tKUEVOKl5dKg==", "dev": true, "license": "MIT", "funding": { @@ -5986,13 +5338,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-text": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.4.0.tgz", - "integrity": "sha512-LV0bvE+VowE8IgLca7pM8ll7quNH+AgEHRbSrsI3SHKDCYB9gTHMjWaAkgkUVaO1u0IfCrjnCLym/PqFKa+vvg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.5.4.tgz", + "integrity": "sha512-+3x/hYqhmCYbvedCcQzQHFtZ5MAcMOlKuczomZtygf8AfDfuQVrG1m4GoJyNzJdqxjN80/xq4e2vDVvqQxYTCw==", "dev": true, "license": "MIT", "funding": { @@ -6000,13 +5352,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-underline": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.4.0.tgz", - "integrity": "sha512-guWojb7JxUwLz4OKzwNExJwOkhZjgw/ttkXCMBT0PVe55k998MMYe1nvN0m2SeTW9IxurEPtScH4kYJ0XuSm8Q==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.5.4.tgz", + "integrity": "sha512-o8T3oWbniA3rLo6LkslPRF8pwdjsaHXJCeK4KmKeCyYhTpMfjypT3uptd+VSSJ4iQkaiFInKeIUOBqqEQ9cADw==", "dev": true, "license": "MIT", "funding": { @@ -6014,13 +5366,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/extension-youtube": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/extension-youtube/-/extension-youtube-2.4.0.tgz", - "integrity": "sha512-Ew6Oik9DaqP0xgQSUIWwozqeToJVOY4nqjRoKExGRuLdzgZeS+SmEA22ITBMcnZSJj8XBMgGBLcCWi+A7x1KAg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/extension-youtube/-/extension-youtube-2.5.4.tgz", + "integrity": "sha512-iHcvXOA32MZsVJTT7mvZ1CWKUo2quQMQXfBniizLm0lUG1ftSioqnDuXy4kEjeCBR2cnZr3yph6tbG/pF0RcHg==", "dev": true, "license": "MIT", "funding": { @@ -6028,13 +5380,13 @@ "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/core": "^2.0.0" + "@tiptap/core": "^2.5.4" } }, "node_modules/@tiptap/pm": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.4.0.tgz", - "integrity": "sha512-B1HMEqGS4MzIVXnpgRZDLm30mxDWj51LkBT/if1XD+hj5gm8B9Q0c84bhvODX6KIs+c6z+zsY9VkVu8w9Yfgxg==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.5.4.tgz", + "integrity": "sha512-oFIsuniptdUXn93x4aM2sVN3hYKo9Fj55zAkYrWhwxFYUYcPxd5ibra2we+wRK5TaiPu098wpC+yMSTZ/KKMpA==", "dev": true, "license": "MIT", "peer": true, @@ -6044,19 +5396,19 @@ "prosemirror-commands": "^1.5.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", - "prosemirror-history": "^1.3.2", - "prosemirror-inputrules": "^1.3.0", + "prosemirror-history": "^1.4.1", + "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", - "prosemirror-markdown": "^1.12.0", + "prosemirror-markdown": "^1.13.0", "prosemirror-menu": "^1.2.4", - "prosemirror-model": "^1.19.4", - "prosemirror-schema-basic": "^1.2.2", - "prosemirror-schema-list": "^1.3.0", + "prosemirror-model": "^1.22.1", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.4.1", "prosemirror-state": "^1.4.3", - "prosemirror-tables": "^1.3.5", - "prosemirror-trailing-node": "^2.0.7", - "prosemirror-transform": "^1.8.0", - "prosemirror-view": "^1.32.7" + "prosemirror-tables": "^1.3.7", + "prosemirror-trailing-node": "^2.0.8", + "prosemirror-transform": "^1.9.0", + "prosemirror-view": "^1.33.8" }, "funding": { "type": "github", @@ -6157,9 +5509,9 @@ "license": "MIT" }, "node_modules/@types/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-hAe0Hc9yJWESaUVl5NM09E9IdAk/0k5njp0gV3yEI5SkmjWuFYh9IexqX3/eTC0y+J+RMx2WIBqkfuzvTRnVmg==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.9.tgz", + "integrity": "sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==", "dev": true, "license": "MIT", "dependencies": { @@ -6167,9 +5519,9 @@ } }, "node_modules/@types/node": { - "version": "20.14.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", - "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", + "version": "20.14.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz", + "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==", "dev": true, "license": "MIT", "dependencies": { @@ -6183,13 +5535,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/shimmer": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.0.5.tgz", - "integrity": "sha512-9Hp0ObzwwO57DpLFF0InUjUm/II8GmKAvzbefxQTihCb7KI6yc9yzf0nLc4mVdby5N4DRCgQM2wCup9KTieeww==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/throttle-debounce": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-5.0.2.tgz", @@ -6198,9 +5543,9 @@ "license": "MIT" }, "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.5.11", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.11.tgz", + "integrity": "sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==", "dev": true, "license": "MIT", "dependencies": { @@ -6290,9 +5635,9 @@ } }, "node_modules/@vinxi/plugin-directives": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@vinxi/plugin-directives/-/plugin-directives-0.3.1.tgz", - "integrity": "sha512-4qz5WifjmJ864VS8Ik9nUG0wAkt/xIcxFpP29RXogGLgccRnceBpWQi+ghw5rm0F6LP/YMAhhO5iFORXclWd0Q==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@vinxi/plugin-directives/-/plugin-directives-0.4.1.tgz", + "integrity": "sha512-NsHCDyqU00i4RKGBoNNcBuONEirg/XfGgPCLFK1CZw3AYBE19haFSgvuo21Bx+BFGcwdRU3BRtaBMvwjLrUCnw==", "dev": true, "dependencies": { "@babel/parser": "^7.23.5", @@ -6306,16 +5651,16 @@ "tslib": "^2.6.2" }, "peerDependencies": { - "vinxi": "^0.3.10" + "vinxi": "^0.4.0" } }, "node_modules/@vinxi/server-components": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@vinxi/server-components/-/server-components-0.3.3.tgz", - "integrity": "sha512-xaW92nj9HUMLyswPcCmsIXOsS3TJll0m9u3WEjWjLrtZWheHggina6+kTCSeltp/Qe8WlUfNU5G02Xy8L4xQxA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@vinxi/server-components/-/server-components-0.4.1.tgz", + "integrity": "sha512-rMS+RCGr1tujO1xWgILMLpOWIyw2OwDO46EtkuhTfqaVgLLt/w7+hxzOnh4s3O9sXoKKuUswtj9/MpQQkFoMOQ==", "dev": true, "dependencies": { - "@vinxi/plugin-directives": "0.3.1", + "@vinxi/plugin-directives": "0.4.1", "acorn": "^8.10.0", "acorn-loose": "^8.3.0", "acorn-typescript": "^1.4.3", @@ -6324,16 +5669,16 @@ "recast": "^0.23.4" }, "peerDependencies": { - "vinxi": "^0.3.10" + "vinxi": "^0.4.0" } }, "node_modules/@vinxi/server-functions": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@vinxi/server-functions/-/server-functions-0.3.3.tgz", - "integrity": "sha512-yUrHov1gc+NM/YCEOekM1DCdu2tNSH1/j0mZPyIOhPZH/yAZKWA+t3dP79Q3g4QLDHchf6xf8z9u1INEADTlXw==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@vinxi/server-functions/-/server-functions-0.4.1.tgz", + "integrity": "sha512-dj5v9V+DurXK8w/nBDgJof+UsK3bkcgk6K/xBUg+WVmn7sUrLTurDTGRkCaknC6tQCyadNzj4FWGGc+qlrWf9g==", "dev": true, "dependencies": { - "@vinxi/plugin-directives": "0.3.1", + "@vinxi/plugin-directives": "0.4.1", "acorn": "^8.10.0", "acorn-loose": "^8.3.0", "acorn-typescript": "^1.4.3", @@ -6342,7 +5687,7 @@ "recast": "^0.23.4" }, "peerDependencies": { - "vinxi": "^0.3.12" + "vinxi": "^0.4.0" } }, "node_modules/@whatwg-node/events": { @@ -6408,9 +5753,9 @@ } }, "node_modules/acorn": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", - "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "license": "MIT", "bin": { @@ -6491,16 +5836,16 @@ } }, "node_modules/ajv": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", - "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -6543,19 +5888,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -6677,9 +6009,9 @@ } }, "node_modules/archiver-utils/node_modules/glob": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", - "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { @@ -6693,9 +6025,6 @@ "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } @@ -7072,13 +6401,13 @@ } }, "node_modules/babel-preset-solid": { - "version": "1.8.17", - "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.8.17.tgz", - "integrity": "sha512-s/FfTZOeds0hYxYqce90Jb+0ycN2lrzC7VP1k1JIn3wBqcaexDKdYi6xjB+hMNkL+Q6HobKbwsriqPloasR9LA==", + "version": "1.8.18", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.8.18.tgz", + "integrity": "sha512-ky0FA4cCS9dk+xYBBItHoxtbRnaDIOGpmHLFqKPaR81hpMbJBOiLOZia2hT0JBwx4zn/D2OjMRvRr6kqtRMoUw==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jsx-dom-expressions": "^0.37.20" + "babel-plugin-jsx-dom-expressions": "^0.37.23" }, "peerDependencies": { "@babel/core": "^7.0.0" @@ -7126,6 +6455,13 @@ ], "license": "MIT" }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", @@ -7517,9 +6853,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", - "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", + "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", "dev": true, "funding": [ { @@ -7537,10 +6873,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001629", - "electron-to-chromium": "^1.4.796", + "caniuse-lite": "^1.0.30001640", + "electron-to-chromium": "^1.4.820", "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.16" + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -7721,9 +7057,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001638", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001638.tgz", - "integrity": "sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==", + "version": "1.0.30001642", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz", + "integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==", "dev": true, "funding": [ { @@ -7889,13 +7225,6 @@ "consola": "^3.2.3" } }, - "node_modules/cjs-module-lexer": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", - "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", - "dev": true, - "license": "MIT" - }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -8295,9 +7624,9 @@ } }, "node_modules/cookie-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.1.0.tgz", - "integrity": "sha512-L2rLOcK0wzWSfSDA33YR+PUHDG10a8px7rUHKWbGLP4YfbsMed2KFUw5fczvDPbT98DDe3LEzviswl810apTEw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.1.tgz", + "integrity": "sha512-ilTPDuxhZX44BSzzRB58gvSY2UevZKQM9fjisn7Z+NJ92CtSU6kO1+22ZN/agbEJANFjK85EiJJbi/gQv18OXA==", "dev": true, "license": "MIT" }, @@ -8477,9 +7806,9 @@ "peer": true }, "node_modules/croner": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/croner/-/croner-8.0.2.tgz", - "integrity": "sha512-HgSdlSUX8mIgDTTiQpWUP4qY4IFRMsduPCYdca34Pelt8MVdxdaDOzreFtCscA6R+cRZd7UbD1CD3uyx6J3X1A==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/croner/-/croner-8.1.0.tgz", + "integrity": "sha512-sz990XOUPR8dG/r5BRKMBd15MYDDUu8oeSaxFD5DqvNgHSZw8Psd1s689/IGET7ezxRMiNlCIyGeY1Gvxp/MLg==", "dev": true, "license": "MIT", "engines": { @@ -8923,6 +8252,13 @@ "node": ">= 0.6.0" } }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true, + "license": "ISC" + }, "node_modules/des.js": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", @@ -9047,6 +8383,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dot-prop/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dotenv": { "version": "16.4.5", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", @@ -9092,16 +8441,16 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.814", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.814.tgz", - "integrity": "sha512-GVulpHjFu1Y9ZvikvbArHmAhZXtm3wHlpjTMcXNGKl4IQ4jMQjlnz8yMQYYqdLHKi/jEL2+CBC2akWVCoIGUdw==", + "version": "1.4.832", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.832.tgz", + "integrity": "sha512-cTen3SB0H2SGU7x467NRe1eVcQgcuS6jckKfWJHia2eo0cHIGOqHoAxevIYZD4eRHcWjkvFzo93bi3vJ9W+1lA==", "dev": true, "license": "ISC" }, "node_modules/elliptic": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz", - "integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==", + "version": "6.5.6", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.6.tgz", + "integrity": "sha512-mpzdtpeCLuS3BmE3pO3Cpp5bbjlOPY2Q0PgoF+Od1XZrHLYI28Xe3ossCmYCQt11FQKEYd9+PF8jymTvtWJSHQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9183,9 +8532,9 @@ } }, "node_modules/error-stack-parser-es": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.4.tgz", - "integrity": "sha512-l0uy0kAoo6toCgVOYaAayqtPa2a1L15efxUMEnQebKwLQX2X0OpS6wMMQdc4juJXmxd9i40DuaUHq+mjIya9TQ==", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz", + "integrity": "sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==", "dev": true, "license": "MIT", "funding": { @@ -9506,6 +8855,13 @@ "fast-decode-uri-component": "^1.0.1" } }, + "node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-url-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", @@ -10466,9 +9822,9 @@ } }, "node_modules/i18next": { - "version": "23.11.5", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.5.tgz", - "integrity": "sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==", + "version": "23.12.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.12.2.tgz", + "integrity": "sha512-XIeh5V+bi8SJSWGL3jqbTEBW5oD6rbP5L+E7dVQh1MNTxxYef0x15rhJVcRb7oiuq4jLtgy2SD8eFlf6P2cmqg==", "dev": true, "funding": [ { @@ -10619,19 +9975,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-in-the-middle": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.8.1.tgz", - "integrity": "sha512-yhRwoHtiLGvmSozNOALgjRPFI6uYsds60EoMqqnXyyv+JOIW/BrrLejuTGBt+bq0T5tLzOHrN0T7xYTm4Qt/ng==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "acorn": "^8.8.2", - "acorn-import-attributes": "^1.9.5", - "cjs-module-lexer": "^1.2.2", - "module-details-from-path": "^1.0.3" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -10844,9 +10187,9 @@ } }, "node_modules/is-core-module": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", - "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", "dev": true, "license": "MIT", "dependencies": { @@ -11209,17 +10552,14 @@ } }, "node_modules/jackspeak": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", - "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=14" - }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -11248,9 +10588,9 @@ } }, "node_modules/jose": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.6.2.tgz", - "integrity": "sha512-F1t1/WZJ4JdmCE/XoMYw1dPOW5g8JF0xGm6Ox2fwaCAPlCzt+4Bh0EWP59iQuZNHHauDkCdjx+kCZSh5z/PGow==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.6.3.tgz", + "integrity": "sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==", "dev": true, "license": "MIT", "funding": { @@ -11428,9 +10768,9 @@ "license": "MIT" }, "node_modules/known-css-properties": { - "version": "0.31.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.31.0.tgz", - "integrity": "sha512-sBPIUGTNF0czz0mwGGUoKKJC8Q7On1GPbCSFPfyEsfHb2DyBG0Y4QtV+EVWpINSaiGKZblDNuF5AezxSgOhesQ==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz", + "integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==", "dev": true, "license": "MIT" }, @@ -11679,13 +11019,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -11700,13 +11033,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -11775,13 +11101,6 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -11848,12 +11167,12 @@ } }, "node_modules/mailgun.js": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-10.2.1.tgz", - "integrity": "sha512-lslXpLdwtLyavJGHsPKlpXLnd3XEuwWY5X/KaTAvv2UvHiOWSlUzqkGW21vVgcQQ9AKw0s5ayS5V9Cv8Ze2OIw==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-10.2.3.tgz", + "integrity": "sha512-7Mcw5IFtzN21i+qFQoWI+aQFDpLYSMUIWvDUXKLlpGFVVGfYVL8GIiveS+LIXpEJTQcF1hoNhOhDwenFqNSKmw==", "license": "MIT", "dependencies": { - "axios": "^1.6.0", + "axios": "^1.7.2", "base-64": "^1.0.0", "url-join": "^4.0.1" }, @@ -12044,9 +11363,9 @@ "license": "MIT" }, "node_modules/mime": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.3.tgz", - "integrity": "sha512-KgUb15Oorc0NEKPbvfa0wRU+PItIEZmiv+pyAO2i0oTIVTJhlzMclU7w4RXWQrSOVH5ax/p/CkIO7KI4OyFJTQ==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz", + "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==", "dev": true, "funding": [ "https://github.com/sponsors/broofa" @@ -12197,13 +11516,6 @@ "ufo": "^1.5.3" } }, - "node_modules/module-details-from-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", - "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==", - "dev": true, - "license": "MIT" - }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -12418,9 +11730,9 @@ } }, "node_modules/nitropack/node_modules/globby": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.1.tgz", - "integrity": "sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==", + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", "dev": true, "license": "MIT", "dependencies": { @@ -12485,9 +11797,9 @@ } }, "node_modules/nitropack/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "license": "ISC", "bin": { @@ -12522,14 +11834,11 @@ } }, "node_modules/node-addon-api": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz", - "integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "dev": true, - "license": "MIT", - "engines": { - "node": "^16 || ^18 || >= 20" - } + "license": "MIT" }, "node_modules/node-fetch": { "version": "2.7.0", @@ -12589,9 +11898,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz", + "integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==", "dev": true, "license": "MIT" }, @@ -13155,9 +12464,9 @@ } }, "node_modules/patch-package/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "license": "ISC", "bin": { @@ -13273,14 +12582,11 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.3.0.tgz", - "integrity": "sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "ISC", - "engines": { - "node": "14 || >=16.14" - } + "license": "ISC" }, "node_modules/path-to-regexp": { "version": "6.2.2", @@ -13364,25 +12670,25 @@ } }, "node_modules/pkg-types": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.1.tgz", - "integrity": "sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.3.tgz", + "integrity": "sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==", "dev": true, "license": "MIT", "dependencies": { "confbox": "^0.1.7", - "mlly": "^1.7.0", + "mlly": "^1.7.1", "pathe": "^1.1.2" } }, "node_modules/playwright": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.0.tgz", - "integrity": "sha512-4z3ac3plDfYzGB6r0Q3LF8POPR20Z8D0aXcxbJvmfMgSSq1hkcgvFRXJk9rUq5H/MJ0Ktal869hhOdI/zUTeLA==", + "version": "1.45.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.2.tgz", + "integrity": "sha512-ReywF2t/0teRvNBpfIgh5e4wnrI/8Su8ssdo5XsQKpjxJj+jspm00jSoz9BTg91TT0c9HRjXO7LBNVrgYj9X0g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.45.0" + "playwright-core": "1.45.2" }, "bin": { "playwright": "cli.js" @@ -13395,9 +12701,9 @@ } }, "node_modules/playwright-core": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", - "integrity": "sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==", + "version": "1.45.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.2.tgz", + "integrity": "sha512-ha175tAWb0dTK0X4orvBIqi3jGEt701SMxMhyujxNrgd8K0Uy5wMSwwcQHtyB4om7INUkfndx02XnQ2p6dvLDw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -13418,9 +12724,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", "dev": true, "funding": [ { @@ -13439,7 +12745,7 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "source-map-js": "^1.2.0" }, "engines": { @@ -13535,9 +12841,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", - "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", + "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", "dev": true, "license": "MIT", "dependencies": { @@ -13566,9 +12872,9 @@ "license": "MIT" }, "node_modules/prettier": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", - "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "license": "MIT", "peer": true, @@ -13685,9 +12991,9 @@ } }, "node_modules/prosemirror-history": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.0.tgz", - "integrity": "sha512-UUiGzDVcqo1lovOPdi9YxxUps3oBFWAIYkXLu3Ot+JPv1qzVogRbcizxK3LhHmtaUxclohgiOVesRw5QSlMnbQ==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz", + "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13748,9 +13054,9 @@ } }, "node_modules/prosemirror-model": { - "version": "1.21.3", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.21.3.tgz", - "integrity": "sha512-nt2Xs/RNGepD9hrrkzXvtCm1mpGJoQfFSPktGa0BF/aav6XsnmVGZ9sTXNWRLupAz5SCLa3EyKlFeK7zJWROKg==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.22.2.tgz", + "integrity": "sha512-I4lS7HHIW47D0Xv/gWmi4iUWcQIDYaJKd8Hk4+lcSps+553FlQrhmxtItpEvTr75iAruhzVShVp6WUwsT6Boww==", "dev": true, "license": "MIT", "dependencies": { @@ -13758,9 +13064,9 @@ } }, "node_modules/prosemirror-schema-basic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.2.tgz", - "integrity": "sha512-/dT4JFEGyO7QnNTe9UaKUhjDXbTNkiWTq/N4VpKaF79bBjSExVV2NXmJpcM7z/gD7mbqNjxbmWW5nf1iNSSGnw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.3.tgz", + "integrity": "sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==", "dev": true, "license": "MIT", "peer": true, @@ -13769,9 +13075,9 @@ } }, "node_modules/prosemirror-schema-list": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.4.0.tgz", - "integrity": "sha512-nZOIq/AkBSzCENxUyLm5ltWE53e2PLk65ghMN8qLQptOmDVixZlPqtMeQdiNw0odL9vNpalEjl3upgRkuJ/Jyw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.4.1.tgz", + "integrity": "sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg==", "dev": true, "license": "MIT", "peer": true, @@ -13794,9 +13100,9 @@ } }, "node_modules/prosemirror-tables": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.3.7.tgz", - "integrity": "sha512-oEwX1wrziuxMtwFvdDWSFHVUWrFJWt929kVVfHvtTi8yvw+5ppxjXZkMG/fuTdFo+3DXyIPSKfid+Be1npKXDA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.4.0.tgz", + "integrity": "sha512-fxryZZkQG12fSCNuZDrYx6Xvo2rLYZTbKLRd8rglOPgNJGMKIS8uvTt6gGC38m7UCu/ENnXIP9pEz5uDaPc+cA==", "dev": true, "license": "MIT", "peer": true, @@ -13809,9 +13115,9 @@ } }, "node_modules/prosemirror-trailing-node": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.8.tgz", - "integrity": "sha512-ujRYhSuhQb1Jsarh1IHqb2KoSnRiD7wAMDGucP35DN7j5af6X7B18PfdPIrbwsPTqIAj0fyOvxbuPsWhNvylmA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.9.tgz", + "integrity": "sha512-YvyIn3/UaLFlFKrlJB6cObvUhmwFNZVhy1Q8OpW/avoTbD/Y7H5EcjK4AZFKhmuS6/N6WkGgt7gWtBWDnmFvHg==", "dev": true, "license": "MIT", "dependencies": { @@ -13819,9 +13125,9 @@ "escape-string-regexp": "^4.0.0" }, "peerDependencies": { - "prosemirror-model": "^1.19.0", + "prosemirror-model": "^1.22.1", "prosemirror-state": "^1.4.2", - "prosemirror-view": "^1.31.2" + "prosemirror-view": "^1.33.8" } }, "node_modules/prosemirror-trailing-node/node_modules/escape-string-regexp": { @@ -13848,9 +13154,9 @@ } }, "node_modules/prosemirror-view": { - "version": "1.33.8", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.8.tgz", - "integrity": "sha512-4PhMr/ufz2cdvFgpUAnZfs+0xij3RsFysreeG9V/utpwX7AJtYCDVyuRxzWoMJIEf4C7wVihuBNMPpFLPCiLQw==", + "version": "1.33.9", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.9.tgz", + "integrity": "sha512-xV1A0Vz9cIcEnwmMhKKFAOkfIp8XmJRnaZoPqNXrPS7EK5n11Ov8V76KhR0RsfQd/SIzmWY+bg+M44A2Lx/Nnw==", "dev": true, "license": "MIT", "dependencies": { @@ -13859,31 +13165,6 @@ "prosemirror-transform": "^1.1.0" } }, - "node_modules/protobufjs": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.2.tgz", - "integrity": "sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==", - "dev": true, - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -13951,9 +13232,9 @@ } }, "node_modules/qs": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", - "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.3.tgz", + "integrity": "sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -14223,21 +13504,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-in-the-middle": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.3.0.tgz", - "integrity": "sha512-nQFEv9gRw6SJAwWD2LrL0NmQvAcO7FBwJbwmr2ttPAacfy0xuiOjE5zt+zM4xDyuyvUaxBi/9gb2SoCyNEVJcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "module-details-from-path": "^1.0.3", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=8.6.0" - } - }, "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -14338,9 +13604,9 @@ } }, "node_modules/rollup": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", - "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.19.0.tgz", + "integrity": "sha512-5r7EYSQIowHsK4eTZ0Y81qpZuJz+MUuYeqmmYmRMl1nwhdmbiYqt5jwzf6u7wyOzJgYqtCRMtVRKOtHANBz7rA==", "dev": true, "license": "MIT", "dependencies": { @@ -14354,22 +13620,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.18.0", - "@rollup/rollup-android-arm64": "4.18.0", - "@rollup/rollup-darwin-arm64": "4.18.0", - "@rollup/rollup-darwin-x64": "4.18.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", - "@rollup/rollup-linux-arm-musleabihf": "4.18.0", - "@rollup/rollup-linux-arm64-gnu": "4.18.0", - "@rollup/rollup-linux-arm64-musl": "4.18.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", - "@rollup/rollup-linux-riscv64-gnu": "4.18.0", - "@rollup/rollup-linux-s390x-gnu": "4.18.0", - "@rollup/rollup-linux-x64-gnu": "4.18.0", - "@rollup/rollup-linux-x64-musl": "4.18.0", - "@rollup/rollup-win32-arm64-msvc": "4.18.0", - "@rollup/rollup-win32-ia32-msvc": "4.18.0", - "@rollup/rollup-win32-x64-msvc": "4.18.0", + "@rollup/rollup-android-arm-eabi": "4.19.0", + "@rollup/rollup-android-arm64": "4.19.0", + "@rollup/rollup-darwin-arm64": "4.19.0", + "@rollup/rollup-darwin-x64": "4.19.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.19.0", + "@rollup/rollup-linux-arm-musleabihf": "4.19.0", + "@rollup/rollup-linux-arm64-gnu": "4.19.0", + "@rollup/rollup-linux-arm64-musl": "4.19.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.19.0", + "@rollup/rollup-linux-riscv64-gnu": "4.19.0", + "@rollup/rollup-linux-s390x-gnu": "4.19.0", + "@rollup/rollup-linux-x64-gnu": "4.19.0", + "@rollup/rollup-linux-x64-musl": "4.19.0", + "@rollup/rollup-win32-arm64-msvc": "4.19.0", + "@rollup/rollup-win32-ia32-msvc": "4.19.0", + "@rollup/rollup-win32-x64-msvc": "4.19.0", "fsevents": "~2.3.2" } }, @@ -14607,9 +13873,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.77.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", - "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.76.0.tgz", + "integrity": "sha512-nc3LeqvF2FNW5xGF1zxZifdW3ffIz5aBb7I7tSvOoNu7z1RQ6pFt9MBuiPtjgaI62YWrM/txjWlOCFiGtf2xpw==", "dev": true, "license": "MIT", "dependencies": { @@ -14740,9 +14006,9 @@ } }, "node_modules/seroval": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.0.7.tgz", - "integrity": "sha512-n6ZMQX5q0Vn19Zq7CIKNIo7E75gPkGCFUEqDpa8jgwpYr/vScjqnQ6H09t1uIiZ0ZSK0ypEGvrYK2bhBGWsGdw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.1.0.tgz", + "integrity": "sha512-74Wpe+hhPx4V8NFe00I2Fu9gTJopKoH5vE7nCqFzVgKOXV8AnN23T58K79QLYQotzGpH93UZ+UN2Y11j9huZJg==", "dev": true, "license": "MIT", "engines": { @@ -14750,9 +14016,9 @@ } }, "node_modules/seroval-plugins": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.0.7.tgz", - "integrity": "sha512-GO7TkWvodGp6buMEX9p7tNyIkbwlyuAWbI6G9Ec5bhcm7mQdu3JOK1IXbEUwb3FVzSc363GraG/wLW23NSavIw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.1.0.tgz", + "integrity": "sha512-KtcJg590L3X3dd7ixs6am4UGVcV69TyjYhHtanIdQJq4dy2OceWXmmvWrYx7oFDNe+LNdxdWd0I5BQXuV5fBhA==", "dev": true, "license": "MIT", "engines": { @@ -14878,6 +14144,7 @@ "version": "0.9.19", "resolved": "https://registry.npmjs.org/shikiji/-/shikiji-0.9.19.tgz", "integrity": "sha512-Kw2NHWktdcdypCj1GkKpXH4o6Vxz8B8TykPlPuLHOGSV8VkhoCLcFOH4k19K4LXAQYRQmxg+0X/eM+m2sLhAkg==", + "deprecated": "Shikiji is merged back to Shiki v1.0, please migrate over to get the latest updates", "dev": true, "license": "MIT", "dependencies": { @@ -14888,16 +14155,10 @@ "version": "0.9.19", "resolved": "https://registry.npmjs.org/shikiji-core/-/shikiji-core-0.9.19.tgz", "integrity": "sha512-AFJu/vcNT21t0e6YrfadZ+9q86gvPum6iywRyt1OtIPjPFe25RQnYJyxHQPMLKCCWA992TPxmEmbNcOZCAJclw==", + "deprecated": "Shikiji is merged back to Shiki v1.0, please migrate over to get the latest updates", "dev": true, "license": "MIT" }, - "node_modules/shimmer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", - "dev": true, - "license": "BSD-2-Clause" - }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -14990,9 +14251,9 @@ } }, "node_modules/solid-js": { - "version": "1.8.17", - "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.8.17.tgz", - "integrity": "sha512-E0FkUgv9sG/gEBWkHr/2XkBluHb1fkrHywUgA6o6XolPDCJ4g1HaLmQufcBBhiF36ee40q+HpG/vCZu7fLpI3Q==", + "version": "1.8.18", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.8.18.tgz", + "integrity": "sha512-cpkxDPvO/AuKBugVv6xKFd1C9VC0XZMu4VtF56IlHoux8HgyW44uqNSWbozMnVcpIzHIhS3vVXPAVZYM26jpWw==", "dev": true, "license": "MIT", "dependencies": { @@ -15063,29 +14324,6 @@ "solid-js": "^1.6.12" } }, - "node_modules/solid-transition-group/node_modules/@solid-primitives/refs": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@solid-primitives/refs/-/refs-1.0.8.tgz", - "integrity": "sha512-+jIsWG8/nYvhaCoG2Vg6CJOLgTmPKFbaCrNQKWfChalgUf9WrVxWw0CdJb3yX15n5lUcQ0jBo6qYtuVVmBLpBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@solid-primitives/utils": "^6.2.3" - }, - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, - "node_modules/solid-transition-group/node_modules/@solid-primitives/utils": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz", - "integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, "node_modules/solid-use": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/solid-use/-/solid-use-0.8.0.tgz", @@ -15328,9 +14566,9 @@ "license": "MIT" }, "node_modules/stylelint": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.6.1.tgz", - "integrity": "sha512-yNgz2PqWLkhH2hw6X9AweV9YvoafbAD5ZsFdKN9BvSDVwGvPh+AUIrn7lYwy1S7IHmtFin75LLfX1m0D2tHu8Q==", + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.7.0.tgz", + "integrity": "sha512-Q1ATiXlz+wYr37a7TGsfvqYn2nSR3T/isw3IWlZQzFzCNoACHuGBb6xBplZXz56/uDRJHIygxjh7jbV/8isewA==", "dev": true, "funding": [ { @@ -15344,9 +14582,9 @@ ], "license": "MIT", "dependencies": { - "@csstools/css-parser-algorithms": "^2.6.3", - "@csstools/css-tokenizer": "^2.3.1", - "@csstools/media-query-list-parser": "^2.1.11", + "@csstools/css-parser-algorithms": "^2.7.1", + "@csstools/css-tokenizer": "^2.4.1", + "@csstools/media-query-list-parser": "^2.1.13", "@csstools/selector-specificity": "^3.1.1", "@dual-bundle/import-meta-resolve": "^4.1.0", "balanced-match": "^2.0.0", @@ -15354,7 +14592,7 @@ "cosmiconfig": "^9.0.0", "css-functions-list": "^3.2.2", "css-tree": "^2.3.1", - "debug": "^4.3.4", + "debug": "^4.3.5", "fast-glob": "^3.3.2", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^9.0.0", @@ -15365,13 +14603,13 @@ "ignore": "^5.3.1", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.31.0", + "known-css-properties": "^0.34.0", "mathml-tag-names": "^2.1.3", "meow": "^13.2.0", "micromatch": "^4.0.7", "normalize-path": "^3.0.0", "picocolors": "^1.0.1", - "postcss": "^8.4.38", + "postcss": "^8.4.39", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^7.0.0", "postcss-selector-parser": "^6.1.0", @@ -15415,22 +14653,22 @@ } }, "node_modules/stylelint-config-recommended-scss": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-14.0.0.tgz", - "integrity": "sha512-HDvpoOAQ1RpF+sPbDOT2Q2/YrBDEJDnUymmVmZ7mMCeNiFSdhRdyGEimBkz06wsN+HaFwUh249gDR+I9JR7Onw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-14.1.0.tgz", + "integrity": "sha512-bhaMhh1u5dQqSsf6ri2GVWWQW5iUjBYgcHkh7SgDDn92ijoItC/cfO/W+fpXshgTQWhwFkP1rVcewcv4jaftRg==", "dev": true, "license": "MIT", "dependencies": { "postcss-scss": "^4.0.9", - "stylelint-config-recommended": "^14.0.0", - "stylelint-scss": "^6.0.0" + "stylelint-config-recommended": "^14.0.1", + "stylelint-scss": "^6.4.0" }, "engines": { "node": ">=18.12.0" }, "peerDependencies": { "postcss": "^8.3.3", - "stylelint": "^16.0.2" + "stylelint": "^16.6.1" }, "peerDependenciesMeta": { "postcss": { @@ -15502,13 +14740,13 @@ } }, "node_modules/stylelint-scss": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.3.2.tgz", - "integrity": "sha512-pNk9mXOVKkQtd+SROPC9io8ISSgX+tOVPhFdBE+LaKQnJMLdWPbGKAGYv4Wmf/RrnOjkutunNTN9kKMhkdE5qA==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.4.1.tgz", + "integrity": "sha512-+clI2bQC2FPOt06ZwUlXZZ95IO2C5bKTP0GLN1LNQPVvISfSNcgMKv/VTwym1mK9vnqhHbOk8lO4rj4nY7L9pw==", "dev": true, "license": "MIT", "dependencies": { - "known-css-properties": "^0.31.0", + "known-css-properties": "^0.34.0", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.1", "postcss-selector-parser": "^6.1.0", @@ -15641,9 +14879,9 @@ } }, "node_modules/swiper": { - "version": "11.1.4", - "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.1.4.tgz", - "integrity": "sha512-1n7kbYJB2dFEpUHRFszq7gys/ofIBrMNibwTiMvPHwneKND/t9kImnHt6CfGPScMHgI+dWMbGTycCKGMoOO1KA==", + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.1.5.tgz", + "integrity": "sha512-JJQWFXdxiMGC2j6ZGTYat5Z7NN9JORJBgHp0/joX9uPod+cRj0wr5H5VnWSNIz9JeAamQvYKaG7aFrGHIF9OEg==", "dev": true, "funding": [ { @@ -15762,9 +15000,9 @@ } }, "node_modules/terser": { - "version": "5.31.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.1.tgz", - "integrity": "sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==", + "version": "5.31.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.3.tgz", + "integrity": "sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -15781,9 +15019,9 @@ } }, "node_modules/text-decoder": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", - "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", + "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -15945,22 +15183,22 @@ "license": "MIT" }, "node_modules/type-fest": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", - "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=14.16" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/typescript": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", - "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -16014,9 +15252,9 @@ "peer": true }, "node_modules/ufo": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", - "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", "dev": true, "license": "MIT" }, @@ -16081,17 +15319,17 @@ "license": "MIT" }, "node_modules/unenv": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-1.9.0.tgz", - "integrity": "sha512-QKnFNznRxmbOF1hDgzpqrlIf6NC5sbZ2OJ+5Wl3OX8uM+LUJXbj4TXvLJCtwbPTmbMHCLIz6JLKNinNsMShK9g==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-1.10.0.tgz", + "integrity": "sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==", "dev": true, "license": "MIT", "dependencies": { "consola": "^3.2.3", - "defu": "^6.1.3", + "defu": "^6.1.4", "mime": "^3.0.0", - "node-fetch-native": "^1.6.1", - "pathe": "^1.1.1" + "node-fetch-native": "^1.6.4", + "pathe": "^1.1.2" } }, "node_modules/unenv/node_modules/mime": { @@ -16121,25 +15359,25 @@ } }, "node_modules/unimport": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.7.2.tgz", - "integrity": "sha512-91mxcZTadgXyj3lFWmrGT8GyoRHWuE5fqPOjg5RVtF6vj+OfM5G6WCzXjuYtSgELE5ggB34RY4oiCSEP8I3AHw==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.9.0.tgz", + "integrity": "sha512-H2ftTISja1BonUVdOKRos6HC6dqYDR40dQTZY3zIDJ/5/z4ihncuL0LqLvtxYqUDMib41eAtunQUhXIWTCZ8rA==", "dev": true, "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.1.0", - "acorn": "^8.11.3", + "acorn": "^8.12.1", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "fast-glob": "^3.3.2", "local-pkg": "^0.5.0", "magic-string": "^0.30.10", - "mlly": "^1.7.0", + "mlly": "^1.7.1", "pathe": "^1.1.2", - "pkg-types": "^1.1.1", + "pkg-types": "^1.1.3", "scule": "^1.3.0", "strip-literal": "^2.1.0", - "unplugin": "^1.10.1" + "unplugin": "^1.11.0" } }, "node_modules/unimport/node_modules/escape-string-regexp": { @@ -16172,6 +15410,13 @@ "dev": true, "license": "MIT" }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "dev": true, + "license": "ISC" + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -16209,9 +15454,9 @@ } }, "node_modules/unplugin": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.10.1.tgz", - "integrity": "sha512-d6Mhq8RJeGA8UfKCu54Um4lFA0eSaRa3XxdAJg8tIdxbu1ubW0hBCZUL7yI2uGyYCRndvbK8FLHzqy2XKfeMsg==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.11.0.tgz", + "integrity": "sha512-3r7VWZ/webh0SGgJScpWl2/MRCZK5d3ZYFcNaeci/GQ7Teop7zf0Nl2pUuz7G21BwPd9pcUPOC5KmJ2L3WgC5g==", "dev": true, "license": "MIT", "dependencies": { @@ -16300,14 +15545,11 @@ } }, "node_modules/unstorage/node_modules/lru-cache": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.3.0.tgz", - "integrity": "sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "ISC", - "engines": { - "node": "14 || >=16.14" - } + "license": "ISC" }, "node_modules/untildify": { "version": "4.0.0", @@ -16350,9 +15592,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, "funding": [ { @@ -16407,26 +15649,6 @@ "dev": true, "license": "MIT" }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/uri-js/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/url": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", @@ -16490,9 +15712,9 @@ } }, "node_modules/vinxi": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/vinxi/-/vinxi-0.3.12.tgz", - "integrity": "sha512-YU/Scild/Rdy6qwgdILYRlO99Wp8ti2CmlMlYioEg7lRtxAST5iCFjviDya+BYQDgc3Pugh4KzOypVwjZknF2A==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/vinxi/-/vinxi-0.4.1.tgz", + "integrity": "sha512-WGEYqIuJ2/P3sBoSVKsGvp/UKpW4wVSaAFdA18gthyMCEExN6nVteoA+Rv1wQFLKXTVL9JRpeGJjcLzcRRgGCA==", "dev": true, "dependencies": { "@babel/core": "^7.22.11", @@ -16534,421 +15756,15 @@ "vinxi": "bin/cli.mjs" } }, - "node_modules/vinxi/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vinxi/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/vinxi/node_modules/vite": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.2.tgz", - "integrity": "sha512-6lA7OBHBlXUxiJxbO5aAY2fsHHzDr1q7DvXYnyZycRs2Dz+dXBWuhpWHvmljTRTpQC2uvGmUFFkSHF2vGo90MA==", + "node_modules/vite": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.4.tgz", + "integrity": "sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.38", + "postcss": "^8.4.39", "rollup": "^4.13.0" }, "bin": { @@ -16996,102 +15812,6 @@ } } }, - "node_modules/vinxi/node_modules/vite/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/vite": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", - "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - }, - "peerDependencies": { - "@types/node": ">= 14", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, "node_modules/vite-plugin-inspect": { "version": "0.7.42", "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.7.42.tgz", @@ -17170,6 +15890,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/vite-plugin-mkcert": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/vite-plugin-mkcert/-/vite-plugin-mkcert-1.17.5.tgz", + "integrity": "sha512-KKGY3iHx/9zb7ow8JJ+nLN2HiNIBuPBwj34fJ+jAJT89/8qfk7msO7G7qipR8VDEm9xMCys0xT11QOJbZcg3/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/rest": "^20.0.2", + "axios": "^1.6.8", + "debug": "^4.3.4", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=v16.7.0" + }, + "peerDependencies": { + "vite": ">=3" + } + }, "node_modules/vite-plugin-node-polyfills": { "version": "0.22.0", "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.22.0.tgz", @@ -17188,16 +15927,16 @@ } }, "node_modules/vite-plugin-sass-dts": { - "version": "1.3.22", - "resolved": "https://registry.npmjs.org/vite-plugin-sass-dts/-/vite-plugin-sass-dts-1.3.22.tgz", - "integrity": "sha512-N09ApMznYMh8o2ab1HgGxrGnLGk1GPAtS+iZdiZWeq6ftRrKVxYSY99yyaDC6mSHczreb/JAC5HbPhrTSHaPEg==", + "version": "1.3.24", + "resolved": "https://registry.npmjs.org/vite-plugin-sass-dts/-/vite-plugin-sass-dts-1.3.24.tgz", + "integrity": "sha512-t6qxkC8seJJuYmlioKu2QFOJhJgIXwmAGbfOwmZ41QH1eqpqzgQkkXgWVLRf0DlO3FNMPn2y5bO9+R1N7LRgTA==", "dev": true, "license": "MIT", "dependencies": { "postcss-js": "^4.0.1" }, "engines": { - "node": "20.x" + "node": ">=20" }, "peerDependencies": { "postcss": "^8", @@ -17231,10 +15970,27 @@ } } }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -17244,15 +16000,14 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -17262,15 +16017,14 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -17280,15 +16034,14 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -17298,15 +16051,14 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -17316,15 +16068,14 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -17334,15 +16085,14 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -17352,15 +16102,14 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -17370,15 +16119,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -17388,15 +16136,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -17406,15 +16153,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -17424,15 +16170,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -17442,15 +16187,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -17460,15 +16204,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -17478,15 +16221,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -17496,15 +16238,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -17514,15 +16255,14 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -17532,15 +16272,14 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -17550,15 +16289,14 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -17568,15 +16306,14 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -17586,15 +16323,14 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -17604,15 +16340,14 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -17622,19 +16357,17 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=12" } }, "node_modules/vite/node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -17642,46 +16375,44 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, - "node_modules/vite/node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "peer": true, - "bin": { - "rollup": "dist/bin/rollup" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/vitefu": { @@ -17982,9 +16713,9 @@ } }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 72cb8757..32d3f562 100644 --- a/package.json +++ b/package.json @@ -2,71 +2,73 @@ "name": "discoursio-webapp", "private": true, "version": "0.9.5", - "contributors": [], "type": "module", "scripts": { "dev": "vinxi dev", "build": "vinxi build", "start": "vinxi start", "codegen": "graphql-codegen", - "e2e": "npx playwright test --project=webkit", - "fix": "npx @biomejs/biome check src/. --write && stylelint **/*.{scss,css} --fix", + "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", "typecheck": "tsc --noEmit" }, "devDependencies": { "@authorizerdev/authorizer-js": "^2.0.3", - "@biomejs/biome": "^1.8.2", + "@biomejs/biome": "^1.8.3", "@graphql-codegen/cli": "^5.0.2", - "@graphql-codegen/typescript": "^4.0.7", - "@graphql-codegen/typescript-operations": "^4.2.1", + "@graphql-codegen/typescript": "^4.0.9", + "@graphql-codegen/typescript-operations": "^4.2.3", "@graphql-codegen/typescript-urql": "^4.0.0", - "@hocuspocus/provider": "^2.13.2", - "@playwright/test": "^1.44.1", + "@hocuspocus/provider": "^2.13.5", + "@playwright/test": "^1.45.2", "@popperjs/core": "^2.11.8", "@solid-primitives/media": "^2.2.9", - "@solid-primitives/memo": "^1.3.8", + "@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": "^3.7.1", + "@solid-primitives/storage": "^3.8.0", "@solid-primitives/upload": "^0.0.117", "@solidjs/meta": "^0.29.4", - "@solidjs/router": "^0.13.6", - "@solidjs/start": "^1.0.2", - "@tiptap/core": "^2.4.0", - "@tiptap/extension-blockquote": "^2.4.0", - "@tiptap/extension-bold": "^2.4.0", - "@tiptap/extension-bubble-menu": "^2.4.0", - "@tiptap/extension-bullet-list": "^2.4.0", - "@tiptap/extension-character-count": "^2.4.0", - "@tiptap/extension-collaboration": "^2.4.0", - "@tiptap/extension-collaboration-cursor": "^2.4.0", - "@tiptap/extension-document": "^2.4.0", - "@tiptap/extension-dropcursor": "^2.4.0", - "@tiptap/extension-floating-menu": "^2.4.0", - "@tiptap/extension-focus": "^2.4.0", - "@tiptap/extension-gapcursor": "^2.4.0", - "@tiptap/extension-hard-break": "^2.4.0", - "@tiptap/extension-heading": "^2.4.0", - "@tiptap/extension-highlight": "^2.4.0", - "@tiptap/extension-history": "^2.4.0", - "@tiptap/extension-horizontal-rule": "^2.4.0", - "@tiptap/extension-image": "^2.4.0", - "@tiptap/extension-italic": "^2.4.0", - "@tiptap/extension-link": "^2.4.0", - "@tiptap/extension-list-item": "^2.4.0", - "@tiptap/extension-ordered-list": "^2.4.0", - "@tiptap/extension-paragraph": "^2.4.0", - "@tiptap/extension-placeholder": "^2.4.0", - "@tiptap/extension-strike": "^2.4.0", - "@tiptap/extension-text": "^2.4.0", - "@tiptap/extension-underline": "^2.4.0", - "@tiptap/extension-youtube": "^2.4.0", + "@solidjs/router": "^0.14.1", + "@solidjs/start": "^1.0.6", + "@tiptap/core": "^2.5.4", + "@tiptap/extension-blockquote": "^2.5.4", + "@tiptap/extension-bold": "^2.5.4", + "@tiptap/extension-bubble-menu": "^2.5.4", + "@tiptap/extension-bullet-list": "^2.5.4", + "@tiptap/extension-character-count": "^2.5.4", + "@tiptap/extension-collaboration": "^2.5.4", + "@tiptap/extension-collaboration-cursor": "^2.5.4", + "@tiptap/extension-document": "^2.5.4", + "@tiptap/extension-dropcursor": "^2.5.4", + "@tiptap/extension-floating-menu": "^2.5.4", + "@tiptap/extension-focus": "^2.5.4", + "@tiptap/extension-gapcursor": "^2.5.4", + "@tiptap/extension-hard-break": "^2.5.4", + "@tiptap/extension-heading": "^2.5.4", + "@tiptap/extension-highlight": "^2.5.4", + "@tiptap/extension-history": "^2.5.4", + "@tiptap/extension-horizontal-rule": "^2.5.4", + "@tiptap/extension-image": "^2.5.4", + "@tiptap/extension-italic": "^2.5.4", + "@tiptap/extension-link": "^2.5.4", + "@tiptap/extension-list-item": "^2.5.4", + "@tiptap/extension-ordered-list": "^2.5.4", + "@tiptap/extension-paragraph": "^2.5.4", + "@tiptap/extension-placeholder": "^2.5.4", + "@tiptap/extension-strike": "^2.5.4", + "@tiptap/extension-text": "^2.5.4", + "@tiptap/extension-underline": "^2.5.4", + "@tiptap/extension-youtube": "^2.5.4", "@types/cookie": "^0.6.0", "@types/cookie-signature": "^1.1.2", - "@types/node": "^20.14.8", + "@types/node": "^20.14.11", "@types/throttle-debounce": "^5.0.2", "@urql/core": "^5.0.4", "bootstrap": "^5.3.3", @@ -77,33 +79,36 @@ "extended-eventsource": "^1.4.9", "fast-deep-equal": "^3.1.3", "graphql": "^16.9.0", - "i18next": "^23.11.5", + "i18next": "^23.12.2", "i18next-http-backend": "^2.5.2", "i18next-icu": "^2.3.0", "intl-messageformat": "^10.5.14", "javascript-time-ago": "^2.5.10", "patch-package": "^8.0.0", - "prosemirror-history": "^1.4.0", - "prosemirror-trailing-node": "^2.0.8", - "prosemirror-view": "^1.33.8", - "sass": "^1.77.6", - "solid-js": "1.8.17", + "prosemirror-history": "^1.4.1", + "prosemirror-trailing-node": "^2.0.9", + "prosemirror-view": "^1.33.9", + "rollup-plugin-visualizer": "^5.12.0", + "sass": "1.76.0", + "solid-js": "^1.8.18", "solid-popper": "^0.3.0", "solid-tiptap": "0.7.0", "solid-transition-group": "^0.2.3", - "stylelint": "^16.6.1", + "stylelint": "^16.7.0", + "stylelint-config-recommended": "^14.0.1", "stylelint-config-standard-scss": "^13.1.0", "stylelint-order": "^6.0.4", - "stylelint-scss": "^6.3.2", - "swiper": "^11.1.4", + "stylelint-scss": "^6.4.1", + "swiper": "^11.1.5", "throttle-debounce": "^5.0.2", "tslib": "^2.6.3", - "typescript": "^5.5.2", + "typescript": "^5.5.3", "typograf": "^7.4.1", "uniqolor": "^1.1.1", - "vinxi": "^0.3.12", + "vinxi": "^0.4.1", + "vite-plugin-mkcert": "^1.17.5", "vite-plugin-node-polyfills": "^0.22.0", - "vite-plugin-sass-dts": "^1.3.22", + "vite-plugin-sass-dts": "^1.3.24", "y-prosemirror": "1.2.9", "yjs": "13.6.18" }, @@ -111,13 +116,13 @@ "yjs": "13.6.18", "y-prosemirror": "1.2.9" }, + "engines": { + "node": ">= 20" + }, "trustedDependencies": ["@biomejs/biome", "esbuild", "protobufjs"], "dependencies": { "form-data": "^4.0.0", "idb": "^8.0.0", - "mailgun.js": "^10.2.1" - }, - "engines": { - "node": "20.x" + "mailgun.js": "^10.2.3" } } diff --git a/playwright.config.ts b/playwright.config.ts index 978dadb9..b3bf954d 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -10,9 +10,10 @@ import { defineConfig, devices } from '@playwright/test' * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ + /* Directory to search for tests */ testDir: './tests', /* Run tests in files in parallel */ - fullyParallel: true, + 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 */ @@ -20,28 +21,23 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', + 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: 'http://127.0.0.1:3000', - + 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: 'chromium', - use: { ...devices['Desktop Chrome'] } - }, - - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] } - }, - { name: 'webkit', use: { ...devices['Desktop Safari'] } @@ -66,12 +62,17 @@ export default defineConfig({ // name: 'Google Chrome', // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, // }, - ] + ], /* Run local dev server before starting the tests */ - //webServer: { - // command: 'npm run dev', - // url: 'https://localhost:3000', - // reuseExistingServer: !process.env.CI, - //}, + /* 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 + } }) diff --git a/public/icons/create-music.svg b/public/icons/create-audio.svg similarity index 100% rename from public/icons/create-music.svg rename to public/icons/create-audio.svg diff --git a/public/icons/create-images.svg b/public/icons/create-image.svg similarity index 100% rename from public/icons/create-images.svg rename to public/icons/create-image.svg diff --git a/public/icons/create-books.svg b/public/icons/create-literature.svg similarity index 100% rename from public/icons/create-books.svg rename to public/icons/create-literature.svg diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json deleted file mode 100644 index e3b4dd66..00000000 --- a/public/locales/en/translation.json +++ /dev/null @@ -1,551 +0,0 @@ -{ - "A guide to horizontal editorial: how an open journal works": "A guide to horizontal editorial: how an open journal works", - "About": "About", - "About the project": "About the project", - "actions": "actions", - "Add": "Add", - "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 link": "Add link", - "Add rule": "Add rule", - "Add signature": "Add signature", - "Add subtitle": "Add subtitle", - "Add url": "Add url", - "Address on Discours": "Address on Discours", - "Album name": "Название aльбома", - "Alignment center": "Alignment center", - "Alignment left": "Alignment left", - "Alignment right": "Alignment right", - "All": "All", - "All articles": "All articles", - "All authors": "All authors", - "All posts": "All posts", - "all topics": "all topics", - "All topics": "All topics", - "Almost done! Check your email.": "Almost done! Just checking your email.", - "and some more authors": "{restUsersCount, plural, =0 {} one { and one more user} other { and more {restUsersCount} users}}", - "Anything else": "Anything else", - "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", - "article": "article", - "Artist": "Artist", - "Artworks": "Artworks", - "Audio": "Audio", - "author": "author", - "Author": "Author", - "authors": "authors", - "Authors": "Authors", - "Autotypograph": "Autotypograph", - "Back": "Back", - "Back to editor": "Back to editor", - "Back to main page": "Back to main page", - "back to menu": "back to menu", - "Be the first to rate": "Be the first to rate", - "Become an author": "Become an author", - "bold": "bold", - "Bold": "Bold", - "Bookmarked": "Saved", - "bookmarks": "bookmarks", - "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", - "Can make any changes, accept or reject suggestions, and share access with others": "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 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": "Can write and edit text directly, and accept or reject suggestions from others", - "cancel": "cancel", - "Cancel": "Cancel", - "Cancel changes": "Cancel changes", - "Change password": "Change password", - "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", - "Close": "Close", - "Co-author": "Co-author", - "Collaborate": "Help Edit", - "Collaborators": "Collaborators", - "collections": "collections", - "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", - "Coming soon": "Coming soon", - "Comment successfully deleted": "Comment successfully deleted", - "Commentator": "Commentator", - "Commenting": "Commenting", - "Comments": "Comments", - "Common feed": "All", - "Communities": "Communities", - "community": "community", - "Community Discussion Rules": "Community Discussion Rules", - "Community Principles": "Community Principles", - "Community values and rules of engagement for the open editorial team": "Community values and rules of engagement for the open editorial team", - "Confirm": "Confirm", - "Confirm your new password": "Confirm your new password", - "Connect": "Connect", - "Contents": "Contents", - "Contribute to free samizdat. Support Discours - an independent non-profit publication that works only for you. Become a pillar of the open newsroom": "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", - "Copy": "Copy", - "Copy link": "Copy link", - "Corrections history": "Corrections history", - "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 Chat": "Create Chat", - "Create gallery": "Create gallery", - "Create Group": "Create a group", - "Create post": "Create post", - "Create video": "Create video", - "Crop image": "Crop image", - "Culture": "Culture", - "Current password": "Current password", - "Date of Birth": "Date of Birth", - "Decline": "Decline", - "Delete": "Delete", - "Delete cover": "Delete cover", - "Delete userpic": "Delete userpic", - "delimiter": "delimiter", - "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 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.
We are convinced that one voice is good, but many is better. We create the most amazing stories together", - "Discours is created with our common effort": "Discours exists because of our common effort", - "Discours Manifest": "Discours Manifest", - "Discours Partners": "Discours Partners", - "Discours – an open magazine about culture, science and society": "Discours – an open magazine about culture, science and society", - "Discussing": "Discussing", - "discussion": "Discours", - "Discussion rules": "Discussion rules", - "Discussions": "Discussions", - "Do you really want to reset all changes?": "Do you really want to reset all changes?", - "Dogma": "Dogma", - "dogma keywords": "Discours.io, dogma, editorial principles, code of ethics, journalism, community", - "Draft successfully deleted": "Draft successfully deleted", - "drafts": "drafts", - "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.", - "earlier": "earlier", - "Edit": "Edit", - "Edit profile": "Edit profile", - "Editing": "Editing", - "Editor": "Editor", - "Email": "Mail", - "email not confirmed": "email not confirmed", - "enter": "enter", - "Enter": "Enter", - "Enter a new password": "Enter a new password", - "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 URL address": "Enter URL address", - "Enter your new password": "Enter your new password", - "Error": "Error", - "Experience": "Experience", - "Failed to delete comment": "Failed to delete comment", - "FAQ": "Tips and suggestions", - "Favorite": "Favorites", - "Favorite topics": "Favorite topics", - "feed": "feed", - "Feed": "Feed", - "Feed settings": "Feed settings", - "Feedback": "Feedback", - "Fill email": "Fill email", - "Fixed": "Fixed", - "Follow": "Follow", - "Follow the topic": "Follow the topic", - "follower": "follower", - "Followers": "Followers", - "Following": "Following", - "Forgot password?": "Forgot password?", - "Forward": "Forward", - "from": "from", - "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 1": "Header 1", - "header 2": "header 2", - "Header 2": "Header 2", - "header 3": "header 3", - "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 Discours subscriptions": "Here you can manage all your Discours subscriptions", - "Here you can upload your photo": "Here you can upload your photo", - "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 Discours works": "How Discours works", - "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", - "Hundreds of people from different countries and cities share their knowledge and art on the Discours. Join us!": "Hundreds of people from different countries and cities share their knowledge and art on the Discours. Join us!", - "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", - "images": "images", - "In bookmarks, you can save favorite discussions and materials that you want to return to": "In bookmarks, you can save favorite discussions and materials that you want to return to", - "Inbox": "Inbox", - "Incorrect new password confirm": "Incorrect new password confirm", - "Incorrect old password": "Incorrect old password", - "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", - "Independent media project about culture, science, art and society with horizontal editing": "Independent media project about culture, science, art and society with horizontal editing", - "Insert footnote": "Insert footnote", - "Insert video link": "Insert video link", - "Interview": "Interview", - "Introduce": "Introduction", - "Invalid email": "Check if your email is correct", - "Invalid image URL": "Invalid image URL", - "invalid password": "invalid password", - "Invalid url format": "Invalid url format", - "Invite": "Invite", - "Invite co-authors": "Invite co-authors", - "Invite collaborators": "Invite collaborators", - "Invite to collab": "Invite to Collab", - "It does not look like url": "It doesn't look like a link", - "It's OK. Just enter your email to receive a link to change your password": "It's OK. Just enter your email to receive a link to change your password", - "italic": "italic", - "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!", - "journal": "journal", - "jpg, .png, max. 10 mb.": "jpg, .png, макс. 10 мб.", - "Just start typing...": "Just start typing...", - "keywords": "Discours.io, Discours magazine, Discours, culture, science, art, society, independent journalism, literature, music, cinema, video, photography", - "Knowledge base": "Knowledge base", - "Language": "Language", - "Last rev.": "Посл. изм.", - "Let's log in": "Let's log in", - "Link copied": "Link copied", - "Link copied to clipboard": "Link copied to clipboard", - "Link sent, check your email": "Link sent, check your email", - "List of authors of the open editorial community": "List of authors of the open editorial community", - "Lists": "Lists", - "literature": "literature", - "Literature": "Literature", - "Load more": "Show more", - "Loading": "Loading", - "Login and security": "Login and security", - "Logout": "Logout", - "Looks like you forgot to upload the video": "Looks like you forgot to upload the video", - "Manifest of samizdat: principles and mission of an open magazine with a horizontal editorial board": "Manifest of samizdat: principles and mission of an open magazine with a horizontal editorial board", - "Manifesto": "Manifesto", - "Many files, choose only one": "Many files, choose only one", - "Mark as read": "Mark as read", - "marker list": "marker list", - "Material card": "Material card", - "Message": "Message", - "Message text": "Message text", - "min. 1400×1400 pix": "мин. 1400×1400 пикс.", - "More": "More", - "Most commented": "Commented", - "Most read": "Readable", - "Move down": "Move down", - "Move up": "Move up", - "music": "music", - "Music": "Music", - "my feed": "my ribbon", - "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!", - "Newsletter": "Newsletter", - "Night mode": "Night mode", - "No notifications yet": "No notifications yet", - "not verified": "not verified", - "Nothing here yet": "There's nothing here yet", - "Nothing is here": "There is nothing here", - "Notifications": "Notifications", - "number list": "number list", - "Or paste a link to an image": "Or paste a link to an image", - "or sign in with social networks": "or sign in with social networks", - "Ordered list": "Ordered list", - "Our regular contributor": "Our regular contributor", - "Paragraphs": "Абзацев", - "Participate in the Discours: share information, join the editorial team": "Участвуйте в Дискурсе: делитесь информацией, присоединяйтесь к редакции", - "Participating": "Participating", - "Participation": "Participation", - "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: !@#$%^&*", - "Password updated!": "Password updated!", - "Passwords are not equal": "Passwords are not equal", - "Paste Embed code": "Paste Embed code", - "Personal": "Personal", - "personal data usage and email notifications": "to process personal data and receive email notifications", - "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", - "Please, set the article title": "Please, set the article title", - "Please, set the main topic first": "Please, set the main topic first", - "Podcasts": "Podcasts", - "Poetry": "Poetry", - "Popular": "Popular", - "Popular authors": "Popular authors", - "post": "post", - "Principles": "Community principles", - "principles keywords": "Discours.io, communities, values, editorial rules, polyphony, creation", - "Professional principles that the open editorial team follows in its work": "Professional principles that the open editorial team follows in its work", - "Profile": "Profile", - "Profile settings": "Profile settings", - "Publications": "Publications", - "Publish Album": "Publish Album", - "Publish Settings": "Publish Settings", - "Published": "Published", - "Punchline": "Punchline", - "Quit": "Quit", - "Quote": "Quote", - "Quotes": "Quotes", - "Reason uknown": "Reason unknown", - "Recent": "Fresh", - "Recommend some new topic": "Recommend some new topic", - "register": "register", - "registered": "registered", - "Registered since {date}": "Registered since {date}", - "Remove link": "Remove link", - "repeat": "repeat", - "Repeat new password": "Repeat new password", - "Reply": "Reply", - "Report": "Complain", - "Report an error": "Report an error", - "Reports": "Reports", - "Required": "Required", - "Resend code": "Send confirmation", - "resend confirmation link": "resend confirmation link", - "Restore password": "Restore password", - "Rules of the journal Discours": "Rules of the journal Discours", - "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", - "Self-publishing exists thanks to the help of wonderful people from all over the world. Thank you!": "Samizdat exists thanks to the help of wonderful people from all over the world. Thank you!", - "Send": "Send", - "Send link again": "Send link again", - "Settings": "Settings", - "Settings for account, email, password and login methods.": "Settings for account, email, password and login methods.", - "Share": "Share", - "Share publication": "Share publication", - "shout": "post", - "Show": "Show", - "Show lyrics": "Show lyrics", - "Show more": "Show more", - "Show table of contents": "Show table of contents", - "sign up or sign in": "sign up or sign in", - "Site search": "Site search", - "Slug": "Slug", - "slug is used by another user": "Slug is already taken by another user", - "Social networks": "Social networks", - "Society": "Society", - "some authors": "{count} {count, plural, one {author} other {authors}}", - "some comments": "{count, plural, =0 {{count} comments} one {{count} comment} few {{count} comments} other {{count} comments}}", - "some followers": "{count} {count, plural, one {follower} other {followers}}", - "some followings": "{count, plural, =0 {no subscriptions} one {{count} subscription} other {{count} subscriptions}}", - "Some new comments to your publication": "{commentsCount, plural, one {New comment} other {{commentsCount} comments}} to your publication", - "Some new replies to your comment": "{commentsCount, plural, one {New reply} other {{commentsCount} replays}} to your publication", - "some posts": "{count, plural, =0 {no publications} one {{count} publication} other {{count} publications}}", - "some shouts": "{count} {count, plural, one {post} other {posts}}", - "some views": "{count} {count, plural, one {view} other {views}}", - "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", - "Soon": "Скоро", - "Sorry, this address is already taken, please choose another one.": "Sorry, this address is already taken, please choose another one", - "Special projects": "Special projects", - "Special Projects": "Special Projects", - "Specify the source and the name of the author": "Specify the source and the name of the author", - "Specify your e-mail and we will reply.": "Specify your e-mail and we will reply.", - "Start conversation": "Start a conversation", - "Start dialog": "Start dialog", - "Subsccriptions": "Subscriptions", - "Subscribe": "Subscribe", - "Subscribe to the best publications newsletter": "Subscribe to the best publications newsletter", - "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", - "subscriber": "subscriber", - "subscriber_rp": "subscriber", - "subscribers": "subscribers", - "Subscribing...": "Subscribing...", - "subscribing...": "subscribing...", - "subscription": "subscription", - "Subscription": "Subscription", - "subscription_rp": "subscription", - "subscriptions": "subscriptions", - "Subscriptions": "Subscriptions", - "Substrate": "Substrate", - "Success": "Success", - "Successfully authorized": "Authorization successful", - "Suggest an idea": "Suggest an idea", - "Support Discours": "Support Discours", - "Support the project": "Support the project", - "Support us": "Support us", - "terms of use": "terms of use", - "Terms of use": "Site rules", - "terms of use keywords": "Discours.io, site rules, terms of use", - "Text checking": "Text checking", - "Thank you": "Thank you", - "Thank you for reaching us": "Thank you for reaching us", - "Thank you!": "Thank you!", - "The address is already taken": "The address is already taken", - "The most interesting publications on the topic": "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 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": "Thematic table of contents of the magazine. Here you can find all the topics that the community authors wrote about", - "Themes and plots": "Themes and plots", - "Theory": "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 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 content is not published yet": "This content is not published yet", - "This email is": "This email is", - "This email is not verified": "This email is not verified", - "This email is registered": "This email is registered", - "This email is verified": "This email is verified", - "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 month": "This month", - "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 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 way you ll be able to subscribe to authors, interesting topics and customize your feed", - "This week": "This week", - "This year": "This year", - "To find publications, art, comments, authors and topics of interest to you, just start typing your query": "To find publications, art, comments, authors and topics of interest to you, just start typing your query", - "To leave a comment please": "To leave a comment please", - "To write a comment, you must": "To write a comment, you must", - "today": "today", - "Top authors": "Authors rating", - "Top commented": "Most commented", - "Top discussed": "Top discussed", - "Top month": "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", - "topicKeywords": "{topic}, Discours.io, articles, journalism, research", - "topics": "topics", - "Topics": "Topics", - "Topics which supported by author": "Topics which supported by author", - "try": "попробуйте", - "Try to find another way": "Try to find another way", - "Unfollow": "Unfollow", - "Unfollow the topic": "Unfollow the topic", - "Unnamed draft": "Unnamed draft", - "Unsubscribing...": "Unsubscribing...", - "Upload": "Upload", - "Upload error": "Upload error", - "Upload userpic": "Upload userpic", - "Upload video": "Upload video", - "Uploading image": "Uploading image", - "user already exist": "user already exists", - "User was not found": "User was not found", - "Username": "Username", - "Userpic": "Userpic", - "Users": "Users", - "verified": "verified", - "video": "video", - "Video": "Video", - "Video format not supported": "Video format not supported", - "view": "view", - "Views": "Views", - "Volounteering": "Volounteering", - "Want to suggest, discuss or advise something? Share a topic or an idea? Please send us a message!": "Want to suggest, discuss or advise something? Share a topic or an idea? Please send us a message!", - "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 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 can't find you, check email or", - "We couldn't find anything for your request": "We couldn’t find anything for your request", - "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", - "Why you can earn a hole in your karma and how to receive rays of gratitude for your contribution to discussions in samizdat communities": "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": "Cooperate with Discours", - "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 good articles, comment\nand it won't be so empty here": "Write good articles, comment\nand it won't be so empty here", - "Write message": "Write a message", - "Write to us": "Write to us", - "Write your colleagues name or email": "Write your colleague's name or email", - "yesterday": "yesterday", - "You can": "You can", - "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 can now login using your new password": "Теперь вы можете входить с помощью нового пароля", - "You can't edit this post": "You can't edit this post", - "You were successfully authorized": "You were successfully authorized", - "You ll be able to participate in discussions, rate others' comments and learn about new responses": "You ll be able to participate in discussions, rate others' comments and learn about 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 contact for answer": "Your contact for answer", - "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" -} diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json deleted file mode 100644 index bf097e77..00000000 --- a/public/locales/ru/translation.json +++ /dev/null @@ -1,579 +0,0 @@ -{ - "A guide to horizontal editorial: how an open journal works": "Гид по горизонтальной редакции: как работает открытый журнал", - "A short introduction to keep the reader interested": "Добавьте вступление, чтобы заинтересовать читателя", - "About": "О себе", - "About the project": "О проекте", - "actions": "действия", - "Add": "Добавить", - "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-виджет", - "Add another image": "Добавить другое изображение", - "Add audio": "Добавить аудио", - "Add blockquote": "Добавить цитату", - "Add comment": "Комментировать", - "Add cover": "Добавить обложку", - "Add image": "Добавить изображение", - "Add images": "Добавить изображения", - "Add intro": "Добавить вступление", - "Add link": "Добавить ссылку", - "add link": "добавить ссылку", - "Add rule": "Добавить разделитель", - "Add signature": "Добавить подпись", - "Add subtitle": "Добавить подзаголовок", - "Add to bookmarks": "Добавить в закладки", - "Add url": "Добавить ссылку", - "Address on Discours": "Адрес на Дискурсе", - "Album name": "Название альбома", - "Alignment center": "По центру", - "Alignment left": "По левому краю", - "Alignment right": "По правому краю", - "All": "Все", - "All articles": "Все материалы", - "All authors": "Все авторы", - "All posts": "Все публикации", - "All posts rating": "Рейтинг всех постов", - "All topics": "Все темы", - "all topics": "все темы", - "Almost done! Check your email.": "Почти готово! Осталось подтвердить вашу почту.", - "and some more authors": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}", - "Anything else": "Что-либо ещё", - "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": "автор", - "Authors": "Авторы", - "authors": "авторы", - "Autotypograph": "Автотипограф", - "Back": "Назад", - "Back to editor": "Вернуться в редактор", - "Back to main page": "Вернуться на главную", - "back to menu": "назад в меню", - "Be the first to rate": "Оцените первым", - "Become an author": "Стать автором", - "Bold": "Жирный", - "bold": "жирный", - "Bookmarked": "Сохранено", - "Bookmarks": "Закладки", - "bookmarks": "закладки", - "Bullet list": "Маркированный список", - "By alphabet": "По алфавиту", - "By authors": "По авторам", - "By name": "По имени", - "By popularity": "По популярности", - "By rating": "По рейтингу", - "By relevance": "По релевантности", - "By shouts": "По публикациям", - "By signing up you agree with our": "Регистрируясь, вы соглашаетесь с", - "By time": "По порядку", - "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": "отменить", - "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": "Коллекции", - "collections": "коллекции", - "Come up with a subtitle for your story": "Придумайте подзаголовок вашей истории", - "Come up with a title for your story": "Придумайте заголовок вашей истории", - "Coming soon": "Уже скоро", - "Comment": "Комментировать", - "Comment successfully deleted": "Комментарий успешно удален", - "Commentator": "Комментатор", - "Commenting": "Комментирование", - "Comments": "Комментарии", - "Common feed": "Общая лента", - "Communities": "Сообщества", - "community": "сообщество", - "Community Discussion Rules": "Правила дискуссий в сообществе", - "Community Principles": "Принципы сообщества", - "Community values and rules of engagement for the open editorial team": "Ценности сообщества и правила взаимодействия открытой редакции", - "Confirm": "Подтвердить", - "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": "Соучаствовать", - "Copy": "Скопировать", - "Copy link": "Скопировать ссылку", - "Corrections history": "История правок", - "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": "Создать видео", - "create_chat": "Создать чат", - "create_group": "Создать группу", - "Crop image": "Кадрировать изображение", - "Culture": "Культура", - "Current password": "Текущий пароль", - "Date of Birth": "Дата рождения", - "Decline": "Отмена", - "Delete": "Удалить", - "Delete cover": "Удалить обложку", - "Delete userpic": "Удалить аватар", - "delimiter": "разделитель", - "Description": "Описание", - "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": "Дискурс — это интеллектуальная среда, веб-пространство и инструменты, которые позволяют авторам сотрудничать с читателями и объединяться для совместного создания публикаций и медиапроектов.
Мы убеждены, один голос хорошо, а много — лучше. Самые потрясающиe истории мы создаём вместе.", - "Discours is created with our common effort": "Дискурс существует благодаря нашему общему вкладу", - "Discours Manifest": "Манифест Дискурса", - "Discours Partners": "Партнеры Дискурса", - "Discours – an open magazine about culture, science and society": "Дискурс – открытый журнал о культуре, науке и обществе", - "Discours_theme": "Тема дискурса", - "Discussing": "Обсуждаемое", - "discussion": "дискурс", - "Discussion rules": "Правила дискуссий", - "Discussions": "Дискуссии", - "Do you really want to reset all changes?": "Вы действительно хотите сбросить все изменения?", - "Dogma": "Догма", - "dogma keywords": "Discours.io, догма, редакционные принципы, этический кодекс, журналистика, сообщество", - "Draft successfully deleted": "Черновик успешно удален", - "Drafts": "Черновики", - "drafts": "черновики", - "Drag the image to this area": "Перетащите изображение в эту область", - "Each image must be no larger than 5 MB.": "Каждое изображение должно быть размером не больше 5 мб.", - "earlier": "ранее", - "Edit": "Редактировать", - "Edit profile": "Редактировать профиль", - "Editing": "Редактирование", - "Editor": "Редактор", - "Email": "Почта", - "email not confirmed": "email не подтвержден", - "Enter": "Войти", - "enter": "войти", - "Enter a new password": "Введите новый пароль", - "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": "Не удалось удалить комментарий", - "FAQ": "Советы и предложения", - "Favorite": "Избранное", - "Favorite topics": "Избранные темы", - "Feed": "Лента", - "feed": "лента", - "Feed settings": "Настроить ленту", - "Feedback": "Обратная связь", - "Fill email": "Введите почту", - "Fixed": "Все поправлено", - "Follow": "Подписаться", - "Follow the topic": "Подписаться на тему", - "follower": "подписчик", - "Followers": "Подписчики", - "Following": "Вы подписаны", - "Forgot password?": "Забыли пароль?", - "Forward": "Переслать", - "from": "от", - "Full name": "Имя и фамилия", - "Gallery": "Галерея", - "Gallery name": "Название галереи", - "Genre...": "Жанр...", - "Get notifications": "Получать уведомления", - "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": "Перейти на главную", - "Group Chat": "Общий чат", - "Groups": "Группы", - "Header": "Заголовок", - "Header 1": "Заголовок 1", - "header 1": "заголовок 1", - "Header 2": "Заголовок 2", - "header 2": "заголовок 2", - "Header 3": "Заголовок 3", - "header 3": "заголовок 3", - "Headers": "Заголовки", - "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": "Здесь вы можете загрузить свою фотографию", - "Hide table of contents": "Скрыть главление", - "Highlight": "Подсветка", - "Hooray! Welcome!": "Ура! Добро пожаловать!", - "Horizontal collaborative journalistic platform": "Открытая платформа
для независимой журналистики", - "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": "Тип изображения не поддерживается", - "images": "изображения", - "In bookmarks, you can save favorite discussions and materials that you want to return to": "В закладках можно сохранять избранные дискуссии и материалы, к которым хочется вернуться", - "Inbox": "Входящие", - "Incorrect new password confirm": "Неверное подтверждение нового пароля", - "Incorrect old password": "Старый пароль не верен", - "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": "Курсив", - "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": "Давайте авторизуемся", - "Link copied": "Ссылка скопирована", - "Link copied to clipboard": "Ссылка скопирована в буфер обмена", - "Link sent, check your email": "Ссылка отправлена, проверьте почту", - "List of authors of the open editorial community": "Список авторов сообщества открытой редакции", - "Lists": "Списки", - "Literature": "Литература", - "literature": "литература", - "Load more": "Показать ещё", - "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": "Манифест самиздата: принципы и миссия открытого журнала с горизонтальной редакцией", - "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": "Переместить вверх", - "Music": "Музыка", - "music": "музыка", - "My feed": "Моя лента", - "my feed": "моя лента", - "My subscriptions": "Подписки", - "Name": "Имя", - "New literary work": "Новое произведение", - "New only": "Только новые", - "New password": "Новый пароль", - "New stories every day and even more!": "Каждый день вас ждут новые истории и ещё много всего интересного!", - "Newsletter": "Рассылка", - "Night mode": "Ночная тема", - "No notifications yet": "Уведомлений пока нет", - "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 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": "Личные", - "personal data usage and email notifications": "на обработку персональных данных и на получение почтовых уведомлений", - "Pin": "Закрепить", - "Platform Guide": "Гид по дискурсу", - "Please check your email address": "Пожалуйста, проверьте введенный адрес почты", - "Please check your inbox! We have sent a password reset link.": "Пожалуйста, проверьте свою почту, мы отправили вам письмо со ссылкой для сброса пароля", - "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, confirm email": "Пожалуйста, подтвердите электронную почту", - "Please, set the article title": "Пожалуйста, задайте заголовок статьи", - "Please, set the main topic first": "Пожалуйста, сначала выберите главную тему", - "Podcasts": "Подкасты", - "Poetry": "Поэзия", - "Popular": "Популярное", - "Popular authors": "Популярные авторы", - "post": "пост", - "Preview": "Предпросмотр", - "Principles": "Принципы сообщества", - "principles keywords": "Discours.io, сообщества, ценности, правила редакции, многоголосие, созидание", - "Professional principles that the open editorial team follows in its work": "Профессиональные принципы, которым открытая редакция следует в работе", - "Profile": "Профиль", - "Profile settings": "Настройки профиля", - "Profile successfully saved": "Профиль успешно сохранён", - "Publication settings": "Настройки публикации", - "Publications": "Публикации", - "Publish": "Опубликовать", - "Publish Album": "Опубликовать альбом", - "Publish Settings": "Настройки публикации", - "Published": "Опубликованные", - "Punchline": "Панчлайн", - "Quit": "Выйти", - "Quote": "Цитата", - "Quotes": "Цитаты", - "Reason uknown": "Причина неизвестна", - "Recent": "Свежее", - "Recommend some new topic": "Предложить тему", - "register": "зарегистрируйтесь", - "registered": "уже зарегистрирован", - "Registered since {date}": "На сайте c {date}", - "Release date...": "Дата выхода...", - "Remove link": "Убрать ссылку", - "repeat": "повторить", - "Repeat new password": "Повторите новый пароль", - "Reply": "Ответить", - "Report": "Пожаловаться", - "Report an error": "Сообщить об ошибке", - "Reports": "Репортажи", - "Required": "Поле обязательно для заполнения", - "Resend code": "Выслать подтверждение", - "resend confirmation link": "отправить ссылку ещё раз", - "Restore password": "Восстановить пароль", - "Rules of the journal Discours": "Правила журнала Дискурс", - "Save": "Сохранить", - "Save draft": "Сохранить черновик", - "Save settings": "Сохранить настройки", - "Saving...": "Сохраняем...", - "Scroll up": "Наверх", - "Search": "Поиск", - "Search author": "Поиск автора", - "Search topic": "Поиск темы", - "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 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": "Спецпроекты", - "Special projects": "Спецпроекты", - "Specify the source and the name of the author": "Укажите источник и имя автора", - "Specify your e-mail and we will reply.": "Укажите ваш e-mail и мы обязательно ответим.", - "squib": "Подверстка", - "Start conversation": "Начать беседу", - "Start dialog": "Начать диалог", - "Subheader": "Подзаголовок", - "Subscribe": "Подписаться", - "Subscribe to comments": "Подписаться на комментарии", - "Subscribe to the best publications newsletter": "Подпишитесь на рассылку лучших публикаций", - "Subscribe us": "Подпишитесь на нас", - "Subscribe what you like to tune your personal feed": "Подпишитесь на интересующие вас темы, чтобы настроить вашу персональную ленту и моментально узнавать о новых публикациях и обсуждениях", - "Subscribe who you like to tune your personal feed": "Подпишитесь на интересующих вас авторов, чтобы настроить вашу персональную ленту и моментально узнавать о новых публикациях и обсуждениях", - "subscriber": "подписчик", - "subscriber_rp": "подписчика", - "subscribers": "подписчиков", - "subscribing...": "Подписка...", - "Subscribing...": "Подписываем...", - "Subscription": "Подписка", - "Subscriptions": "Подписки", - "Substrate": "Подложка", - "Success": "Успешно", - "Successfully authorized": "Авторизация успешна", - "Suggest an idea": "Предложить идею", - "Support Discours": "Поддержите Дискурс", - "Support the project": "Поддержать проект", - "Support us": "Помочь журналу", - "Terms of use": "Правила сайта", - "terms of use": "правилами пользования сайтом", - "terms of use keywords": "Discours.io, правила сайта, terms of use", - "Text checking": "Проверка текста", - "Thank you": "Благодарности", - "Thank you for reaching us": "Спасибо, что связались с нами", - "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 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": "Для поиска публикаций, искусства, комментариев, интересных вам авторов и тем, просто начните вводить ваш запрос", - "To leave a comment please": "Чтобы оставить комментарий, необходимо", - "To write a comment, you must": "Чтобы написать комментарий, необходимо", - "today": "сегодня", - "Top authors": "Рейтинг авторов", - "Top commented": "Самое комментируемое", - "Top discussed": "Обсуждаемое", - "Top month": "Лучшее за месяц", - "Top rated": "Популярное", - "Top recent": "Самое новое", - "Top topics": "Интересные темы", - "Top viewed": "Самое читаемое", - "Topic is supported by": "Тему поддерживают", - "topicKeywords": "{topic}, Discours.io, статьи, журналистика, исследования", - "Topics": "Темы", - "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": "видео", - "Video format not supported": "Тип видео не поддерживается", - "view": "просмотр", - "Views": "Просмотры", - "Volounteering": "Волонтёрство", - "Want to suggest, discuss or advise something? Share a topic or an idea? Please send us a message!": "Хотите что-то предложить, обсудить или посоветовать? Поделиться темой или идеей? Напишите нам скорее!", - "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": "Добро пожаловать в Дискурс", - "Welcome to Discours to add to your bookmarks": "Войдите в Дискурс, чтобы добавить в закладки", - "Welcome to Discours to participate in discussions": "Войдите в Дискурс для участия в дискуссиях", - "Welcome to Discours to publish articles": "Войдите в Дискурс, чтобы публиковать статьи", - "Welcome to Discours to subscribe": "Войдите в Дискурс для подписки на новые публикации", - "Welcome to Discours to subscribe to new publications": "Войдите в Дискурс, чтобы подписаться", - "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 was successfully authorized": "Вы были успешно авторизованы", - "You ll be able to participate in discussions, rate others' comments and learn about new responses": "Вы сможете участвовать в обсуждениях, оценивать комментарии других и узнавать о новых ответах", - "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.": "Ваше имя появится на странице вашего профиля и как ваша подпись в публикациях, комментариях и откликах" -} diff --git a/src/app.tsx b/src/app.tsx index c47709f8..df984dfd 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -4,6 +4,7 @@ import { FileRoutes } from '@solidjs/start/router' import { type JSX, Suspense } from 'solid-js' import { Loading } from './components/_shared/Loading' +import { AuthorsProvider } from './context/authors' import { EditorProvider } from './context/editor' import { FeedProvider } from './context/feed' import { GraphQLClientProvider } from './context/graphql' @@ -24,7 +25,9 @@ export const Providers = (props: { children?: JSX.Element }) => { - }>{props.children} + + }>{props.children} + diff --git a/src/components/Article/Article.module.scss b/src/components/Article/Article.module.scss index 55b8dd95..e56638f3 100644 --- a/src/components/Article/Article.module.scss +++ b/src/components/Article/Article.module.scss @@ -27,6 +27,16 @@ img { } .shoutBody { + @include media-breakpoint-up(sm) { + :global(.width-30) { + width: 30%; + } + + :global(.width-50) { + width: 50%; + } + } + font-size: 1.6rem; line-height: 1.6; @@ -65,6 +75,16 @@ 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; @@ -78,21 +98,11 @@ img { &[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; - } - - &[data-float='right'] { - margin-left: 1.5em; - } + line-height: 1.4; } &::before { @@ -106,17 +116,17 @@ img { ta-border-sub { @include font-size(1.4rem); + @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; - @include media-breakpoint-up(md) { - margin: 3.2rem -8.3333%; - padding: 3.2rem 8.3333%; - } - p:last-child { margin-bottom: 0; } @@ -194,16 +204,6 @@ img { margin: 0 8.3333% 1.5em 0; } - @include media-breakpoint-up(sm) { - :global(.width-30) { - width: 30%; - } - - :global(.width-50) { - width: 50%; - } - } - :global(.img-align-left.width-50) { @include media-breakpoint-up(xl) { margin-left: -16.6666%; @@ -313,20 +313,24 @@ 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(lg) { - 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; @@ -334,10 +338,6 @@ img { vertical-align: baseline; cursor: pointer; - @include media-breakpoint-up(xl) { - margin-right: 3.2rem; - } - .icon { display: inline-block; margin-right: 0.2em; @@ -379,11 +379,11 @@ img { } .shoutStatsItemBookmarks { - margin-left: auto; - @include media-breakpoint-up(lg) { margin-left: 0; } + + margin-left: auto; } .shoutStatsItemInner { @@ -408,6 +408,15 @@ 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; @@ -418,24 +427,9 @@ img { opacity: 0.4; height: 2rem; } - - @include media-breakpoint-down(lg) { - flex: 1 100%; - order: 9; - - .shoutStatsItemAdditionalDataItem { - margin-left: 0; - } - } } .shoutStatsItemViews { - color: rgb(0 0 0 / 40%); - cursor: default; - font-weight: normal; - margin-left: auto; - white-space: nowrap; - @include media-breakpoint-down(lg) { bottom: 0; flex: 1 40%; @@ -449,6 +443,12 @@ img { display: none !important; } } + + color: rgb(0 0 0 / 40%); + cursor: default; + font-weight: normal; + margin-left: auto; + white-space: nowrap; } .shoutStatsItemLabel { @@ -457,11 +457,11 @@ img { } .commentsTextLabel { - display: none; - @include media-breakpoint-up(sm) { display: block; } + + display: none; } .shoutStatsItemCount { @@ -471,6 +471,12 @@ img { } .shoutStatsItemAdditionalDataItem { + @include media-breakpoint-down(sm) { + &:first-child { + margin-left: 0; + } + } + font-weight: normal; display: inline-block; @@ -478,12 +484,6 @@ img { margin-right: 0; margin-bottom: 0; cursor: default; - - @include media-breakpoint-down(sm) { - &:first-child { - margin-left: 0; - } - } } .topicsList { diff --git a/src/components/Article/AudioHeader/AudioHeader.tsx b/src/components/Article/AudioHeader/AudioHeader.tsx index 68454c97..aa36ca28 100644 --- a/src/components/Article/AudioHeader/AudioHeader.tsx +++ b/src/components/Article/AudioHeader/AudioHeader.tsx @@ -1,11 +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 { Topic } from '../../../graphql/schema/core.gen' import { CardTopic } from '../../Feed/CardTopic' -import { Icon } from '../../_shared/Icon' -import { Image } from '../../_shared/Image' import styles from './AudioHeader.module.scss' diff --git a/src/components/Article/AudioPlayer/AudioPlayer.module.scss b/src/components/Article/AudioPlayer/AudioPlayer.module.scss index 0a1f2293..7b953eef 100644 --- a/src/components/Article/AudioPlayer/AudioPlayer.module.scss +++ b/src/components/Article/AudioPlayer/AudioPlayer.module.scss @@ -3,27 +3,32 @@ } .playerHeader { - width: 100%; - display: flex; - justify-content: space-between; - @include media-breakpoint-down(sm) { flex-direction: column; } + + width: 100%; + display: flex; + justify-content: space-between; } .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; @@ -42,11 +47,6 @@ } } - @include media-breakpoint-down(sm) { - margin-top: 20px; - margin-left: 0; - } - .playButton { display: flex; align-items: center; diff --git a/src/components/Article/AudioPlayer/PlayerHeader.tsx b/src/components/Article/AudioPlayer/PlayerHeader.tsx index 2570682b..3894af1f 100644 --- a/src/components/Article/AudioPlayer/PlayerHeader.tsx +++ b/src/components/Article/AudioPlayer/PlayerHeader.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx' import { Show, createSignal } from 'solid-js' -import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler' -import { Icon } from '../../_shared/Icon' +import { Icon } from '~/components/_shared/Icon' +import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler' import { MediaItem } from '~/types/mediaitem' import styles from './AudioPlayer.module.scss' diff --git a/src/components/Article/AudioPlayer/PlayerPlaylist.tsx b/src/components/Article/AudioPlayer/PlayerPlaylist.tsx index 6766b82e..10a740a7 100644 --- a/src/components/Article/AudioPlayer/PlayerPlaylist.tsx +++ b/src/components/Article/AudioPlayer/PlayerPlaylist.tsx @@ -1,16 +1,16 @@ 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 { useLocalize } from '../../../context/localize' -import { getDescription } from '../../../utils/meta' -import { Icon } from '../../_shared/Icon' -import { Popover } from '../../_shared/Popover' +import { descFromBody } from '~/utils/meta' import { SharePopup, getShareUrl } from '../SharePopup' import styles from './AudioPlayer.module.scss' const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) -const GrowingTextarea = lazy(() => import('../../_shared/GrowingTextarea/GrowingTextarea')) +const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea')) type Props = { media: MediaItem[] @@ -137,7 +137,7 @@ export const PlayerPlaylist = (props: Props) => { > { {/* class={clsx(styles.commentControl, styles.commentControlComplain)}*/} {/* onClick={() => showModal('reportComment')}*/} {/*>*/} - {/* {t('Report')}*/} + {/* {t('Complain')}*/} {/**/} diff --git a/src/components/Article/CommentDate/CommentDate.tsx b/src/components/Article/CommentDate/CommentDate.tsx index ffcdcb99..295376c1 100644 --- a/src/components/Article/CommentDate/CommentDate.tsx +++ b/src/components/Article/CommentDate/CommentDate.tsx @@ -1,8 +1,8 @@ -import type { Reaction } from '../../../graphql/schema/core.gen' +import type { Reaction } from '~/graphql/schema/core.gen' import { clsx } from 'clsx' -import { useLocalize } from '../../../context/localize' +import { useLocalize } from '~/context/localize' import styles from './CommentDate.module.scss' diff --git a/src/components/Article/CommentsTree.tsx b/src/components/Article/CommentsTree.tsx index e942060b..13fffae5 100644 --- a/src/components/Article/CommentsTree.tsx +++ b/src/components/Article/CommentsTree.tsx @@ -1,19 +1,17 @@ import { clsx } from 'clsx' import { For, Show, createMemo, createSignal, lazy, onMount } from 'solid-js' -import { useLocalize } from '../../context/localize' -import { useReactions } from '../../context/reactions' -import { useSession } from '../../context/session' -import { Author, Reaction, ReactionKind, ReactionSort } from '../../graphql/schema/core.gen' -import { byCreated, byStat } from '../../utils/sortby' +import { useFeed } from '~/context/feed' +import { useLocalize } from '~/context/localize' +import { useReactions } from '~/context/reactions' +import { useSession } from '~/context/session' +import { Author, Reaction, ReactionKind, ReactionSort } from '~/graphql/schema/core.gen' +import { byCreated, byStat } from '~/lib/sort' +import { SortFunction } from '~/types/common' import { Button } from '../_shared/Button' import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated' - -import { Comment } from './Comment' - -import { SortFunction } from '~/context/authors' -import { useFeed } from '../../context/feed' import styles from './Article.module.scss' +import { Comment } from './Comment' const SimplifiedEditor = lazy(() => import('../Editor/SimplifiedEditor')) @@ -52,10 +50,10 @@ export const CommentsTree = (props: Props) => { }) const { seen } = useFeed() const shoutLastSeen = createMemo(() => seen()[props.shoutSlug] ?? 0) - const currentDate = new Date() - const setCookie = () => localStorage.setItem(`${props.shoutSlug}`, `${currentDate}`) onMount(() => { + const currentDate = new Date() + const setCookie = () => localStorage?.setItem(`${props.shoutSlug}`, `${currentDate}`) if (!shoutLastSeen()) { setCookie() } else if (currentDate.getTime() > shoutLastSeen()) { @@ -98,7 +96,7 @@ export const CommentsTree = (props: Props) => {

{t('Comments')} {comments().length.toString() || ''} 0}> -  +{newReactions().length} + {` +${newReactions().length}`}

0}> @@ -150,7 +148,7 @@ export const CommentsTree = (props: Props) => { {t('sign up')} {' '} - {t('or')}  + {t('or')}{' '} {t('sign in')} diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index fea3eb57..4fa83ef9 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -1,44 +1,41 @@ import { createPopper } from '@popperjs/core' +import { Link } from '@solidjs/meta' +import { A, useSearchParams } from '@solidjs/router' import { clsx } from 'clsx' -// import { install } from 'ga-gtag' import { For, Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js' import { isServer } from 'solid-js/web' - -import { Link, Meta } from '@solidjs/meta' +import { useFeed } from '~/context/feed' +import { useLocalize } from '~/context/localize' +import { useReactions } from '~/context/reactions' +import { useSession } from '~/context/session' import { DEFAULT_HEADER_OFFSET, useUI } from '~/context/ui' +import type { Author, Maybe, Shout, Topic } from '~/graphql/schema/core.gen' +import { processPrepositions } from '~/intl/prepositions' +import { isCyrillic } from '~/intl/translate' +import { getImageUrl } from '~/lib/getThumbUrl' import { MediaItem } from '~/types/mediaitem' -import { useLocalize } from '../../context/localize' -import { useReactions } from '../../context/reactions' -import { useSession } from '../../context/session' -import type { Author, Maybe, Shout, Topic } from '../../graphql/schema/core.gen' -import { capitalize } from '../../utils/capitalize' -import { getImageUrl, getOpenGraphImageUrl } from '../../utils/getImageUrl' -import { getDescription, getKeywords } from '../../utils/meta' -import { isCyrillic } from '../../utils/translate' +import { capitalize } from '~/utils/capitalize' import { AuthorBadge } from '../Author/AuthorBadge' import { CardTopic } from '../Feed/CardTopic' import { FeedArticlePopup } from '../Feed/FeedArticlePopup' -import { Modal } from '../Nav/Modal' -import { TableOfContents } from '../TableOfContents' +import stylesHeader from '../HeaderNav/Header.module.scss' import { Icon } from '../_shared/Icon' import { Image } from '../_shared/Image' import { InviteMembers } from '../_shared/InviteMembers' import { Lightbox } from '../_shared/Lightbox' +import { Modal } from '../_shared/Modal' import { Popover } from '../_shared/Popover' import { ShareModal } from '../_shared/ShareModal' import { ImageSwiper } from '../_shared/SolidSwiper' +import { TableOfContents } from '../_shared/TableOfContents' import { VideoPlayer } from '../_shared/VideoPlayer' +import styles from './Article.module.scss' import { AudioHeader } from './AudioHeader' import { AudioPlayer } from './AudioPlayer' import { CommentsTree } from './CommentsTree' import { RatingControl as ShoutRatingControl } from './RatingControl' import { SharePopup, getShareUrl } from './SharePopup' -import { A, useSearchParams } from '@solidjs/router' -import { useFeed } from '~/context/feed' -import stylesHeader from '../Nav/Header/Header.module.scss' -import styles from './Article.module.scss' - type Props = { article: Shout scrollToComments?: boolean @@ -57,15 +54,17 @@ export type ArticlePageSearchParams = { const scrollTo = (el: HTMLElement) => { const { top } = el.getBoundingClientRect() - if (window) - window.scrollTo({ - top: top + window.scrollY - DEFAULT_HEADER_OFFSET, - left: 0, - behavior: 'smooth' - }) + + window?.scrollTo({ + top: top + window.scrollY - DEFAULT_HEADER_OFFSET, + left: 0, + behavior: 'smooth' + }) } const imgSrcRegExp = /]+src\s*=\s*["']([^"']+)["']/gi +const COMMENTS_PER_PAGE = 30 +const VOTES_PER_PAGE = 50 export const FullArticle = (props: Props) => { const [searchParams, changeSearchParams] = useSearchParams() @@ -78,25 +77,45 @@ export const FullArticle = (props: Props) => { const { session, requireAuthentication } = useSession() const author = createMemo(() => session()?.user?.app_data?.profile as Author) const { addSeen } = useFeed() + const formattedDate = createMemo(() => formatDate(new Date((props.article.published_at || 0) * 1000))) - const formattedDate = createMemo(() => formatDate(new Date((props.article?.published_at || 0) * 1000))) + const [pages, setPages] = createSignal>({}) + createEffect( + on( + pages, + async (p: Record) => { + await loadReactionsBy({ + by: { shout: props.article.slug, comment: true }, + limit: COMMENTS_PER_PAGE, + offset: COMMENTS_PER_PAGE * p.comments || 0 + }) + await loadReactionsBy({ + by: { shout: props.article.slug, rating: true }, + limit: VOTES_PER_PAGE, + offset: VOTES_PER_PAGE * p.rating || 0 + }) + setIsReactionsLoaded(true) + }, + { defer: true } + ) + ) const canEdit = createMemo( () => Boolean(author()?.id) && - (props.article?.authors?.some((a) => Boolean(a) && a?.id === author().id) || - props.article?.created_by?.id === author().id || + (props.article.authors?.some((a) => Boolean(a) && a?.id === author().id) || + props.article.created_by?.id === author().id || session()?.user?.roles?.includes('editor')) ) const mainTopic = createMemo(() => { - const mainTopicSlug = (props.article?.topics?.length || 0) > 0 ? props.article.main_topic : null + const mainTopicSlug = (props.article.topics?.length || 0) > 0 ? props.article.main_topic : null const mt = props.article.topics?.find((tpc: Maybe) => tpc?.slug === mainTopicSlug) if (mt) { mt.title = lang() === 'en' ? capitalize(mt.slug.replace(/-/, ' ')) : mt.title return mt } - return props.article?.topics?.[0] + return props.article.topics?.[0] }) const handleBookmarkButtonClick = (ev: MouseEvent | undefined) => { @@ -109,17 +128,17 @@ export const FullArticle = (props: Props) => { const body = createMemo(() => { if (props.article.layout === 'literature') { try { - if (props.article?.media) { + if (props.article.media) { const media = JSON.parse(props.article.media) if (media.length > 0) { - return media[0].body + return processPrepositions(media[0].body) } } } catch (error) { console.error(error) } } - return props.article.body + return processPrepositions(props.article.body) || '' }) const imageUrls = createMemo(() => { @@ -143,13 +162,7 @@ export const FullArticle = (props: Props) => { return Array.from(imageElements).map((img) => img.src) }) - const media = createMemo(() => { - try { - return JSON.parse(props.article?.media || '[]') - } catch { - return [] - } - }) + const media = createMemo(() => JSON.parse(props.article.media || '[]')) let commentsRef: HTMLDivElement | undefined @@ -270,7 +283,8 @@ export const FullArticle = (props: Props) => { // Check iframes size let articleContainer: HTMLElement | undefined const updateIframeSizes = () => { - if (!(articleContainer && props.article.body && window)) return + if (!window) return + if (!(articleContainer && props.article.body)) return const iframes = articleContainer?.querySelectorAll('iframe') if (!iframes) return const containerWidth = articleContainer?.offsetWidth @@ -306,67 +320,25 @@ export const FullArticle = (props: Props) => { // install('G-LQ4B87H8C2') await loadReactionsBy({ by: { shout: props.article.slug } }) addSeen(props.article.slug) - setIsReactionsLoaded(true) document.title = props.article.title + updateIframeSizes() window?.addEventListener('resize', updateIframeSizes) - onCleanup(() => window.removeEventListener('resize', updateIframeSizes)) - - createEffect(() => { - if (props.scrollToComments && commentsRef) { - scrollTo(commentsRef) - } - }) - - createEffect(() => { - if (searchParams?.scrollTo === 'comments' && commentsRef) { - requestAnimationFrame(() => commentsRef && scrollTo(commentsRef)) - changeSearchParams({ scrollTo: undefined }) - } - }) }) - createEffect( - on( - () => props.article, - async (shout: Shout) => { - setIsReactionsLoaded(false) - const rrr = await loadReactionsBy({ by: { shout: shout?.slug } }) - setRatings((_) => rrr.filter((r) => ['LIKE', 'DISLIKE'].includes(r.kind))) - setIsReactionsLoaded(true) - }, - { defer: true }, - ), - ) - - const cover = props.article.cover ?? 'production/image/logo_image.png' - const ogImage = getOpenGraphImageUrl(cover, { - title: props.article.title, - topic: mainTopic()?.title || '', - author: props.article?.authors?.[0]?.name || '', - width: 1200 + createEffect(() => props.scrollToComments && commentsRef && scrollTo(commentsRef)) + createEffect(() => { + if (searchParams?.scrollTo === 'comments' && commentsRef) { + requestAnimationFrame(() => commentsRef && scrollTo(commentsRef)) + changeSearchParams({ scrollTo: undefined }) + } }) - const description = getDescription(props.article.description || body() || media()[0]?.body) - const ogTitle = props.article.title - const keywords = getKeywords(props.article) - const shareUrl = getShareUrl({ pathname: `/${props.article.slug}` }) - const getAuthorName = (a: Author) => { - return lang() === 'en' && isCyrillic(a.name || '') ? capitalize(a.slug.replace(/-/, ' ')) : a.name - } + const shareUrl = createMemo(() => getShareUrl({ pathname: `/${props.article.slug || ''}` })) + const getAuthorName = (a: Author) => + lang() === 'en' && isCyrillic(a.name || '') ? capitalize(a.slug.replace(/-/, ' ')) : a.name return ( <> - - - - - - - - - - - {(imageUrl) => }
@@ -382,9 +354,9 @@ export const FullArticle = (props: Props) => { -

{props.article.title}

+

{props.article.title || ''}

-

{props.article.subtitle}

+

{processPrepositions(props.article.subtitle || '')}

@@ -392,7 +364,7 @@ export const FullArticle = (props: Props) => { {(a: Maybe, index: () => number) => ( <> 0}>, - {a && getAuthorName(a)} + {a && getAuthorName(a)} )} @@ -416,18 +388,18 @@ export const FullArticle = (props: Props) => {
-
+
0}>
- +
@@ -539,9 +511,9 @@ export const FullArticle = (props: Props) => {
setIsActionPopupActive(isVisible)} trigger={ @@ -559,7 +531,7 @@ export const FullArticle = (props: Props) => { {(triggerRef: (el: HTMLElement) => void) => (
- + @@ -596,7 +568,7 @@ export const FullArticle = (props: Props) => {
- + {(topic) => (
@@ -640,9 +612,9 @@ export const FullArticle = (props: Props) => { ) diff --git a/src/components/Article/ShoutRatingControl.tsx b/src/components/Article/ShoutRatingControl.tsx index e69de29b..8b137891 100644 --- a/src/components/Article/ShoutRatingControl.tsx +++ b/src/components/Article/ShoutRatingControl.tsx @@ -0,0 +1 @@ + diff --git a/src/components/AuthGuard/AuthGuard.tsx b/src/components/AuthGuard/AuthGuard.tsx index 2224f9f9..3390fbe3 100644 --- a/src/components/AuthGuard/AuthGuard.tsx +++ b/src/components/AuthGuard/AuthGuard.tsx @@ -1,7 +1,7 @@ import { useSearchParams } from '@solidjs/router' import { JSX, Show, createEffect, createMemo, on } from 'solid-js' +import { useSession } from '~/context/session' import { useUI } from '~/context/ui' -import { useSession } from '../../context/session' type Props = { children: JSX.Element diff --git a/src/components/Nav/AuthModal/AuthModal.module.scss b/src/components/AuthModal/AuthModal.module.scss similarity index 100% rename from src/components/Nav/AuthModal/AuthModal.module.scss rename to src/components/AuthModal/AuthModal.module.scss index 2d4cbc98..1e72c779 100644 --- a/src/components/Nav/AuthModal/AuthModal.module.scss +++ b/src/components/AuthModal/AuthModal.module.scss @@ -1,13 +1,13 @@ .view { + @include media-breakpoint-up(md) { + min-height: 600px; + } + background: var(--background-color); min-height: 550px; position: relative; justify-content: center; - @include media-breakpoint-up(md) { - min-height: 600px; - } - input { font-size: 1.7rem; } @@ -41,6 +41,10 @@ .authImage { @include font-size(1.5rem); + @include media-breakpoint-down(sm) { + display: none; + } + background: var(--background-color-invert) url('https://images.discours.io/unsafe/1600x/production/image/auth-page.jpg') center no-repeat; background-size: cover; @@ -49,10 +53,6 @@ padding: 3em; position: relative; - @include media-breakpoint-down(sm) { - display: none; - } - h2 { text-transform: uppercase; } @@ -118,13 +118,13 @@ } .auth { - display: flex; - flex-direction: column; - padding: $container-padding-x; - @include media-breakpoint-up(lg) { padding: 4rem !important; } + + display: flex; + flex-direction: column; + padding: $container-padding-x; } .submitButton { diff --git a/src/components/Nav/AuthModal/AuthModalHeader/AuthModalHeader.module.scss b/src/components/AuthModal/AuthModalHeader/AuthModalHeader.module.scss similarity index 100% rename from src/components/Nav/AuthModal/AuthModalHeader/AuthModalHeader.module.scss rename to src/components/AuthModal/AuthModalHeader/AuthModalHeader.module.scss diff --git a/src/components/Nav/AuthModal/AuthModalHeader/AuthModalHeader.tsx b/src/components/AuthModal/AuthModalHeader/AuthModalHeader.tsx similarity index 71% rename from src/components/Nav/AuthModal/AuthModalHeader/AuthModalHeader.tsx rename to src/components/AuthModal/AuthModalHeader/AuthModalHeader.tsx index 2d800c0c..38bff6a5 100644 --- a/src/components/Nav/AuthModal/AuthModalHeader/AuthModalHeader.tsx +++ b/src/components/AuthModal/AuthModalHeader/AuthModalHeader.tsx @@ -1,6 +1,6 @@ import { useSearchParams } from '@solidjs/router' import { Show } from 'solid-js' -import { useLocalize } from '../../../../context/localize' +import { useLocalize } from '~/context/localize' import styles from './AuthModalHeader.module.scss' type Props = { @@ -14,7 +14,7 @@ export const AuthModalHeader = (props: Props) => { const generateModalTextsFromSource = ( modalType: 'login' | 'register' ): { title: string; description: string } => { - const title = modalType === 'login' ? 'Welcome to Discours' : 'Create account' + const title = modalType === 'login' ? 'Welcome to Discours' : 'Sign up' switch (searchParams?.source) { case 'create': { @@ -27,7 +27,7 @@ export const AuthModalHeader = (props: Props) => { return { title: t(`${title} to add to your bookmarks`), description: t( - 'In bookmarks, you can save favorite discussions and materials that you want to return to' + 'In bookmarks, you can save favorite discussions and materials that you want to return to' ) } } @@ -35,7 +35,7 @@ export const AuthModalHeader = (props: Props) => { return { title: t(`${title} to participate in discussions`), description: t( - "You ll be able to participate in discussions, rate others' comments and learn about new responses" + "You ll be able to participate in discussions, rate others' comments and learn about new responses" ) } } @@ -43,7 +43,7 @@ export const AuthModalHeader = (props: Props) => { return { title: t(`${title} to subscribe`), description: t( - 'This way you ll be able to subscribe to authors, interesting topics and customize your feed' + 'This way you ll be able to subscribe to authors, interesting topics and customize your feed' ) } } @@ -51,7 +51,7 @@ export const AuthModalHeader = (props: Props) => { return { title: t(`${title} to subscribe to new publications`), description: t( - 'This way you ll be able to subscribe to authors, interesting topics and customize your feed' + 'This way you ll be able to subscribe to authors, interesting topics and customize your feed' ) } } @@ -59,7 +59,7 @@ export const AuthModalHeader = (props: Props) => { return { title: t(`${title} to vote`), description: t( - '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 we ll realize that you re a real person and ll take your vote into account. And you ll see how others voted' ) } } diff --git a/src/components/Nav/AuthModal/AuthModalHeader/index.ts b/src/components/AuthModal/AuthModalHeader/index.ts similarity index 100% rename from src/components/Nav/AuthModal/AuthModalHeader/index.ts rename to src/components/AuthModal/AuthModalHeader/index.ts diff --git a/src/components/Nav/AuthModal/ChangePasswordForm.tsx b/src/components/AuthModal/ChangePasswordForm.tsx similarity index 96% rename from src/components/Nav/AuthModal/ChangePasswordForm.tsx rename to src/components/AuthModal/ChangePasswordForm.tsx index a2c0a129..84b190c4 100644 --- a/src/components/Nav/AuthModal/ChangePasswordForm.tsx +++ b/src/components/AuthModal/ChangePasswordForm.tsx @@ -1,9 +1,9 @@ import { clsx } from 'clsx' import { JSX, Show, createSignal } from 'solid-js' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' import { useUI } from '~/context/ui' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' import { PasswordField } from './PasswordField' import { useSearchParams } from '@solidjs/router' diff --git a/src/components/Nav/AuthModal/EmailConfirm.tsx b/src/components/AuthModal/EmailConfirm.tsx similarity index 93% rename from src/components/Nav/AuthModal/EmailConfirm.tsx rename to src/components/AuthModal/EmailConfirm.tsx index 1766d3ef..9529bc0c 100644 --- a/src/components/Nav/AuthModal/EmailConfirm.tsx +++ b/src/components/AuthModal/EmailConfirm.tsx @@ -1,9 +1,9 @@ import { clsx } from 'clsx' import { Show, createEffect, createSignal } from 'solid-js' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' import { useUI } from '~/context/ui' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' import { email, setEmail } from './sharedLogic' diff --git a/src/components/Nav/AuthModal/LoginForm.tsx b/src/components/AuthModal/LoginForm.tsx similarity index 97% rename from src/components/Nav/AuthModal/LoginForm.tsx rename to src/components/AuthModal/LoginForm.tsx index 1d4a1825..7c6efc10 100644 --- a/src/components/Nav/AuthModal/LoginForm.tsx +++ b/src/components/AuthModal/LoginForm.tsx @@ -1,10 +1,10 @@ import { clsx } from 'clsx' import { JSX, Show, createSignal } from 'solid-js' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' import { useSnackbar, useUI } from '~/context/ui' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' -import { validateEmail } from '../../../utils/validateEmail' +import { validateEmail } from '~/utils/validate' import { AuthModalHeader } from './AuthModalHeader' import { PasswordField } from './PasswordField' diff --git a/src/components/Nav/AuthModal/PasswordField/PasswordField.module.scss b/src/components/AuthModal/PasswordField/PasswordField.module.scss similarity index 100% rename from src/components/Nav/AuthModal/PasswordField/PasswordField.module.scss rename to src/components/AuthModal/PasswordField/PasswordField.module.scss index f050d700..14be1fe7 100644 --- a/src/components/Nav/AuthModal/PasswordField/PasswordField.module.scss +++ b/src/components/AuthModal/PasswordField/PasswordField.module.scss @@ -26,13 +26,13 @@ line-height: 16px; margin-top: 0.3em; + /* Red/500 */ + color: orange; + &.registerPassword { margin-bottom: -32px; } - /* Red/500 */ - color: orange; - a { color: orange; border-color: orange; diff --git a/src/components/Nav/AuthModal/PasswordField/PasswordField.tsx b/src/components/AuthModal/PasswordField/PasswordField.tsx similarity index 96% rename from src/components/Nav/AuthModal/PasswordField/PasswordField.tsx rename to src/components/AuthModal/PasswordField/PasswordField.tsx index 32d80e4f..c2fbd029 100644 --- a/src/components/Nav/AuthModal/PasswordField/PasswordField.tsx +++ b/src/components/AuthModal/PasswordField/PasswordField.tsx @@ -1,9 +1,7 @@ import { clsx } from 'clsx' import { Show, createEffect, createSignal, on } from 'solid-js' - -import { useLocalize } from '../../../../context/localize' -import { Icon } from '../../../_shared/Icon' - +import { useLocalize } from '~/context/localize' +import { Icon } from '../../_shared/Icon' import styles from './PasswordField.module.scss' type Props = { diff --git a/src/components/Nav/AuthModal/PasswordField/index.ts b/src/components/AuthModal/PasswordField/index.ts similarity index 100% rename from src/components/Nav/AuthModal/PasswordField/index.ts rename to src/components/AuthModal/PasswordField/index.ts diff --git a/src/components/Nav/AuthModal/RegisterForm.tsx b/src/components/AuthModal/RegisterForm.tsx similarity index 97% rename from src/components/Nav/AuthModal/RegisterForm.tsx rename to src/components/AuthModal/RegisterForm.tsx index ecbb13ea..00ae273b 100644 --- a/src/components/Nav/AuthModal/RegisterForm.tsx +++ b/src/components/AuthModal/RegisterForm.tsx @@ -3,10 +3,10 @@ import type { JSX } from 'solid-js' import { Show, createMemo, createSignal } from 'solid-js' import { useSearchParams } from '@solidjs/router' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' import { useUI } from '~/context/ui' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' -import { validateEmail } from '../../../utils/validateEmail' +import { validateEmail } from '~/utils/validate' import { AuthModalHeader } from './AuthModalHeader' import { PasswordField } from './PasswordField' import { SocialProviders } from './SocialProviders' @@ -134,7 +134,7 @@ export const RegisterForm = () => { {t('This email is registered')}. {t('try')} {', '} changeSearchParams({ mode: 'login' })}> - {t('enter')} + {t('Enter').toLocaleLowerCase()} ) diff --git a/src/components/Nav/AuthModal/SendEmailConfirm.tsx b/src/components/AuthModal/SendEmailConfirm.tsx similarity index 91% rename from src/components/Nav/AuthModal/SendEmailConfirm.tsx rename to src/components/AuthModal/SendEmailConfirm.tsx index b2f03778..97f9e677 100644 --- a/src/components/Nav/AuthModal/SendEmailConfirm.tsx +++ b/src/components/AuthModal/SendEmailConfirm.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx' +import { useLocalize } from '~/context/localize' import { useUI } from '~/context/ui' -import { useLocalize } from '../../../context/localize' import styles from './AuthModal.module.scss' diff --git a/src/components/Nav/AuthModal/SendResetLinkForm.tsx b/src/components/AuthModal/SendResetLinkForm.tsx similarity index 96% rename from src/components/Nav/AuthModal/SendResetLinkForm.tsx rename to src/components/AuthModal/SendResetLinkForm.tsx index 29f5a9cf..f7ef038b 100644 --- a/src/components/Nav/AuthModal/SendResetLinkForm.tsx +++ b/src/components/AuthModal/SendResetLinkForm.tsx @@ -1,9 +1,9 @@ import { clsx } from 'clsx' import { JSX, Show, createSignal, onMount } from 'solid-js' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' -import { validateEmail } from '../../../utils/validateEmail' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import { validateEmail } from '~/utils/validate' import { email, setEmail } from './sharedLogic' import { useSearchParams } from '@solidjs/router' diff --git a/src/components/Nav/AuthModal/SocialProviders/SocialProviders.module.scss b/src/components/AuthModal/SocialProviders/SocialProviders.module.scss similarity index 100% rename from src/components/Nav/AuthModal/SocialProviders/SocialProviders.module.scss rename to src/components/AuthModal/SocialProviders/SocialProviders.module.scss diff --git a/src/components/Nav/AuthModal/SocialProviders/SocialProviders.tsx b/src/components/AuthModal/SocialProviders/SocialProviders.tsx similarity index 82% rename from src/components/Nav/AuthModal/SocialProviders/SocialProviders.tsx rename to src/components/AuthModal/SocialProviders/SocialProviders.tsx index 2aefd0c7..36d6c705 100644 --- a/src/components/Nav/AuthModal/SocialProviders/SocialProviders.tsx +++ b/src/components/AuthModal/SocialProviders/SocialProviders.tsx @@ -1,9 +1,7 @@ import { For } from 'solid-js' - -import { useLocalize } from '../../../../context/localize' -import { useSession } from '../../../../context/session' -import { Icon } from '../../../_shared/Icon' - +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import { Icon } from '../../_shared/Icon' import styles from './SocialProviders.module.scss' export const PROVIDERS = ['facebook', 'google', 'github'] // 'vk' | 'telegram' diff --git a/src/components/Nav/AuthModal/SocialProviders/index.ts b/src/components/AuthModal/SocialProviders/index.ts similarity index 100% rename from src/components/Nav/AuthModal/SocialProviders/index.ts rename to src/components/AuthModal/SocialProviders/index.ts diff --git a/src/components/Nav/AuthModal/index.tsx b/src/components/AuthModal/index.tsx similarity index 90% rename from src/components/Nav/AuthModal/index.tsx rename to src/components/AuthModal/index.tsx index c3ccaf61..b0b118c1 100644 --- a/src/components/Nav/AuthModal/index.tsx +++ b/src/components/AuthModal/index.tsx @@ -2,9 +2,9 @@ import { clsx } from 'clsx' import { Component, Show, createEffect, createMemo } from 'solid-js' import { Dynamic } from 'solid-js/web' +import { useLocalize } from '~/context/localize' import { AuthModalSource, useUI } from '~/context/ui' -import { useLocalize } from '../../../context/localize' -import { isMobile } from '../../../utils/media-query' +import { isMobile } from '~/lib/mediaQuery' import { ChangePasswordForm } from './ChangePasswordForm' import { EmailConfirm } from './EmailConfirm' import { LoginForm } from './LoginForm' @@ -75,21 +75,20 @@ export const AuthModal = () => { {t( 'Get to know the most intelligent people of our time, edit and discuss the articles, share your expertise, rate and decide what to publish in the magazine' )} - .  - {t('New stories every day and even more!')} + . {t('New stories and more are waiting for you every day!')}

{t('By signing up you agree with our')}{' '} { hideModal() }} > {t('terms of use')} - , {t('personal data usage and email notifications')}. + , {t('to process personal data and receive email notifications')}.

{' '} diff --git a/src/components/Nav/AuthModal/sharedLogic.tsx b/src/components/AuthModal/sharedLogic.tsx similarity index 100% rename from src/components/Nav/AuthModal/sharedLogic.tsx rename to src/components/AuthModal/sharedLogic.tsx diff --git a/src/components/Author/AuthorBadge/AuthorBadge.module.scss b/src/components/Author/AuthorBadge/AuthorBadge.module.scss index d3310486..0bc9a9d6 100644 --- a/src/components/Author/AuthorBadge/AuthorBadge.module.scss +++ b/src/components/Author/AuthorBadge/AuthorBadge.module.scss @@ -1,4 +1,8 @@ .AuthorBadge { + @include media-breakpoint-down(md) { + text-align: left; + } + align-items: flex-start; display: flex; gap: 1rem; @@ -12,34 +16,30 @@ } } - @include media-breakpoint-down(md) { - text-align: left; - } - .basicInfo { + @include media-breakpoint-down(sm) { + flex: 0 100%; + } + display: flex; flex-direction: row; align-items: center; flex: 0 calc(100% - 5.2rem); gap: 1rem; - - @include media-breakpoint-down(sm) { - flex: 0 100%; - } } .info { @include font-size(1.4rem); + @include media-breakpoint-up(sm) { + flex: 1 100%; + } + border: none; display: flex; flex-direction: column; line-height: 1.3; - @include media-breakpoint-up(sm) { - flex: 1 100%; - } - &:hover { background: unset; } @@ -70,12 +70,6 @@ } .actions { - flex: 0 20%; - display: flex; - flex-direction: row; - margin-left: 5.2rem; - gap: 1rem; - @include media-breakpoint-down(sm) { margin-left: 0; } @@ -90,6 +84,12 @@ padding-left: 1rem; text-align: right; } + + flex: 0 20%; + display: flex; + flex-direction: row; + margin-left: 5.2rem; + gap: 1rem; } .actionButton { diff --git a/src/components/Author/AuthorBadge/AuthorBadge.tsx b/src/components/Author/AuthorBadge/AuthorBadge.tsx index 71c357a9..ea9149b3 100644 --- a/src/components/Author/AuthorBadge/AuthorBadge.tsx +++ b/src/components/Author/AuthorBadge/AuthorBadge.tsx @@ -2,18 +2,18 @@ import { clsx } from 'clsx' import { Match, Show, Switch, createEffect, createMemo, createSignal, on } from 'solid-js' import { useNavigate, useSearchParams } from '@solidjs/router' -import { mediaMatches } from '~/utils/media-query' -import { useFollowing } from '../../../context/following' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' -import { Author, FollowingEntity } from '../../../graphql/schema/core.gen' -import { translit } from '../../../utils/ru2en' -import { isCyrillic } from '../../../utils/translate' -import { Button } from '../../_shared/Button' -import { CheckButton } from '../../_shared/CheckButton' -import { ConditionalWrapper } from '../../_shared/ConditionalWrapper' -import { FollowingButton } from '../../_shared/FollowingButton' -import { Icon } from '../../_shared/Icon' +import { Button } from '~/components/_shared/Button' +import { CheckButton } from '~/components/_shared/CheckButton' +import { ConditionalWrapper } from '~/components/_shared/ConditionalWrapper' +import { FollowingButton } from '~/components/_shared/FollowingButton' +import { Icon } from '~/components/_shared/Icon' +import { useFollowing } from '~/context/following' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import { Author, FollowingEntity } from '~/graphql/schema/core.gen' +import { isCyrillic } from '~/intl/translate' +import { translit } from '~/intl/translit' +import { mediaMatches } from '~/lib/mediaQuery' import { Userpic } from '../Userpic' import styles from './AuthorBadge.module.scss' @@ -94,7 +94,7 @@ export const AuthorBadge = (props: Props) => { ( - + {children} )} diff --git a/src/components/Author/AuthorCard/AuthorCard.module.scss b/src/components/Author/AuthorCard/AuthorCard.module.scss index 09885754..5c652d44 100644 --- a/src/components/Author/AuthorCard/AuthorCard.module.scss +++ b/src/components/Author/AuthorCard/AuthorCard.module.scss @@ -1,4 +1,16 @@ .author { + @include media-breakpoint-down(md) { + justify-content: center; + } + + @include media-breakpoint-up(md) { + margin-bottom: 2.4rem; + } + + @include media-breakpoint-down(lg) { + flex-wrap: wrap; + } + display: flex; align-items: center; flex-flow: row nowrap; @@ -8,14 +20,6 @@ margin-bottom: 0; } - @include media-breakpoint-down(md) { - justify-content: center; - } - - @include media-breakpoint-up(md) { - margin-bottom: 2.4rem; - } - .authorName { @include font-size(4rem); @@ -32,15 +36,15 @@ } .authorActions { + @include media-breakpoint-down(md) { + justify-content: center; + } + margin: 2rem -0.8rem 0 0; padding-left: 0; display: flex; flex-direction: row; gap: 1rem; - - @include media-breakpoint-down(md) { - justify-content: center; - } } .authorActionsLabel { @@ -50,27 +54,24 @@ } .authorActionsLabelMobile { - display: none; - @include media-breakpoint-down(sm) { display: block; } + + display: none; } .authorDetails { - display: block; - margin-bottom: 0; - @include media-breakpoint-down(md) { flex: 1 100%; text-align: center; } + + display: block; + margin-bottom: 0; } .listWrapper & { - align-items: flex-start; - margin-bottom: 2rem; - @include media-breakpoint-down(sm) { margin-bottom: 3rem; } @@ -79,6 +80,9 @@ margin-bottom: 3rem; } + align-items: flex-start; + margin-bottom: 2rem; + .circlewrap { margin-top: 1rem; } @@ -88,10 +92,6 @@ } } - @include media-breakpoint-down(lg) { - flex-wrap: wrap; - } - .buttonWriteMessage { border-radius: 0.8rem; padding-bottom: 0.6rem; @@ -100,8 +100,6 @@ } .authorDetails { - flex: 0 0 auto; - @include media-breakpoint-up(sm) { align-items: center; display: flex; @@ -118,12 +116,11 @@ flex-wrap: nowrap; } } + + flex: 0 0 auto; } .authorDetailsWrapper { - flex: 1 0; - position: relative; - @include media-breakpoint-up(sm) { flex: 1; } @@ -139,6 +136,9 @@ @include media-breakpoint-up(md) { padding-right: 1.2rem; } + + flex: 1 0; + position: relative; } .authorName { @@ -160,6 +160,15 @@ } .authorSubscribeSocial { + @include media-breakpoint-down(sm) { + flex: 1 100%; + justify-content: center; + } + + @include media-breakpoint-down(md) { + justify-content: center; + } + align-items: center; display: flex; margin: 0.5rem 0 2rem -0.4rem; @@ -415,15 +424,6 @@ } } - @include media-breakpoint-down(sm) { - flex: 1 100%; - justify-content: center; - } - - @include media-breakpoint-down(md) { - justify-content: center; - } - a:link { border: none; } @@ -434,14 +434,14 @@ } .subscribersContainer { + @include media-breakpoint-down(md) { + justify-content: center; + } + display: flex; flex-wrap: wrap; font-size: 1.4rem; gap: 1rem; margin-top: 0; white-space: nowrap; - - @include media-breakpoint-down(md) { - justify-content: center; - } } diff --git a/src/components/Author/AuthorCard/AuthorCard.tsx b/src/components/Author/AuthorCard/AuthorCard.tsx index 0cd605af..405749a9 100644 --- a/src/components/Author/AuthorCard/AuthorCard.tsx +++ b/src/components/Author/AuthorCard/AuthorCard.tsx @@ -1,25 +1,22 @@ -import type { Author, Community } from '../../../graphql/schema/core.gen' - +import { redirect, useNavigate, useSearchParams } from '@solidjs/router' import { clsx } from 'clsx' import { For, Show, createEffect, createMemo, createSignal, onMount } from 'solid-js' -import { FollowsFilter, useFollowing } from '../../../context/following' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' -import { FollowingEntity, Topic } from '../../../graphql/schema/core.gen' -import { isAuthor } from '../../../utils/isAuthor' -import { translit } from '../../../utils/ru2en' -import { isCyrillic } from '../../../utils/translate' +import { Button } from '~/components/_shared/Button' +import stylesButton from '~/components/_shared/Button/Button.module.scss' +import { FollowingCounters } from '~/components/_shared/FollowingCounters/FollowingCounters' +import { ShowOnlyOnClient } from '~/components/_shared/ShowOnlyOnClient' +import { FollowsFilter, useFollowing } from '~/context/following' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import type { Author, Community } from '~/graphql/schema/core.gen' +import { FollowingEntity, Topic } from '~/graphql/schema/core.gen' +import { isCyrillic } from '~/intl/translate' +import { translit } from '~/intl/translit' import { SharePopup, getShareUrl } from '../../Article/SharePopup' -import { Modal } from '../../Nav/Modal' import { TopicBadge } from '../../Topic/TopicBadge' -import { Button } from '../../_shared/Button' -import { FollowingCounters } from '../../_shared/FollowingCounters/FollowingCounters' -import { ShowOnlyOnClient } from '../../_shared/ShowOnlyOnClient' +import { Modal } from '../../_shared/Modal' import { AuthorBadge } from '../AuthorBadge' import { Userpic } from '../Userpic' - -import { useNavigate, useSearchParams } from '@solidjs/router' -import stylesButton from '../../_shared/Button/Button.module.scss' import styles from './AuthorCard.module.scss' type Props = { @@ -164,10 +161,10 @@ export const AuthorCard = (props: Props) => {
{(subscription) => - isAuthor(subscription) ? ( - + 'name' in subscription ? ( + ) : ( - + ) } @@ -252,7 +249,7 @@ export const AuthorCard = (props: Props) => {
}> {props.name} diff --git a/src/components/AuthorsList/AuthorsList.tsx b/src/components/AuthorsList/AuthorsList.tsx deleted file mode 100644 index cabc14be..00000000 --- a/src/components/AuthorsList/AuthorsList.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { clsx } from 'clsx' -import { For, Show, createEffect, createSignal, on } from 'solid-js' -import { useAuthors } from '~/context/authors' -import { useGraphQL } from '~/context/graphql' -import loadAuthorsByQuery from '~/graphql/query/core/authors-load-by' -import { Author } from '~/graphql/schema/core.gen' -import { useLocalize } from '../../context/localize' -import { AuthorBadge } from '../Author/AuthorBadge' -import { InlineLoader } from '../InlineLoader' -import { Button } from '../_shared/Button' -import styles from './AuthorsList.module.scss' - -type Props = { - class?: string - query: 'followers' | 'shouts' - searchQuery?: string - allAuthorsLength?: number -} - -const PAGE_SIZE = 20 - -export const AuthorsList = (props: Props) => { - const { t } = useLocalize() - const { addAuthors } = useAuthors() - const [authorsByShouts, setAuthorsByShouts] = createSignal() - const [authorsByFollowers, setAuthorsByFollowers] = createSignal() - const [loading, setLoading] = createSignal(false) - const [currentPage, setCurrentPage] = createSignal({ shouts: 0, followers: 0 }) - const [allLoaded, setAllLoaded] = createSignal(false) - const { query } = useGraphQL() - - const fetchAuthors = async (queryType: Props['query'], page: number) => { - setLoading(true) - const offset = PAGE_SIZE * page - const resp = await query(loadAuthorsByQuery, { - by: { order: queryType }, - limit: PAGE_SIZE, - offset - }) - const result = resp?.data?.load_authors_by - if ((result?.length || 0) > 0) { - addAuthors([...result]) - if (queryType === 'shouts') { - setAuthorsByShouts((prev) => [...(prev || []), ...result]) - } else if (queryType === 'followers') { - setAuthorsByFollowers((prev) => [...(prev || []), ...result]) - } - setLoading(false) - } - } - - const loadMoreAuthors = () => { - const nextPage = currentPage()[props.query] + 1 - fetchAuthors(props.query, nextPage).then(() => - setCurrentPage({ ...currentPage(), [props.query]: nextPage }) - ) - } - - createEffect( - on( - () => props.query, - (query) => { - const al = query === 'shouts' ? authorsByShouts() : authorsByFollowers() - if (al?.length === 0 && currentPage()[query] === 0) { - setCurrentPage((prev) => ({ ...prev, [query]: 0 })) - fetchAuthors(query, 0).then(() => setCurrentPage((prev) => ({ ...prev, [query]: 1 }))) - } - } - ) - ) - - const authorsList = () => (props.query === 'shouts' ? authorsByShouts() : authorsByFollowers()) - - // TODO: do it with backend - // createEffect(() => { - // if (props.searchQuery) { - // // search logic - // } - // }) - - createEffect(() => { - setAllLoaded(props.allAuthorsLength === authorsList.length) - }) - - return ( -
- - {(author) => ( -
-
- -
-
- )} -
-
-
-
- 0 && !allLoaded()}> -
-
-
-
- ) -} diff --git a/src/components/AuthorsList/index.ts b/src/components/AuthorsList/index.ts deleted file mode 100644 index 4187ebae..00000000 --- a/src/components/AuthorsList/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { AuthorsList } from './AuthorsList' diff --git a/src/components/Discours/Banner.module.scss b/src/components/Discours/Banner.module.scss index 667b4a79..ea6244b2 100644 --- a/src/components/Discours/Banner.module.scss +++ b/src/components/Discours/Banner.module.scss @@ -1,12 +1,12 @@ .discoursBanner { - background: #f8f8f8; - margin-bottom: 6.4rem; - padding: 0.8rem 0 0; - @include media-breakpoint-down(sm) { font-size: 80%; } + background: #f8f8f8; + margin-bottom: 6.4rem; + padding: 0.8rem 0 0; + h3 { font-size: 3.2rem; font-weight: 800; diff --git a/src/components/Discours/Banner.tsx b/src/components/Discours/Banner.tsx index d50d6471..d0822e73 100644 --- a/src/components/Discours/Banner.tsx +++ b/src/components/Discours/Banner.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx' +import { useLocalize } from '~/context/localize' import { useUI } from '~/context/ui' -import { useLocalize } from '../../context/localize' import { Image } from '../_shared/Image' import styles from './Banner.module.scss' @@ -14,10 +14,10 @@ export default () => {
-

{t('Discours is created with our common effort')}

+

{t('Discours exists because of our common effort')}

- {t('Support us')} - {t('Become an author')} + {t('Support us')} + {t('Become an author')} showModal('auth')}> {t('Join the community')} diff --git a/src/components/Discours/Donate.module.scss b/src/components/Discours/Donate.module.scss index 010d164d..5b8b268c 100644 --- a/src/components/Discours/Donate.module.scss +++ b/src/components/Discours/Donate.module.scss @@ -24,12 +24,12 @@ } &:focus { + box-shadow: inset 0 0 0 3px #000; + &::placeholder { opacity: 0; transition: opacity 0.2s ease; } - - box-shadow: inset 0 0 0 3px #000; } &:valid, @@ -49,18 +49,18 @@ } .donateForm .btn { + @include media-breakpoint-down(sm) { + &:last-of-type { + margin-right: 0 !important; + } + } + cursor: pointer; flex: 1; padding: 5px 10px; text-align: center; white-space: nowrap; transform: none !important; - - @include media-breakpoint-down(sm) { - &:last-of-type { - margin-right: 0 !important; - } - } } .btnGroup { @@ -82,22 +82,22 @@ } .donateButtonsContainer { + @include media-breakpoint-down(sm) { + flex-wrap: wrap; + } + align-items: center; display: flex; flex: 1; justify-content: space-between; - @include media-breakpoint-down(sm) { - flex-wrap: wrap; - } - input, label { - margin: 0 8px; - @include media-breakpoint-down(sm) { margin-bottom: 1em; } + + margin: 0 8px; } input { diff --git a/src/components/Discours/Donate.tsx b/src/components/Discours/Donate.tsx index 52b255fc..15445f41 100644 --- a/src/components/Discours/Donate.tsx +++ b/src/components/Discours/Donate.tsx @@ -1,8 +1,8 @@ import { clsx } from 'clsx' import { createSignal, onMount } from 'solid-js' +import { useLocalize } from '~/context/localize' import { useSnackbar, useUI } from '~/context/ui' -import { useLocalize } from '../../context/localize' import styles from './Donate.module.scss' diff --git a/src/components/Discours/Feedback.tsx b/src/components/Discours/Feedback.tsx index 161a8f18..145ff30a 100644 --- a/src/components/Discours/Feedback.tsx +++ b/src/components/Discours/Feedback.tsx @@ -1,5 +1,5 @@ +import { useLocalize } from '~/context/localize' import { useUI } from '~/context/ui' -import { useLocalize } from '../../context/localize' import { Button } from '../_shared/Button' export const Feedback = () => { diff --git a/src/components/Discours/Footer.module.scss b/src/components/Discours/Footer.module.scss index bad25698..ab803c63 100644 --- a/src/components/Discours/Footer.module.scss +++ b/src/components/Discours/Footer.module.scss @@ -88,16 +88,16 @@ } .socialItem { - margin-top: 1em; - text-align: center; - width: 25%; - @include media-breakpoint-up(md) { margin-top: 0; margin-left: 0.3em; text-align: right; } + margin-top: 1em; + text-align: center; + width: 25%; + a:link { border: none; padding-bottom: 0; diff --git a/src/components/Discours/Footer.tsx b/src/components/Discours/Footer.tsx index e499cea9..c22969fe 100644 --- a/src/components/Discours/Footer.tsx +++ b/src/components/Discours/Footer.tsx @@ -1,6 +1,6 @@ import { clsx } from 'clsx' import { For, createSignal, onMount } from 'solid-js' -import { useLocalize } from '../../context/localize' +import { useLocalize } from '~/context/localize' import { Icon } from '../_shared/Icon' import { Newsletter } from '../_shared/Newsletter' import styles from './Footer.module.scss' @@ -25,10 +25,10 @@ export const FooterView = () => { { header: t('About the project'), items: [ - { title: t('Discours Manifest'), slug: '/about/manifest' }, - { title: t('How it works'), slug: '/about/guide' }, - { title: t('Dogma'), slug: '/about/dogma' }, - { title: t('Principles'), slug: '/about/principles' }, + { title: t('Discours Manifest'), slug: '/manifest' }, + { title: t('How it works'), slug: '/guide' }, + { title: t('Dogma'), slug: '/dogma' }, + { title: t('Our principles'), slug: '/principles' }, { title: t('How to write an article'), slug: '/how-to-write-a-good-article' } ] }, @@ -36,10 +36,10 @@ export const FooterView = () => { header: t('Participating'), items: [ { title: t('Suggest an idea'), slug: '/connect' }, - { title: t('Become an author'), slug: '/create' }, - { title: t('Support Discours'), slug: '/about/help' }, + { title: t('Become an author'), slug: '/edit/new' }, + { title: t('Support Discours'), slug: '/support' }, { - title: t('Work with us'), + title: t('Cooperate with Discours'), slug: 'https://docs.google.com/forms/d/e/1FAIpQLSeNNvIzKlXElJtkPkYiXl-jQjlvsL9u4-kpnoRjz1O8Wo40xQ/viewform' } ] @@ -47,10 +47,10 @@ export const FooterView = () => { { header: t('Sections'), items: [ - { title: t('Authors'), slug: '/authors' }, + { title: t('Authors'), slug: '/author' }, { title: t('Communities'), slug: '/community' }, - { title: t('Partners'), slug: '/about/partners' }, - { title: t('Special projects'), slug: '/about/projects' }, + { title: t('Partners'), slug: '/partners' }, + { title: t('Special projects'), slug: '/projects' }, { title: lang() === 'ru' ? 'English' : 'Русский', slug: `?lng=${lang() === 'ru' ? 'en' : 'ru'}`, @@ -97,7 +97,7 @@ export const FooterView = () => { 'Independant magazine with an open horizontal cooperation about culture, science and society' )} . {t('Discours')} © 2015–{new Date().getFullYear()}{' '} - {t('Terms of use')} + {t('Terms of use')}

diff --git a/src/components/Discours/Hero.tsx b/src/components/Discours/Hero.tsx index 0d605058..070cda5c 100644 --- a/src/components/Discours/Hero.tsx +++ b/src/components/Discours/Hero.tsx @@ -1,5 +1,5 @@ +import { useLocalize } from '~/context/localize' import { useUI } from '~/context/ui' -import { useLocalize } from '../../context/localize' import { useSearchParams } from '@solidjs/router' import styles from './Hero.module.scss' @@ -20,7 +20,7 @@ export default () => { )} /> diff --git a/src/components/Draft/Draft.tsx b/src/components/Draft/Draft.tsx index 784ce128..1821dfaf 100644 --- a/src/components/Draft/Draft.tsx +++ b/src/components/Draft/Draft.tsx @@ -1,8 +1,8 @@ import { clsx } from 'clsx' +import { useLocalize } from '~/context/localize' import { useSnackbar, useUI } from '~/context/ui' -import { useLocalize } from '../../context/localize' -import type { Shout } from '../../graphql/schema/core.gen' +import type { Shout } from '~/graphql/schema/core.gen' import { Icon } from '../_shared/Icon' import { A } from '@solidjs/router' diff --git a/src/components/Editor/AudioUploader/AudioUploader.tsx b/src/components/Editor/AudioUploader/AudioUploader.tsx index fcd7691d..0e9c46e2 100644 --- a/src/components/Editor/AudioUploader/AudioUploader.tsx +++ b/src/components/Editor/AudioUploader/AudioUploader.tsx @@ -1,16 +1,15 @@ import { clsx } from 'clsx' import { Show } from 'solid-js' - +import { isServer } from 'solid-js/web' +import { DropArea } from '~/components/_shared/DropArea' +import { useLocalize } from '~/context/localize' +import { composeMediaItems } from '~/lib/composeMediaItems' import { MediaItem } from '~/types/mediaitem' -import { useLocalize } from '../../../context/localize' -import { composeMediaItems } from '../../../utils/composeMediaItems' import { AudioPlayer } from '../../Article/AudioPlayer' -import { DropArea } from '../../_shared/DropArea' - -// import { Buffer } from 'node:buffer' import styles from './AudioUploader.module.scss' -if (window) window.Buffer = Buffer +if (!isServer && window) window.Buffer = Buffer +// console.debug('buffer patch passed') type Props = { class?: string diff --git a/src/components/Editor/AutoSaveNotice/AutoSaveNotice.tsx b/src/components/Editor/AutoSaveNotice/AutoSaveNotice.tsx index 956d5735..0b1b9cab 100644 --- a/src/components/Editor/AutoSaveNotice/AutoSaveNotice.tsx +++ b/src/components/Editor/AutoSaveNotice/AutoSaveNotice.tsx @@ -1,8 +1,6 @@ import { clsx } from 'clsx' - -import { useLocalize } from '../../../context/localize' -import { Loading } from '../../_shared/Loading' - +import { Loading } from '~/components/_shared/Loading' +import { useLocalize } from '~/context/localize' import styles from './AutoSaveNotice.module.scss' type Props = { diff --git a/src/components/Editor/BubbleMenu/BlockquoteBubbleMenu.tsx b/src/components/Editor/BubbleMenu/BlockquoteBubbleMenu.tsx index b25809c5..4d1b4f2e 100644 --- a/src/components/Editor/BubbleMenu/BlockquoteBubbleMenu.tsx +++ b/src/components/Editor/BubbleMenu/BlockquoteBubbleMenu.tsx @@ -1,8 +1,8 @@ import type { Editor } from '@tiptap/core' -import { useLocalize } from '../../../context/localize' -import { Icon } from '../../_shared/Icon' -import { Popover } from '../../_shared/Popover' +import { Icon } from '~/components/_shared/Icon' +import { Popover } from '~/components/_shared/Popover' +import { useLocalize } from '~/context/localize' import styles from './BubbleMenu.module.scss' diff --git a/src/components/Editor/BubbleMenu/FigureBubbleMenu.tsx b/src/components/Editor/BubbleMenu/FigureBubbleMenu.tsx index 2facc7ee..27ce2f28 100644 --- a/src/components/Editor/BubbleMenu/FigureBubbleMenu.tsx +++ b/src/components/Editor/BubbleMenu/FigureBubbleMenu.tsx @@ -1,11 +1,11 @@ import type { Editor } from '@tiptap/core' +import { renderUploadedImage } from '~/components/Editor/renderUploadedImage' +import { Icon } from '~/components/_shared/Icon' +import { Popover } from '~/components/_shared/Popover' +import { useLocalize } from '~/context/localize' import { UploadedFile } from '~/types/upload' -import { useLocalize } from '../../../context/localize' -import { renderUploadedImage } from '../../../utils/renderUploadedImage' -import { Modal } from '../../Nav/Modal' -import { Icon } from '../../_shared/Icon' -import { Popover } from '../../_shared/Popover' +import { Modal } from '../../_shared/Modal' import { UploadModalContent } from '../UploadModalContent' import { useUI } from '~/context/ui' diff --git a/src/components/Editor/BubbleMenu/IncutBubbleMenu.tsx b/src/components/Editor/BubbleMenu/IncutBubbleMenu.tsx index d07ddc6f..01517729 100644 --- a/src/components/Editor/BubbleMenu/IncutBubbleMenu.tsx +++ b/src/components/Editor/BubbleMenu/IncutBubbleMenu.tsx @@ -3,8 +3,8 @@ import type { Editor } from '@tiptap/core' import { clsx } from 'clsx' import { For, Show, createSignal } from 'solid-js' -import { useLocalize } from '../../../context/localize' -import { Icon } from '../../_shared/Icon' +import { Icon } from '~/components/_shared/Icon' +import { useLocalize } from '~/context/localize' import styles from './BubbleMenu.module.scss' diff --git a/src/components/Editor/Editor.tsx b/src/components/Editor/Editor.tsx index 2321a682..7415bcf3 100644 --- a/src/components/Editor/Editor.tsx +++ b/src/components/Editor/Editor.tsx @@ -30,11 +30,11 @@ import { createTiptapEditor, useEditorHTML } from 'solid-tiptap' import uniqolor from 'uniqolor' import { Doc } from 'yjs' +import { useEditorContext } from '~/context/editor' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' import { useSnackbar } from '~/context/ui' -import { useEditorContext } from '../../context/editor' -import { useLocalize } from '../../context/localize' -import { useSession } from '../../context/session' -import { handleImageUpload } from '../../utils/handleImageUpload' +import { handleImageUpload } from '~/lib/handleImageUpload' import { BlockquoteBubbleMenu, FigureBubbleMenu, IncutBubbleMenu } from './BubbleMenu' import { EditorFloatingMenu } from './EditorFloatingMenu' diff --git a/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx b/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx index cdcc774f..5761398c 100644 --- a/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx +++ b/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx @@ -1,13 +1,13 @@ import type { Editor } from '@tiptap/core' import { Show, createEffect, createSignal } from 'solid-js' +import { renderUploadedImage } from '~/components/Editor/renderUploadedImage' +import { Icon } from '~/components/_shared/Icon' +import { useLocalize } from '~/context/localize' import { useUI } from '~/context/ui' +import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler' import { UploadedFile } from '~/types/upload' -import { useLocalize } from '../../../context/localize' -import { renderUploadedImage } from '../../../utils/renderUploadedImage' -import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler' -import { Modal } from '../../Nav/Modal' -import { Icon } from '../../_shared/Icon' +import { Modal } from '../../_shared/Modal' import { InlineForm } from '../InlineForm' import { UploadModalContent } from '../UploadModalContent' import { Menu } from './Menu' diff --git a/src/components/Editor/EditorFloatingMenu/Menu/Menu.tsx b/src/components/Editor/EditorFloatingMenu/Menu/Menu.tsx index 8a9ce1f4..4a4791c5 100644 --- a/src/components/Editor/EditorFloatingMenu/Menu/Menu.tsx +++ b/src/components/Editor/EditorFloatingMenu/Menu/Menu.tsx @@ -1,4 +1,4 @@ -import { useLocalize } from '../../../../context/localize' +import { useLocalize } from '~/context/localize' import { Icon } from '../../../_shared/Icon' import { Popover } from '../../../_shared/Popover' diff --git a/src/components/Editor/InlineForm/InlineForm.tsx b/src/components/Editor/InlineForm/InlineForm.tsx index 21707315..2eaf983f 100644 --- a/src/components/Editor/InlineForm/InlineForm.tsx +++ b/src/components/Editor/InlineForm/InlineForm.tsx @@ -1,9 +1,9 @@ import { clsx } from 'clsx' import { createSignal, onMount } from 'solid-js' -import { useLocalize } from '../../../context/localize' -import { Icon } from '../../_shared/Icon' -import { Popover } from '../../_shared/Popover' +import { Icon } from '~/components/_shared/Icon' +import { Popover } from '~/components/_shared/Popover' +import { useLocalize } from '~/context/localize' import styles from './InlineForm.module.scss' diff --git a/src/components/Editor/InsertLinkForm/InsertLinkForm.tsx b/src/components/Editor/InsertLinkForm/InsertLinkForm.tsx index de051726..96b6472c 100644 --- a/src/components/Editor/InsertLinkForm/InsertLinkForm.tsx +++ b/src/components/Editor/InsertLinkForm/InsertLinkForm.tsx @@ -1,8 +1,8 @@ import { Editor } from '@tiptap/core' import { createEditorTransaction } from 'solid-tiptap' -import { useLocalize } from '../../../context/localize' -import { validateUrl } from '../../../utils/validateUrl' +import { useLocalize } from '~/context/localize' +import { validateUrl } from '~/utils/validate' import { InlineForm } from '../InlineForm' type Props = { diff --git a/src/components/Editor/Panel/Panel.tsx b/src/components/Editor/Panel/Panel.tsx index 57ba8c57..6427487a 100644 --- a/src/components/Editor/Panel/Panel.tsx +++ b/src/components/Editor/Panel/Panel.tsx @@ -1,18 +1,16 @@ +import { A } from '@solidjs/router' import { clsx } from 'clsx' import { Show, createSignal } from 'solid-js' import { useEditorHTML } from 'solid-tiptap' import Typograf from 'typograf' - +import { Button } from '~/components/_shared/Button' +import { DarkModeToggle } from '~/components/_shared/DarkModeToggle' +import { Icon } from '~/components/_shared/Icon' +import { useEditorContext } from '~/context/editor' +import { useLocalize } from '~/context/localize' import { useUI } from '~/context/ui' -import { useEditorContext } from '../../../context/editor' -import { useLocalize } from '../../../context/localize' -import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler' -import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler' -import { Button } from '../../_shared/Button' -import { DarkModeToggle } from '../../_shared/DarkModeToggle' -import { Icon } from '../../_shared/Icon' - -import { A } from '@solidjs/router' +import { useEscKeyDownHandler } from '~/lib/useEscKeyDownHandler' +import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler' import styles from './Panel.module.scss' const typograf = new Typograf({ locale: ['ru', 'en-US'] }) @@ -164,27 +162,27 @@ export const Panel = (props: Props) => {

- {t('bold')} + {t('Bold').toLocaleLowerCase()} Ctrl B

- {t('italic')} + {t('Italic').toLocaleLowerCase()} Ctrl I

- {t('add link')} + {t('Add link').toLocaleLowerCase()} Ctrl K @@ -194,7 +192,7 @@ export const Panel = (props: Props) => {

- {t('header 1')} + {t('Header 1').toLocaleLowerCase()} Ctrl Alt @@ -202,7 +200,7 @@ export const Panel = (props: Props) => {

- {t('header 2')} + {t('Header 2').toLocaleLowerCase()} Ctrl Alt @@ -210,7 +208,7 @@ export const Panel = (props: Props) => {

- {t('header 3')} + {t('Header 3').toLocaleLowerCase()} Ctrl Alt @@ -245,14 +243,14 @@ export const Panel = (props: Props) => {

- {t('cancel')} + {t('Cancel').toLocaleLowerCase()} Ctrl Z

- {t('repeat')} + {t('Repeat').toLocaleLowerCase()} Ctrl Shift diff --git a/src/components/Editor/Prosemirror.scss b/src/components/Editor/Prosemirror.scss index a28ee8db..267ef123 100644 --- a/src/components/Editor/Prosemirror.scss +++ b/src/components/Editor/Prosemirror.scss @@ -86,25 +86,25 @@ mark.highlight { } [data-float='half-left'] { + @include media-breakpoint-up(md) { + max-width: 50%; + min-width: 30%; + } + float: left; margin: 1rem 1rem 0; clear: left; - - @include media-breakpoint-up(md) { - max-width: 50%; - min-width: 30%; - } } [data-float='half-right'] { - float: right; - margin: 1rem 0; - clear: right; - @include media-breakpoint-up(md) { max-width: 50%; min-width: 30%; } + + float: right; + margin: 1rem 0; + clear: right; } } @@ -137,23 +137,8 @@ mark.highlight { } &[data-type='punchline'] { - border: solid #000; - border-width: 2px 0; - @include font-size(3.2rem); - font-weight: 700; - line-height: 1.2; - margin: 1em 0; - padding: 2.4rem 0; - - &[data-float='left'], - &[data-float='right'] { - @include font-size(2.2rem); - - line-height: 1.4; - } - @include media-breakpoint-up(sm) { &[data-float='left'] { margin-right: 1.5em; @@ -165,18 +150,26 @@ mark.highlight { clear: right; } } + + border: solid #000; + border-width: 2px 0; + font-weight: 700; + line-height: 1.2; + margin: 1em 0; + padding: 2.4rem 0; + + &[data-float='left'], + &[data-float='right'] { + @include font-size(2.2rem); + + line-height: 1.4; + } } } .ProseMirror article[data-type='incut'] { - background: #f1f2f3; - @include font-size(1.4rem); - margin: 1em -1rem; - padding: 2em 2rem; - transition: background 0.3s ease-in-out; - @include media-breakpoint-up(sm) { margin-left: -2rem; margin-right: -2rem; @@ -193,6 +186,11 @@ mark.highlight { margin-right: -3em; } + background: #f1f2f3; + margin: 1em -1rem; + padding: 2em 2rem; + transition: background 0.3s ease-in-out; + &[data-float] img { float: none; max-width: unset; @@ -202,9 +200,6 @@ mark.highlight { &[data-float='left'], &[data-float='half-left'] { - margin-left: -1rem; - clear: left; - @include media-breakpoint-up(sm) { margin-left: -2rem; margin-right: 2rem; @@ -217,13 +212,13 @@ mark.highlight { @include media-breakpoint-up(xl) { margin-left: -12.5%; } + + margin-left: -1rem; + clear: left; } &[data-float='right'], &[data-float='half-right'] { - margin-right: -1rem; - clear: right; - @include media-breakpoint-up(sm) { margin-left: 2rem; margin-right: -2rem; @@ -236,6 +231,9 @@ mark.highlight { @include media-breakpoint-up(xl) { margin-right: -12.5%; } + + margin-right: -1rem; + clear: right; } *:last-child { diff --git a/src/components/Editor/SimplifiedEditor.tsx b/src/components/Editor/SimplifiedEditor.tsx index e0b5dd24..79db649f 100644 --- a/src/components/Editor/SimplifiedEditor.tsx +++ b/src/components/Editor/SimplifiedEditor.tsx @@ -20,13 +20,13 @@ import { useEditorIsFocused } from 'solid-tiptap' +import { useEditorContext } from '~/context/editor' +import { useLocalize } from '~/context/localize' import { UploadedFile } from '~/types/upload' -import { useEditorContext } from '../../context/editor' -import { useLocalize } from '../../context/localize' -import { Modal } from '../Nav/Modal' import { Button } from '../_shared/Button' import { Icon } from '../_shared/Icon' import { Loading } from '../_shared/Loading' +import { Modal } from '../_shared/Modal' import { Popover } from '../_shared/Popover' import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient' import { LinkBubbleMenuModule } from './LinkBubbleMenu' diff --git a/src/components/Editor/TextBubbleMenu/TextBubbleMenu.tsx b/src/components/Editor/TextBubbleMenu/TextBubbleMenu.tsx index 176fff95..633fa63e 100644 --- a/src/components/Editor/TextBubbleMenu/TextBubbleMenu.tsx +++ b/src/components/Editor/TextBubbleMenu/TextBubbleMenu.tsx @@ -4,9 +4,9 @@ import { clsx } from 'clsx' import { Match, Show, Switch, createEffect, createSignal, lazy, onCleanup, onMount } from 'solid-js' import { createEditorTransaction } from 'solid-tiptap' -import { useLocalize } from '../../../context/localize' -import { Icon } from '../../_shared/Icon' -import { Popover } from '../../_shared/Popover' +import { Icon } from '~/components/_shared/Icon' +import { Popover } from '~/components/_shared/Popover' +import { useLocalize } from '~/context/localize' import { InsertLinkForm } from '../InsertLinkForm' import styles from './TextBubbleMenu.module.scss' diff --git a/src/components/Editor/TopicSelect/TopicSelect.tsx b/src/components/Editor/TopicSelect/TopicSelect.tsx index e7ec1364..a250f705 100644 --- a/src/components/Editor/TopicSelect/TopicSelect.tsx +++ b/src/components/Editor/TopicSelect/TopicSelect.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx' import { For, Show, createSignal } from 'solid-js' +import { useLocalize } from '~/context/localize' import type { Topic } from '~/graphql/schema/core.gen' -import { useLocalize } from '../../../context/localize' import styles from './TopicSelect.module.scss' type TopicSelectProps = { diff --git a/src/components/Editor/UploadModalContent/UploadModalContent.tsx b/src/components/Editor/UploadModalContent/UploadModalContent.tsx index 432a8170..31ddc7fe 100644 --- a/src/components/Editor/UploadModalContent/UploadModalContent.tsx +++ b/src/components/Editor/UploadModalContent/UploadModalContent.tsx @@ -2,15 +2,14 @@ import { UploadFile, createDropzone, createFileUploader } from '@solid-primitive import { clsx } from 'clsx' import { Show, createSignal } from 'solid-js' +import { Button } from '~/components/_shared/Button' +import { Icon } from '~/components/_shared/Icon' +import { Loading } from '~/components/_shared/Loading' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' import { useUI } from '~/context/ui' +import { handleImageUpload } from '~/lib/handleImageUpload' import { UploadedFile } from '~/types/upload' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' -import { handleImageUpload } from '../../../utils/handleImageUpload' -import { verifyImg } from '../../../utils/verifyImg' -import { Button } from '../../_shared/Button' -import { Icon } from '../../_shared/Icon' -import { Loading } from '../../_shared/Loading' import { InlineForm } from '../InlineForm' import styles from './UploadModalContent.module.scss' @@ -19,6 +18,9 @@ type Props = { onClose: (image?: UploadedFile) => void } +const verify = (url: string) => + fetch(url, { method: 'HEAD' }).then((res) => res.headers.get('Content-Type')?.startsWith('image')) + export const UploadModalContent = (props: Props) => { const { t } = useLocalize() const { hideModal } = useUI() @@ -87,7 +89,7 @@ export const UploadModalContent = (props: Props) => { } const handleValidate = async (value: string) => { - const validationResult = await verifyImg(value) + const validationResult = await verify(value) if (!validationResult) { return t('Invalid image URL') } diff --git a/src/components/Editor/VideoUploader/VideoUploader.tsx b/src/components/Editor/VideoUploader/VideoUploader.tsx index 8244d7da..6f4c9a69 100644 --- a/src/components/Editor/VideoUploader/VideoUploader.tsx +++ b/src/components/Editor/VideoUploader/VideoUploader.tsx @@ -2,11 +2,11 @@ import { createDropzone } from '@solid-primitives/upload' import { clsx } from 'clsx' import { For, Show, createSignal } from 'solid-js' +import { VideoPlayer } from '~/components/_shared/VideoPlayer' +import { useLocalize } from '~/context/localize' import { useSnackbar } from '~/context/ui' -import { useLocalize } from '../../../context/localize' -import { composeMediaItems } from '../../../utils/composeMediaItems' -import { validateUrl } from '../../../utils/validateUrl' -import { VideoPlayer } from '../../_shared/VideoPlayer' +import { composeMediaItems } from '~/lib/composeMediaItems' +import { validateUrl } from '~/utils/validate' import { MediaItem } from '~/types/mediaitem' import styles from './VideoUploader.module.scss' diff --git a/src/utils/renderUploadedImage.ts b/src/components/Editor/renderUploadedImage.ts similarity index 100% rename from src/utils/renderUploadedImage.ts rename to src/components/Editor/renderUploadedImage.ts diff --git a/src/components/Feed/ArticleCard/ArticleCard.module.scss b/src/components/Feed/ArticleCard/ArticleCard.module.scss index aeea9014..c617942f 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.module.scss +++ b/src/components/Feed/ArticleCard/ArticleCard.module.scss @@ -1,16 +1,16 @@ .shoutCard { - display: flex; - flex-direction: column; - line-height: 1.2; - margin-bottom: 2.4rem; - position: relative; - @include media-breakpoint-up(md) { &:last-child { margin-bottom: 0; } } + display: flex; + flex-direction: column; + line-height: 1.2; + margin-bottom: 2.4rem; + position: relative; + &:hover { .shoutCardCover { img, @@ -437,18 +437,15 @@ } .shoutCardWithCover { - aspect-ratio: 16/9; - width: 100%; - @include media-breakpoint-down(xl) { aspect-ratio: auto; height: 100%; } - swiper-slide & { - aspect-ratio: 16/9; - margin-bottom: 0; + aspect-ratio: 16/9; + width: 100%; + swiper-slide & { @include media-breakpoint-down(lg) { aspect-ratio: 1/1; } @@ -464,6 +461,9 @@ padding-left: 10%; } } + + aspect-ratio: 16/9; + margin-bottom: 0; } &.shoutCardNoImage { @@ -501,15 +501,15 @@ } .shoutCardContent { + @include media-breakpoint-down(xl) { + position: relative; + } + display: flex; flex-direction: column; justify-content: end; padding: 30% 2.4rem 2.4rem; z-index: 1; - - @include media-breakpoint-down(xl) { - position: relative; - } } .shoutCardCover { @@ -656,12 +656,6 @@ } .shoutCardVertical { - aspect-ratio: auto; - height: 100%; - min-height: 250px; - margin: 0; - padding: 0; - @include media-breakpoint-up(sm) { aspect-ratio: 2/1; } @@ -674,13 +668,19 @@ aspect-ratio: 1/1.6; } + aspect-ratio: auto; + height: 100%; + min-height: 250px; + margin: 0; + padding: 0; + .shoutCardTitle, .shoutCardSubtitle { - font-size: 2.2rem !important; - @include media-breakpoint-between(lg, xl) { font-size: 1.8rem !important; } + + font-size: 2.2rem !important; } .shoutCardContent { diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index 5991932c..6f599901 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -1,19 +1,19 @@ import { A, useNavigate, useSearchParams } from '@solidjs/router' import { clsx } from 'clsx' import { Accessor, For, Show, createMemo, createSignal } from 'solid-js' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' -import type { Author, Maybe, Shout, Topic } from '../../../graphql/schema/core.gen' -import { capitalize } from '../../../utils/capitalize' -import { getDescription } from '../../../utils/meta' +import { Icon } from '~/components/_shared/Icon' +import { Image } from '~/components/_shared/Image' +import { Popover } from '~/components/_shared/Popover' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import type { Author, Maybe, Shout, Topic } from '~/graphql/schema/core.gen' +import { capitalize } from '~/utils/capitalize' +import { descFromBody } from '~/utils/meta' import { CoverImage } from '../../Article/CoverImage' import { RatingControl as ShoutRatingControl } from '../../Article/RatingControl' import { SharePopup, getShareUrl } from '../../Article/SharePopup' import { AuthorLink } from '../../Author/AuthorLink' -import stylesHeader from '../../Nav/Header/Header.module.scss' -import { Icon } from '../../_shared/Icon' -import { Image } from '../../_shared/Image' -import { Popover } from '../../_shared/Popover' +import stylesHeader from '../../HeaderNav/Header.module.scss' import { CardTopic } from '../CardTopic' import { FeedArticlePopup } from '../FeedArticlePopup' import styles from './ArticleCard.module.scss' @@ -109,7 +109,7 @@ export const ArticleCard = (props: ArticleCardProps) => { const [isActionPopupActive, setIsActionPopupActive] = createSignal(false) const [isCoverImageLoadError, setIsCoverImageLoadError] = createSignal(false) const [isCoverImageLoading, setIsCoverImageLoading] = createSignal(true) - const description = getDescription(props.article?.body) + const description = descFromBody(props.article?.body) const aspectRatio: Accessor = () => LAYOUT_ASPECT[props.article?.layout as string] const [mainTopicTitle, mainTopicSlug] = getMainTopicTitle(props.article, lang()) const { title, subtitle } = getTitleAndSubtitle(props.article) @@ -128,7 +128,7 @@ export const ArticleCard = (props: ArticleCardProps) => { const navigate = useNavigate() const scrollToComments = (event: MouseEvent & { currentTarget: HTMLAnchorElement; target: Element }) => { event.preventDefault() - navigate(`/article/${props.article.slug}`) + navigate(`/${props.article.slug}`) changeSearchParams({ scrollTo: 'comments' }) @@ -220,7 +220,7 @@ export const ArticleCard = (props: ArticleCardProps) => { [styles.shoutCardTitlesContainerFeedMode]: props.settings?.isFeedMode })} > - +

diff --git a/src/components/Feed/Beside.module.scss b/src/components/Feed/Beside.module.scss index e6ad65aa..4dd2ae73 100644 --- a/src/components/Feed/Beside.module.scss +++ b/src/components/Feed/Beside.module.scss @@ -1,12 +1,12 @@ .besideColumn { - counter-reset: item; - list-style-type: none; - padding-left: 0; - @include media-breakpoint-up(md) { margin-bottom: 0; } + counter-reset: item; + list-style-type: none; + padding-left: 0; + a { border: none; } diff --git a/src/components/Feed/Beside.tsx b/src/components/Feed/Beside.tsx index 8995eec0..772054a8 100644 --- a/src/components/Feed/Beside.tsx +++ b/src/components/Feed/Beside.tsx @@ -1,10 +1,10 @@ // TODO: additional entities list column + article -import type { Author, Shout, Topic } from '../../graphql/schema/core.gen' +import type { Author, Shout, Topic } from '~/graphql/schema/core.gen' import { clsx } from 'clsx' import { For, Show } from 'solid-js' -import { useLocalize } from '../../context/localize' +import { useLocalize } from '~/context/localize' import { AuthorBadge } from '../Author/AuthorBadge' import { TopicCard } from '../Topic/Card' import { Icon } from '../_shared/Icon' @@ -48,14 +48,14 @@ export const Beside = (props: Props) => {

{props.title}

-
+ {t('All authors')} - + {t('All topics')} diff --git a/src/components/Feed/FeedArticlePopup/FeedArticlePopup.module.scss b/src/components/Feed/FeedArticlePopup/FeedArticlePopup.module.scss index 6b2d15d6..a9144f20 100644 --- a/src/components/Feed/FeedArticlePopup/FeedArticlePopup.module.scss +++ b/src/components/Feed/FeedArticlePopup/FeedArticlePopup.module.scss @@ -1,15 +1,15 @@ .feedArticlePopup { - padding: 0 !important; - text-align: left; - overflow: hidden; - margin-top: -14px; - @include media-breakpoint-down(md) { left: auto !important; right: 0; transform: none !important; } + padding: 0 !important; + text-align: left; + overflow: hidden; + margin-top: -14px; + .actionList { & > li { margin-bottom: 0 !important; diff --git a/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx b/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx index 0943b814..1aaf587b 100644 --- a/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx +++ b/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx @@ -1,12 +1,12 @@ -import type { PopupProps } from '../../_shared/Popup' +import type { PopupProps } from '~/components/_shared/Popup' import { clsx } from 'clsx' import { Show, createSignal } from 'solid-js' -import { useLocalize } from '../../../context/localize' -import { Icon } from '../../_shared/Icon' -import { Popup } from '../../_shared/Popup' -import { SoonChip } from '../../_shared/SoonChip' +import { Icon } from '~/components/_shared/Icon' +import { Popup } from '~/components/_shared/Popup' +import { SoonChip } from '~/components/_shared/SoonChip' +import { useLocalize } from '~/context/localize' import styles from './FeedArticlePopup.module.scss' @@ -93,10 +93,10 @@ export const FeedArticlePopup = (props: Props) => { {/* class={styles.action}*/} {/* role="button"*/} {/* onClick={() => {*/} - {/* alert('Report')*/} + {/* alert('Complain')*/} {/* }}*/} {/* >*/} - {/* {t('Report')}*/} + {/* {t('Complain')}*/} {/* */} {/* */} {/**/} diff --git a/src/components/Feed/Group.scss b/src/components/Feed/Group.scss index b875ec05..3166d6a5 100644 --- a/src/components/Feed/Group.scss +++ b/src/components/Feed/Group.scss @@ -35,9 +35,6 @@ } .floor--group { - background: #e8e5f0; - padding: 4rem 0 0; - @include media-breakpoint-up(md) { padding-bottom: 3rem; } @@ -49,4 +46,7 @@ } } } + + background: #e8e5f0; + padding: 4rem 0 0; } diff --git a/src/components/Feed/Group.tsx b/src/components/Feed/Group.tsx index 8efd45b4..ceffa7f1 100644 --- a/src/components/Feed/Group.tsx +++ b/src/components/Feed/Group.tsx @@ -1,5 +1,5 @@ import type { JSX } from 'solid-js/jsx-runtime' -import type { Shout } from '../../graphql/schema/core.gen' +import type { Shout } from '~/graphql/schema/core.gen' import { For, Show } from 'solid-js' diff --git a/src/components/Feed/Placeholder/Placeholder.module.scss b/src/components/Feed/Placeholder/Placeholder.module.scss index a2b79da5..ff928c65 100644 --- a/src/components/Feed/Placeholder/Placeholder.module.scss +++ b/src/components/Feed/Placeholder/Placeholder.module.scss @@ -12,12 +12,11 @@ button, .button { + @include font-size(1.5rem); + align-items: center; border-radius: 1.2rem; display: flex; - - @include font-size(1.5rem); - gap: 0.6rem; justify-content: center; margin-top: 3rem; @@ -32,14 +31,14 @@ } .placeholder--feed-mode { - flex-direction: column; - min-height: 40rem; - text-align: center; - @include media-breakpoint-up(lg) { aspect-ratio: 1 / 0.8; } + flex-direction: column; + min-height: 40rem; + text-align: center; + .placeholderCover { flex: 1 100%; position: relative; @@ -68,8 +67,6 @@ } .placeholder--profile-mode { - min-height: 40rem; - @include media-breakpoint-down(lg) { display: block; } @@ -79,36 +76,30 @@ min-height: auto; } - .placeholderCover { - flex: 1; - padding: 1.6rem; + min-height: 40rem; + .placeholderCover { @include media-breakpoint-up(lg) { order: 2; position: static; } + flex: 1; + padding: 1.6rem; + img { + @include media-breakpoint-up(lg) { + object-position: right; + } + aspect-ratio: 16/10; min-width: 40rem; object-fit: contain; width: 100%; - - @include media-breakpoint-up(lg) { - object-position: right; - } } } .placeholderContent { - display: flex; - flex-direction: column; - justify-content: space-between; - font-size: 1.4rem; - line-height: 1.2; - min-width: 60%; - padding: 0 2rem 2rem; - @include media-breakpoint-up(md) { font-size: 1.6rem; padding: 3rem; @@ -117,6 +108,14 @@ @include media-breakpoint-up(lg) { font-size: 2rem; } + + display: flex; + flex-direction: column; + justify-content: space-between; + font-size: 1.4rem; + line-height: 1.2; + min-width: 60%; + padding: 0 2rem 2rem; } h3 { @@ -124,6 +123,12 @@ } .button { + @include media-breakpoint-up(lg) { + left: auto; + position: absolute; + width: auto; + } + background: var(--background-color-invert); bottom: 2rem; color: var(--default-color-invert); @@ -132,12 +137,6 @@ right: 2rem; width: 100%; - @include media-breakpoint-up(lg) { - left: auto; - position: absolute; - width: auto; - } - .icon { filter: invert(1); } @@ -156,13 +155,13 @@ } .placeholderContent { - padding: 1.6rem; - @include media-breakpoint-down(lg) { br { display: none; } } + + padding: 1.6rem; } .placeholder--feedMy, @@ -234,17 +233,16 @@ } .bottomLinks { - display: flex; - @include font-size(1.6rem); - gap: 4rem; - @include media-breakpoint-down(sm) { flex-direction: column; gap: 1.4rem; } + display: flex; + gap: 4rem; + a { border: none !important; padding-left: 2.6rem; diff --git a/src/components/Feed/Placeholder/Placeholder.tsx b/src/components/Feed/Placeholder/Placeholder.tsx index 6f608dd3..a1f60fd3 100644 --- a/src/components/Feed/Placeholder/Placeholder.tsx +++ b/src/components/Feed/Placeholder/Placeholder.tsx @@ -1,9 +1,9 @@ import { clsx } from 'clsx' import { For, Show, createMemo } from 'solid-js' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' -import { Icon } from '../../_shared/Icon' +import { Icon } from '~/components/_shared/Icon' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' import styles from './Placeholder.module.scss' type ProfileLink = { @@ -36,14 +36,14 @@ const data: PlaceholderData = { text: 'Placeholder feed', buttonLabelAuthor: 'Popular authors', buttonLabelFeed: 'Create own feed', - href: '/authors?by=followers' + href: '/author?by=followers' }, feedCollaborations: { image: 'placeholder-experts.webp', header: 'Find collaborators', text: 'Placeholder feedCollaborations', buttonLabel: 'Find co-authors', - href: '/authors?by=name' + href: '/author?by=name' }, feedDiscussions: { image: 'placeholder-discussions.webp', @@ -51,14 +51,14 @@ const data: PlaceholderData = { text: 'Placeholder feedDiscussions', buttonLabelAuthor: 'Current discussions', buttonLabelFeed: 'Enter', - href: '/feed?by=last_comment' + href: '/feed/hot' }, author: { image: 'placeholder-join.webp', header: 'Join our team of authors', text: 'Join our team of authors text', buttonLabel: 'Create post', - href: '/create', + href: '/edit/new', profileLinks: [ { href: '/how-to-write-a-good-article', @@ -71,14 +71,14 @@ const data: PlaceholderData = { header: 'Join discussions', text: 'Placeholder feedDiscussions', buttonLabel: 'Go to discussions', - href: '/feed?by=last_comment', + href: '/feed/hot', profileLinks: [ { - href: '/about/discussion-rules', + href: '/debate', label: 'Discussion rules' }, { - href: '/about/discussion-rules#ban', + href: '/debate#ban', label: 'Block rules' } ] diff --git a/src/components/Feed/Row1.tsx b/src/components/Feed/Row1.tsx index 41deb490..8fa819e9 100644 --- a/src/components/Feed/Row1.tsx +++ b/src/components/Feed/Row1.tsx @@ -1,4 +1,4 @@ -import type { Shout } from '../../graphql/schema/core.gen' +import type { Shout } from '~/graphql/schema/core.gen' import { Show } from 'solid-js' diff --git a/src/components/Feed/Row2.tsx b/src/components/Feed/Row2.tsx index 2c927570..4d757ef7 100644 --- a/src/components/Feed/Row2.tsx +++ b/src/components/Feed/Row2.tsx @@ -1,4 +1,4 @@ -import type { Shout } from '../../graphql/schema/core.gen' +import type { Shout } from '~/graphql/schema/core.gen' import { For, Show, createEffect, createSignal } from 'solid-js' diff --git a/src/components/Feed/Row3.tsx b/src/components/Feed/Row3.tsx index 12433206..1de498d1 100644 --- a/src/components/Feed/Row3.tsx +++ b/src/components/Feed/Row3.tsx @@ -1,5 +1,5 @@ import type { JSX } from 'solid-js/jsx-runtime' -import type { Shout } from '../../graphql/schema/core.gen' +import type { Shout } from '~/graphql/schema/core.gen' import { For, Show } from 'solid-js' diff --git a/src/components/Feed/Row5.tsx b/src/components/Feed/Row5.tsx index 34d82d60..d2db45bb 100644 --- a/src/components/Feed/Row5.tsx +++ b/src/components/Feed/Row5.tsx @@ -1,4 +1,4 @@ -import type { Shout } from '../../graphql/schema/core.gen' +import type { Shout } from '~/graphql/schema/core.gen' import { ArticleCard } from './ArticleCard' diff --git a/src/components/Feed/RowShort.tsx b/src/components/Feed/RowShort.tsx index 6188b270..1f1a9315 100644 --- a/src/components/Feed/RowShort.tsx +++ b/src/components/Feed/RowShort.tsx @@ -1,4 +1,4 @@ -import type { Shout } from '../../graphql/schema/core.gen' +import type { Shout } from '~/graphql/schema/core.gen' import { For } from 'solid-js' diff --git a/src/components/Feed/Sidebar/Sidebar.module.scss b/src/components/Feed/Sidebar/Sidebar.module.scss index 6ae8bda9..ff65e801 100644 --- a/src/components/Feed/Sidebar/Sidebar.module.scss +++ b/src/components/Feed/Sidebar/Sidebar.module.scss @@ -1,9 +1,4 @@ .sidebar { - margin-top: -0.7rem; - max-height: calc(100vh - 120px); - overflow: auto; - top: 120px; - @include media-breakpoint-up(md) { margin-top: 0; position: sticky; @@ -15,6 +10,11 @@ } } + margin-top: -0.7rem; + max-height: calc(100vh - 120px); + overflow: auto; + top: 120px; + .sidebarItemName { background: transparent; align-items: center; @@ -71,10 +71,6 @@ } .settings { - display: flex; - justify-content: space-between; - margin: 2em 0; - @include media-breakpoint-down(md) { margin: 0; position: absolute; @@ -85,6 +81,10 @@ right: 2px; } + display: flex; + justify-content: space-between; + margin: 2em 0; + a { border: none; } diff --git a/src/components/Feed/Sidebar/Sidebar.tsx b/src/components/Feed/Sidebar/Sidebar.tsx index 8cec85c2..da116213 100644 --- a/src/components/Feed/Sidebar/Sidebar.tsx +++ b/src/components/Feed/Sidebar/Sidebar.tsx @@ -1,13 +1,12 @@ +import { A, useLocation, useParams } from '@solidjs/router' import { clsx } from 'clsx' import { For, Show, createSignal } from 'solid-js' - -import { A, useMatch } from '@solidjs/router' +import { Icon } from '~/components/_shared/Icon' import { useFeed } from '~/context/feed' -import { useFollowing } from '../../../context/following' -import { useLocalize } from '../../../context/localize' -import { Author } from '../../../graphql/schema/core.gen' +import { useFollowing } from '~/context/following' +import { useLocalize } from '~/context/localize' +import { Author } from '~/graphql/schema/core.gen' import { Userpic } from '../../Author/Userpic' -import { Icon } from '../../_shared/Icon' import styles from './Sidebar.module.scss' export const Sidebar = () => { @@ -15,10 +14,8 @@ export const Sidebar = () => { const { follows } = useFollowing() const { feedByTopic, feedByAuthor, seen } = useFeed() const [isSubscriptionsVisible, setSubscriptionsVisible] = createSignal(true) - const matchFeed = useMatch(() => '/feed') - const matchFeedMy = useMatch(() => '/feed/my') - const matchFeedCollabs = useMatch(() => '/feed/collabs') - const matchFeedDiscussions = useMatch(() => '/feed/discussions') + const loc = useLocation() + const params = useParams() const checkTopicIsSeen = (topicSlug: string) => { return feedByTopic()[topicSlug]?.every((article) => Boolean(seen()[article.slug])) } @@ -32,22 +29,22 @@ export const Sidebar = () => {
  • - {t('Common feed')} + {t('All')}
  • @@ -58,9 +55,9 @@ export const Sidebar = () => {
  • @@ -71,9 +68,9 @@ export const Sidebar = () => {
  • @@ -99,7 +96,7 @@ export const Sidebar = () => { {(a: Author) => (
  • - +
    {a.name}
    @@ -127,7 +124,7 @@ export const Sidebar = () => {
    - + {t('Feed settings')} diff --git a/src/components/Nav/Header/Header.module.scss b/src/components/HeaderNav/Header.module.scss similarity index 98% rename from src/components/Nav/Header/Header.module.scss rename to src/components/HeaderNav/Header.module.scss index c1c6e9e9..a4c8b461 100644 --- a/src/components/Nav/Header/Header.module.scss +++ b/src/components/HeaderNav/Header.module.scss @@ -8,11 +8,11 @@ z-index: 10003; .wide-container { - background: var(--background-color); - @include media-breakpoint-down(lg) { padding: 0 divide($container-padding-x, 2); } + + background: var(--background-color); } } @@ -49,6 +49,10 @@ justify-content: flex-start; .fixed & { + @include media-breakpoint-down(md) { + border-bottom: 2px solid #000; + } + left: 0; position: fixed; right: 0; @@ -56,10 +60,6 @@ margin: 0; padding: 0; z-index: 9; - - @include media-breakpoint-down(md) { - border-bottom: 2px solid #000; - } } > * { @@ -104,17 +104,17 @@ } .usernav { - display: inline-flex; - font-weight: 500; - justify-content: end; - position: relative; - @include media-breakpoint-down(lg) { max-width: 100% !important; position: absolute; right: 0; } + display: inline-flex; + font-weight: 500; + justify-content: end; + position: relative; + .control { align-items: center; display: flex; @@ -124,22 +124,18 @@ .mainNavigationWrapper { @include font-size(1.7rem); - position: relative; - @include media-breakpoint-down(lg) { display: none; } + position: relative; + .fixed & { display: block; } } .mainNavigation { - font-size: 1.4rem !important; - opacity: 1; - transition: opacity 0.3s; - // margin: 0 0 0 -0.4rem !important; @include media-breakpoint-down(lg) { @@ -197,6 +193,10 @@ padding: divide($container-padding-x, 2) !important; } + font-size: 1.4rem !important; + opacity: 1; + transition: opacity 0.3s; + :global(.view-switcher) { margin: 0; overflow: hidden; @@ -295,12 +295,12 @@ } .burgerContainer { - box-sizing: content-box; - display: inline-flex; - @include media-breakpoint-up(lg) { display: none; } + + box-sizing: content-box; + display: inline-flex; } .burger { @@ -382,6 +382,10 @@ .articleHeader { @include font-size(1.4rem); + @include media-breakpoint-down(md) { + display: none; + } + left: $container-padding-x; margin: 0.2em 0; overflow: hidden; @@ -389,10 +393,6 @@ text-overflow: ellipsis; white-space: nowrap; width: 100%; - - @include media-breakpoint-down(md) { - display: none; - } } .iconHover { @@ -412,6 +412,10 @@ } .articleControls { + @include media-breakpoint-up(xl) { + right: 1.4rem; + } + display: flex; justify-content: flex-end; position: absolute; @@ -420,10 +424,6 @@ transform: translateY(-50%); width: 100%; - @include media-breakpoint-up(xl) { - right: 1.4rem; - } - .control { border: 0; cursor: pointer; @@ -454,11 +454,11 @@ } .articleControlsAuthorized { - right: 3.6rem; - @include media-breakpoint-up(xl) { right: 9rem; } + + right: 3.6rem; } .userControl { @@ -517,6 +517,16 @@ } .userControlItem { + @include media-breakpoint-up(md) { + height: 3.2rem; + margin: 0 0.7rem; + width: 3.2rem; + } + + @include media-breakpoint-down(xl) { + margin-left: 0.4rem !important; + } + align-items: center; border-radius: 100%; display: flex; @@ -528,16 +538,6 @@ transition: margin-left 0.3s; width: 2.8rem; - @include media-breakpoint-up(md) { - height: 3.2rem; - margin: 0 0.7rem; - width: 3.2rem; - } - - @include media-breakpoint-down(xl) { - margin-left: 0.4rem !important; - } - .headerScrolledTop &, .headerScrolledBottom & { transition: none; @@ -584,65 +584,29 @@ } .userControlItemSearch { - margin: 0 1rem 0 2.2rem; - @include media-breakpoint-down(xl) { order: 1; } + + margin: 0 1rem 0 2.2rem; } .userControlItemUserpic { - height: 3.2rem; - width: 3.2rem; - @include media-breakpoint-up(md) { height: 4rem; width: 4rem; } + + height: 3.2rem; + width: 3.2rem; } .userControlItemVerbose { - align-items: stretch; - display: flex; - height: 3.2rem; - margin-left: 1rem !important; - width: 3.2rem; - @include media-breakpoint-up(md) { height: 4rem; width: 4rem; } - &:first-child { - margin-left: 0 !important; - } - - &:global(.loginbtn) { - background: #e9e9ee; - - @include media-breakpoint-up(xl) { - background: none; - margin-left: 0.8rem !important; - } - - .icon { - height: 2.4rem; - width: 2.4rem; - } - - a:hover { - .icon { - display: block; - } - } - - img { - max-width: none; - height: 2.4rem; - width: 2.4rem; - } - } - @include media-breakpoint-up(xl) { margin-left: 3rem !important; margin-right: 0; @@ -666,19 +630,55 @@ } } + align-items: stretch; + display: flex; + height: 3.2rem; + margin-left: 1rem !important; + width: 3.2rem; + + &:first-child { + margin-left: 0 !important; + } + + &:global(.loginbtn) { + @include media-breakpoint-up(xl) { + background: none; + margin-left: 0.8rem !important; + } + + background: #e9e9ee; + + .icon { + height: 2.4rem; + width: 2.4rem; + } + + a:hover { + .icon { + display: block; + } + } + + img { + max-width: none; + height: 2.4rem; + width: 2.4rem; + } + } + a:link, a:visited, button { - align-items: center; - display: flex; - justify-content: center; - @include media-breakpoint-up(xl) { border-radius: 2rem; box-shadow: inset 0 0 0 2px #000; padding: 0 2rem; } + align-items: center; + display: flex; + justify-content: center; + &:hover { @include media-breakpoint-up(xl) { background-color: var(--link-hover-background); @@ -710,17 +710,21 @@ .userControlItemCreate { .icon { - height: 2.8rem; - width: 2.8rem; - @include media-breakpoint-up(md) { height: 3.2rem; width: 3.2rem; } + + height: 2.8rem; + width: 2.8rem; } } .subnavigation { + @include media-breakpoint-down(md) { + display: none; + } + background: #000; color: #fff; font-weight: 500; @@ -730,11 +734,11 @@ transform: translateY(-2px); width: 100%; - @include media-breakpoint-down(md) { - display: none; - } - ul { + @include media-breakpoint-up(xl) { + margin: 0 calc(0.5 * var(--bs-gutter-x)); + } + display: flex; flex-wrap: wrap; height: 6rem; @@ -744,21 +748,12 @@ position: relative; overflow: hidden; - @include media-breakpoint-up(xl) { - margin: 0 calc(0.5 * var(--bs-gutter-x)); - } - li { margin-right: 2.4rem; white-space: nowrap; } - .rightItem { - margin-right: 0; - position: absolute; - right: 0; - top: 0; - } + } a:link, @@ -801,13 +796,6 @@ } } -.rightItemIcon { - display: inline-block; - margin-left: 0.3em; - position: relative; - top: 0.15em; -} - .editorPopup { border: 1px solid rgb(0 0 0 / 15%) !important; border-radius: 1.6rem; diff --git a/src/components/Nav/Header/Header.tsx b/src/components/HeaderNav/Header.tsx similarity index 66% rename from src/components/Nav/Header/Header.tsx rename to src/components/HeaderNav/Header.tsx index 9c88d968..8f187fe7 100644 --- a/src/components/Nav/Header/Header.tsx +++ b/src/components/HeaderNav/Header.tsx @@ -1,32 +1,27 @@ +import { A, redirect, useSearchParams } from '@solidjs/router' import { clsx } from 'clsx' -import { For, Show, createEffect, createMemo, createSignal, onCleanup, onMount } from 'solid-js' - +import { Show, createEffect, createSignal, onCleanup, onMount } from 'solid-js' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' import { useUI } from '~/context/ui' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' -import { useTopics } from '../../../context/topics' -import type { Topic } from '../../../graphql/schema/core.gen' -import { getRandomTopicsFromArray } from '../../../utils/getRandomTopicsFromArray' -import { getDescription } from '../../../utils/meta' -import { SharePopup, getShareUrl } from '../../Article/SharePopup' -import { Icon } from '../../_shared/Icon' -import { Newsletter } from '../../_shared/Newsletter' +import { SharePopup, getShareUrl } from '../Article/SharePopup' import { AuthModal } from '../AuthModal' -import { ConfirmModal } from '../ConfirmModal' -import { HeaderAuth } from '../HeaderAuth' -import { Modal } from '../Modal' import { SearchModal } from '../SearchModal/SearchModal' -import { Snackbar } from '../Snackbar' -import { Link } from './Link' - -import { A, useLocation, useNavigate, useSearchParams } from '@solidjs/router' +import { Snackbar } from '../Snackbar/Snackbar' +import { RandomTopics } from '../TopicsNav/TopicsNav' +import { ConfirmModal } from '../_shared/ConfirmModal' +import { Icon } from '../_shared/Icon' +import { Modal } from '../_shared/Modal' +import { Newsletter } from '../_shared/Newsletter' import styles from './Header.module.scss' +import { HeaderAuth } from './HeaderAuth' +import { Link } from './HeaderLink' type Props = { title?: string slug?: string isHeaderFixed?: boolean - articleBody?: string + desc?: string cover?: string scrollToComments?: (value: boolean) => void } @@ -35,19 +30,15 @@ type HeaderSearchParams = { source?: string } -const handleSwitchLanguage = (value: string) => { - location.href = `${location.href}${location.href.includes('?') ? '&' : '?'}lng=${value}` +const handleSwitchLanguage = (event: { target: { value: string } }) => { + location.href = `${location.href}${location.href.includes('?') ? '&' : '?'}lng=${event.target.value}` } export const Header = (props: Props) => { const { t, lang } = useLocalize() const { modal } = useUI() - const navigate = useNavigate() - const [searchParams] = useSearchParams() const { requireAuthentication } = useSession() - const { sortedTopics } = useTopics() - const topics = createMemo(() => sortedTopics()) - const [randomTopics, setRandomTopics] = createSignal([]) + const [searchParams] = useSearchParams() const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false) const [getIsScrolled, setIsScrolled] = createSignal(false) const [fixed, setFixed] = createSignal(false) @@ -58,39 +49,24 @@ export const Header = (props: Props) => { const [isZineVisible, setIsZineVisible] = createSignal(false) const [isFeedVisible, setIsFeedVisible] = createSignal(false) const { session } = useSession() - const toggleFixed = () => setFixed(!fixed()) - const tag = (topic: Topic) => - /[ЁА-яё]/.test(topic.title || '') && lang() !== 'ru' ? topic.slug : topic.title - let windowScrollTop = 0 - createEffect(() => { - if (topics()?.length) { - const rt: Topic[] = getRandomTopicsFromArray(topics()) - setRandomTopics(rt) - } - }) - createEffect(() => { const mainContent = document.querySelector('.main-content') - if ((window && fixed()) || modal() !== null) { - windowScrollTop = window.scrollY - if (mainContent) { - mainContent.style.marginTop = `-${windowScrollTop}px` - } + if (fixed() || modal() !== null) { + windowScrollTop = window?.scrollY || 0 + if (mainContent) mainContent.style.marginTop = `-${windowScrollTop}px` } document.body.classList.toggle('fixed', fixed() || modal() !== null) document.body.classList.toggle(styles.fixed, fixed() && !modal()) if (!(fixed() || modal())) { - if (mainContent) { - mainContent.style.marginTop = '' - } - window.scrollTo(0, windowScrollTop) + window?.scrollTo(0, windowScrollTop) + if (mainContent) mainContent.style.marginTop = '' } }) @@ -109,27 +85,30 @@ export const Header = (props: Props) => { }) }) - const scrollToComments = (event: MouseEvent | undefined, value: boolean) => { - event?.preventDefault() + const scrollToComments = ( + event: MouseEvent & { currentTarget: HTMLDivElement; target: Element }, + value: boolean + ) => { + event.preventDefault() props.scrollToComments?.(value) } - const handleBookmarkButtonClick = (ev: MouseEvent | undefined) => { + const handleBookmarkButtonClick = (ev: { preventDefault: () => void }) => { requireAuthentication(() => { // TODO: implement bookmark clicked - ev?.preventDefault() + ev.preventDefault() }, 'bookmark') } - const handleCreateButtonClick = (ev: MouseEvent | undefined) => { + const handleCreateButtonClick = (ev?: { preventDefault: () => void }) => { requireAuthentication(() => { ev?.preventDefault() - navigate('/create') + redirect('/edit/new') }, 'create') } - const toggleSubnavigation = (isShow: boolean, signal?: (v: boolean) => void) => { + const toggleSubnavigation = (isShow: boolean, signal?: (b: boolean) => void) => { clearTimer() setIsKnowledgeBaseVisible(false) setIsTopicsVisible(false) @@ -147,22 +126,12 @@ export const Header = (props: Props) => { clearTimeout(timer) } - const hideSubnavigation = (time = 500) => { + const hideSubnavigation = (_ev?: MouseEvent, time = 500) => { timer = setTimeout(() => { toggleSubnavigation(false) }, time) } - const loc = useLocation() - const handleToggleMenuByLink = (event: MouseEvent, route: string) => { - event.preventDefault() - // console.debug('[Header] toggle menu link from', loc.pathname) - // console.debug('to', route) - if (!fixed()) return - if (loc.pathname.startsWith(route) || loc.pathname.startsWith(`/${route}`)) { - toggleFixed() - } - navigate(route) - } + return (
    { @@ -199,7 +168,7 @@ export const Header = (props: Props) => {
    @@ -211,42 +180,37 @@ export const Header = (props: Props) => {
      toggleSubnavigation(true, setIsZineVisible)} - onMouseOut={() => hideSubnavigation()} - routeName="home" + onMouseOut={hideSubnavigation} + href="/" active={isZineVisible()} - body={t('journal')} - onClick={(event) => handleToggleMenuByLink(event, '/')} + body={t('Journal')} /> toggleSubnavigation(true, setIsFeedVisible)} - onMouseOut={() => hideSubnavigation()} - routeName="feed" + onMouseOut={hideSubnavigation} + href="/feed" active={isFeedVisible()} - body={t('feed')} - onClick={(event) => handleToggleMenuByLink(event, '/feed')} + body={t('Feed')} /> toggleSubnavigation(true, setIsTopicsVisible)} - onMouseOut={() => hideSubnavigation} - routeName="topics" + onMouseOut={hideSubnavigation} + href="/topic" active={isTopicsVisible()} - body={t('topics')} - onClick={(event) => handleToggleMenuByLink(event, '/topics')} + body={t('Topics')} /> hideSubnavigation(0)} - onMouseOut={() => hideSubnavigation(0)} - routeName="authors" - body={t('authors')} - onClick={(event) => handleToggleMenuByLink(event, '/authors')} + onMouseOver={(event?: MouseEvent) => hideSubnavigation(event, 0)} + onMouseOut={(event?: MouseEvent) => hideSubnavigation(event, 0)} + href="/author" + body={t('Authors')} /> toggleSubnavigation(true, setIsKnowledgeBaseVisible)} - onMouseOut={() => hideSubnavigation()} - routeName="guide" + onMouseOut={() => hideSubnavigation} + href="/guide" body={t('Knowledge base')} active={isKnowledgeBaseVisible()} - onClick={(event) => handleToggleMenuByLink(event, '/guide')} />
    @@ -254,13 +218,13 @@ export const Header = (props: Props) => {

    {t('Participating')}

    @@ -310,7 +274,7 @@ export const Header = (props: Props) => {

    {t('Language')}

    { title={props.title || ''} imageUrl={props.cover || ''} shareUrl={getShareUrl()} - description={getDescription(props.articleBody || '')} - onVisibilityChange={(isVisible) => { - setIsSharePopupVisible(isVisible) - }} + description={props.desc || ''} + onVisibilityChange={setIsSharePopupVisible} containerCssClass={styles.control} trigger={ <> @@ -377,32 +339,32 @@ export const Header = (props: Props) => { class={clsx(styles.subnavigation, 'col')} classList={{ hidden: !isKnowledgeBaseVisible() }} onMouseOver={clearTimer} - onMouseOut={() => hideSubnavigation()} + onMouseOut={hideSubnavigation} >

@@ -411,44 +373,47 @@ export const Header = (props: Props) => { class={clsx(styles.subnavigation, 'col')} classList={{ hidden: !isZineVisible() }} onMouseOver={clearTimer} - onMouseOut={() => hideSubnavigation()} + onMouseOut={hideSubnavigation} >
@@ -457,34 +422,16 @@ export const Header = (props: Props) => { class={clsx(styles.subnavigation, 'col')} classList={{ hidden: !isTopicsVisible() }} onMouseOver={clearTimer} - onMouseOut={() => hideSubnavigation()} + onMouseOut={hideSubnavigation} > - +
diff --git a/src/components/Nav/HeaderAuth.tsx b/src/components/HeaderNav/HeaderAuth.tsx similarity index 96% rename from src/components/Nav/HeaderAuth.tsx rename to src/components/HeaderNav/HeaderAuth.tsx index a7bc5163..c61eb1ff 100644 --- a/src/components/Nav/HeaderAuth.tsx +++ b/src/components/HeaderNav/HeaderAuth.tsx @@ -1,21 +1,20 @@ +import { A, useLocation } from '@solidjs/router' import { clsx } from 'clsx' import { Show, createMemo, createSignal, onCleanup, onMount } from 'solid-js' +import { useEditorContext } from '~/context/editor' +import { useLocalize } from '~/context/localize' +import { useNotifications } from '~/context/notifications' +import { useSession } from '~/context/session' import { useUI } from '~/context/ui' import type { Author } from '~/graphql/schema/core.gen' -import { useEditorContext } from '../../context/editor' -import { useLocalize } from '../../context/localize' -import { useNotifications } from '../../context/notifications' -import { useSession } from '../../context/session' import { Userpic } from '../Author/Userpic' +import { ProfilePopup } from '../ProfileNav/ProfilePopup' import { Button } from '../_shared/Button' import { Icon } from '../_shared/Icon' import { Popover } from '../_shared/Popover' -import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient' - -import { A, useLocation } from '@solidjs/router' import { Popup } from '../_shared/Popup' -import styles from './Header/Header.module.scss' -import { ProfilePopup } from './ProfilePopup' +import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient' +import styles from './Header.module.scss' type Props = { setIsProfilePopupVisible: (value: boolean) => void @@ -111,7 +110,7 @@ export const HeaderAuth = (props: Props) => { styles.userControlItemCreate )} > - + {t('Create post')} @@ -224,7 +223,7 @@ export const HeaderAuth = (props: Props) => { styles.userControlItemCreate )} > - + {t('Create post')} diff --git a/src/components/Nav/Header/Link.tsx b/src/components/HeaderNav/HeaderLink.tsx similarity index 52% rename from src/components/Nav/Header/Link.tsx rename to src/components/HeaderNav/HeaderLink.tsx index 1dd2a12e..35374782 100644 --- a/src/components/Nav/Header/Link.tsx +++ b/src/components/HeaderNav/HeaderLink.tsx @@ -1,29 +1,27 @@ +import { A, useLocation } from '@solidjs/router' import { clsx } from 'clsx' - -import { ROUTES } from '../../../config/routes' -import { ConditionalWrapper } from '../../_shared/ConditionalWrapper' - -import { A, useMatch } from '@solidjs/router' -import { createMemo } from 'solid-js' +import { ConditionalWrapper } from '../_shared/ConditionalWrapper' import styles from './Header.module.scss' type Props = { onMouseOver: (event?: MouseEvent, time?: number) => void onMouseOut: (event?: MouseEvent, time?: number) => void - routeName?: keyof typeof ROUTES + href?: string body: string active?: boolean onClick?: (event: MouseEvent) => void } export const Link = (props: Props) => { - const matchRoute = useMatch(() => props.routeName || '') - const isSelected = createMemo(() => Boolean(matchRoute())) + const loc = useLocation() return ( -
  • +
  • {children}} + condition={props.href !== `/${loc.pathname}`} + wrapper={(children) => {children}} > { onClick={handleCreate} disabled={usersId().length === 0} > - {usersId().length > 1 ? t('Create Group') : t('Create Chat')} + {usersId().length > 1 ? t('New group') : t('Create Chat')}
  • diff --git a/src/components/Inbox/DialogAvatar.tsx b/src/components/Inbox/DialogAvatar.tsx index 3a40e751..e49ef397 100644 --- a/src/components/Inbox/DialogAvatar.tsx +++ b/src/components/Inbox/DialogAvatar.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx' import { Show, createMemo } from 'solid-js' -import { getImageUrl } from '../../utils/getImageUrl' +import { getImageUrl } from '~/lib/getThumbUrl' import './DialogCard.module.scss' import styles from './DialogAvatar.module.scss' diff --git a/src/components/Inbox/DialogCard.tsx b/src/components/Inbox/DialogCard.tsx index e63bdf8a..e1f7c3e4 100644 --- a/src/components/Inbox/DialogCard.tsx +++ b/src/components/Inbox/DialogCard.tsx @@ -1,10 +1,10 @@ -import type { ChatMember } from '../../graphql/schema/chat.gen' +import type { ChatMember } from '~/graphql/schema/chat.gen' import { clsx } from 'clsx' import { Match, Show, Switch, createMemo } from 'solid-js' -import { useLocalize } from '../../context/localize' -import { Author } from '../../graphql/schema/core.gen' +import { useLocalize } from '~/context/localize' +import { Author } from '~/graphql/schema/core.gen' import { AuthorBadge } from '../Author/AuthorBadge' import DialogAvatar from './DialogAvatar' diff --git a/src/components/Inbox/DialogHeader.tsx b/src/components/Inbox/DialogHeader.tsx index 5b2df4ca..8a92ba52 100644 --- a/src/components/Inbox/DialogHeader.tsx +++ b/src/components/Inbox/DialogHeader.tsx @@ -1,4 +1,4 @@ -import type { Chat, ChatMember } from '../../graphql/schema/chat.gen' +import type { Chat, ChatMember } from '~/graphql/schema/chat.gen' import DialogCard from './DialogCard' diff --git a/src/components/Inbox/GroupDialogAvatar.tsx b/src/components/Inbox/GroupDialogAvatar.tsx index b949fe6d..b0a366ed 100644 --- a/src/components/Inbox/GroupDialogAvatar.tsx +++ b/src/components/Inbox/GroupDialogAvatar.tsx @@ -1,4 +1,4 @@ -import type { ChatMember } from '../../graphql/schema/chat.gen' +import type { ChatMember } from '~/graphql/schema/chat.gen' import { clsx } from 'clsx' import { For } from 'solid-js' diff --git a/src/components/Inbox/InviteUser.tsx b/src/components/Inbox/InviteUser.tsx index 0caf4ce4..c06c42b7 100644 --- a/src/components/Inbox/InviteUser.tsx +++ b/src/components/Inbox/InviteUser.tsx @@ -1,4 +1,4 @@ -import type { Author } from '../../graphql/schema/core.gen' +import type { Author } from '~/graphql/schema/core.gen' import { Icon } from '../_shared/Icon' diff --git a/src/components/Inbox/Message.tsx b/src/components/Inbox/Message.tsx index 61548fc5..75b7136f 100644 --- a/src/components/Inbox/Message.tsx +++ b/src/components/Inbox/Message.tsx @@ -1,9 +1,9 @@ -import type { ChatMember, Message as MessageType } from '../../graphql/schema/chat.gen' +import type { ChatMember, Message as MessageType } from '~/graphql/schema/chat.gen' import { clsx } from 'clsx' import { Show, createSignal } from 'solid-js' -import { useLocalize } from '../../context/localize' +import { useLocalize } from '~/context/localize' import { Icon } from '../_shared/Icon' import DialogAvatar from './DialogAvatar' diff --git a/src/components/Inbox/MessageActionsPopup.tsx b/src/components/Inbox/MessageActionsPopup.tsx index 76e54f71..fed7b881 100644 --- a/src/components/Inbox/MessageActionsPopup.tsx +++ b/src/components/Inbox/MessageActionsPopup.tsx @@ -2,7 +2,7 @@ import type { PopupProps } from '../_shared/Popup' import { For, createEffect, createSignal } from 'solid-js' -import { useLocalize } from '../../context/localize' +import { useLocalize } from '~/context/localize' import { Popup } from '../_shared/Popup' export type MessageActionType = 'reply' | 'copy' | 'pin' | 'forward' | 'select' | 'delete' diff --git a/src/components/Nav/Topics/Topics.tsx b/src/components/Nav/Topics/Topics.tsx deleted file mode 100644 index 5b5ecc9b..00000000 --- a/src/components/Nav/Topics/Topics.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { clsx } from 'clsx' -import { useLocalize } from '../../../context/localize' -import { Icon } from '../../_shared/Icon' - -import { A, useMatch } from '@solidjs/router' -import styles from './Topics.module.scss' - -export const Topics = () => { - const { t } = useLocalize() - const matchExpo = useMatch(() => '/expo') - return ( - - ) -} diff --git a/src/components/Nav/Topics/index.ts b/src/components/Nav/Topics/index.ts deleted file mode 100644 index c6ce8d87..00000000 --- a/src/components/Nav/Topics/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { Topics } from './Topics' diff --git a/src/components/NotificationsPanel/EmptyMessage/EmptyMessage.tsx b/src/components/NotificationsPanel/EmptyMessage/EmptyMessage.tsx index 39e0e597..7cb3e0bc 100644 --- a/src/components/NotificationsPanel/EmptyMessage/EmptyMessage.tsx +++ b/src/components/NotificationsPanel/EmptyMessage/EmptyMessage.tsx @@ -1,6 +1,6 @@ import { clsx } from 'clsx' -import { useLocalize } from '../../../context/localize' +import { useLocalize } from '~/context/localize' import styles from './EmptyMessage.module.scss' diff --git a/src/components/NotificationsPanel/NotificationView/NotificationGroup.tsx b/src/components/NotificationsPanel/NotificationView/NotificationGroup.tsx index 9d230261..16b7c861 100644 --- a/src/components/NotificationsPanel/NotificationView/NotificationGroup.tsx +++ b/src/components/NotificationsPanel/NotificationView/NotificationGroup.tsx @@ -1,11 +1,11 @@ import { clsx } from 'clsx' import { For, Show } from 'solid-js' -import { useLocalize } from '../../../context/localize' -import { useNotifications } from '../../../context/notifications' -import { Author, NotificationGroup as Group } from '../../../graphql/schema/core.gen' -import { GroupAvatar } from '../../_shared/GroupAvatar' -import { TimeAgo } from '../../_shared/TimeAgo' +import { GroupAvatar } from '~/components/_shared/GroupAvatar' +import { TimeAgo } from '~/components/_shared/TimeAgo' +import { useLocalize } from '~/context/localize' +import { useNotifications } from '~/context/notifications' +import { Author, NotificationGroup as Group } from '~/graphql/schema/core.gen' import { A, useNavigate, useSearchParams } from '@solidjs/router' import styles from './NotificationView.module.scss' @@ -50,7 +50,7 @@ export const NotificationGroup = (props: NotificationGroupProps) => { markSeenThread(threadId) const [slug, commentId] = threadId.split('::') - navigate(`/article/${slug}`) + navigate(`/${slug}`) if (commentId) changeSearchParams({ commentId }) } @@ -77,7 +77,7 @@ export const NotificationGroup = (props: NotificationGroupProps) => { {getTitle(n.shout?.title || '')} {' '} {t('from')}{' '} - + {n.authors?.[0]?.name || ''} {' '}
    diff --git a/src/components/NotificationsPanel/NotificationsPanel.module.scss b/src/components/NotificationsPanel/NotificationsPanel.module.scss index 8044b92b..f0a31b76 100644 --- a/src/components/NotificationsPanel/NotificationsPanel.module.scss +++ b/src/components/NotificationsPanel/NotificationsPanel.module.scss @@ -16,6 +16,10 @@ $transition-duration: 200ms; width 0ms linear $transition-duration; .panel { + @include media-breakpoint-up(md) { + width: 50%; + } + position: relative; background-color: var(--background-color); width: 100%; @@ -25,10 +29,6 @@ $transition-duration: 200ms; display: flex; flex-direction: column; - @include media-breakpoint-up(md) { - width: 50%; - } - .title { @include font-size(2rem); diff --git a/src/components/NotificationsPanel/NotificationsPanel.tsx b/src/components/NotificationsPanel/NotificationsPanel.tsx index 927c74d0..fcdd1dff 100644 --- a/src/components/NotificationsPanel/NotificationsPanel.tsx +++ b/src/components/NotificationsPanel/NotificationsPanel.tsx @@ -2,11 +2,11 @@ import { clsx } from 'clsx' import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js' import { throttle } from 'throttle-debounce' -import { useLocalize } from '../../context/localize' -import { PAGE_SIZE, useNotifications } from '../../context/notifications' -import { useSession } from '../../context/session' -import { useEscKeyDownHandler } from '../../utils/useEscKeyDownHandler' -import { useOutsideClickHandler } from '../../utils/useOutsideClickHandler' +import { useLocalize } from '~/context/localize' +import { PAGE_SIZE, useNotifications } from '~/context/notifications' +import { useSession } from '~/context/session' +import { useEscKeyDownHandler } from '~/lib/useEscKeyDownHandler' +import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler' import { Button } from '../_shared/Button' import { Icon } from '../_shared/Icon' @@ -75,7 +75,7 @@ export const NotificationsPanel = (props: Props) => { const mainContent = document.querySelector('.main-content') if (props.isOpen && mainContent && window) { - windowScrollTop = window.scrollY + windowScrollTop = window?.scrollY || 0 mainContent.style.marginTop = `-${windowScrollTop}px` } @@ -83,7 +83,7 @@ export const NotificationsPanel = (props: Props) => { if (!props.isOpen && mainContent && window) { mainContent.style.marginTop = '' - window.scrollTo(0, windowScrollTop) + window?.scrollTo(0, windowScrollTop) } }) diff --git a/src/components/Nav/ProfilePopup.tsx b/src/components/ProfileNav/ProfilePopup.tsx similarity index 82% rename from src/components/Nav/ProfilePopup.tsx rename to src/components/ProfileNav/ProfilePopup.tsx index b5c06d66..9b53c58f 100644 --- a/src/components/Nav/ProfilePopup.tsx +++ b/src/components/ProfileNav/ProfilePopup.tsx @@ -1,13 +1,12 @@ +import { A } from '@solidjs/router' import { clsx } from 'clsx' import { createMemo } from 'solid-js' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' import type { Author } from '~/graphql/schema/core.gen' -import { useLocalize } from '../../context/localize' -import { useSession } from '../../context/session' import { Icon } from '../_shared/Icon' import type { PopupProps } from '../_shared/Popup' import { Popup } from '../_shared/Popup' - -import { A } from '@solidjs/router' import styles from '../_shared/Popup/Popup.module.scss' type ProfilePopupProps = Omit @@ -21,19 +20,19 @@ export const ProfilePopup = (props: ProfilePopupProps) => {
    • - + {t('Profile')}
    • - + {t('Drafts')}
    • - + {t('Subscriptions')} @@ -51,7 +50,7 @@ export const ProfilePopup = (props: ProfilePopupProps) => {
    • - + {t('Settings')} diff --git a/src/components/Nav/ProfileSettingsNavigation/ProfileSettingsNavigation.module.scss b/src/components/ProfileNav/ProfileSettingsNavigation.module.scss similarity index 100% rename from src/components/Nav/ProfileSettingsNavigation/ProfileSettingsNavigation.module.scss rename to src/components/ProfileNav/ProfileSettingsNavigation.module.scss diff --git a/src/components/Nav/ProfileSettingsNavigation/ProfileSettingsNavigation.tsx b/src/components/ProfileNav/ProfileSettingsNavigation.tsx similarity index 75% rename from src/components/Nav/ProfileSettingsNavigation/ProfileSettingsNavigation.tsx rename to src/components/ProfileNav/ProfileSettingsNavigation.tsx index e8a7232d..253b1329 100644 --- a/src/components/Nav/ProfileSettingsNavigation/ProfileSettingsNavigation.tsx +++ b/src/components/ProfileNav/ProfileSettingsNavigation.tsx @@ -1,6 +1,6 @@ import { clsx } from 'clsx' -import { useLocalize } from '../../../context/localize' +import { useLocalize } from '~/context/localize' import { useLocation } from '@solidjs/router' import styles from './ProfileSettingsNavigation.module.scss' @@ -12,11 +12,11 @@ export const ProfileSettingsNavigation = () => { <>

      {t('Settings')}

        -
      • - {t('Profile')} +
      • + {t('Profile')}
      • -
      • - {t('Subscriptions')} +
      • + {t('Subscriptions')}
      • {t('Security')} diff --git a/src/components/Nav/ProfileSettingsNavigation/index.ts b/src/components/ProfileNav/index.ts similarity index 100% rename from src/components/Nav/ProfileSettingsNavigation/index.ts rename to src/components/ProfileNav/index.ts diff --git a/src/components/ProfileSettings/index.ts b/src/components/ProfileSettings/index.ts deleted file mode 100644 index af814349..00000000 --- a/src/components/ProfileSettings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ProfileSettings } from './ProfileSettings' diff --git a/src/components/Nav/SearchModal/SearchModal.module.scss b/src/components/SearchModal/SearchModal.module.scss similarity index 99% rename from src/components/Nav/SearchModal/SearchModal.module.scss rename to src/components/SearchModal/SearchModal.module.scss index a4f06662..a35c919c 100644 --- a/src/components/Nav/SearchModal/SearchModal.module.scss +++ b/src/components/SearchModal/SearchModal.module.scss @@ -60,10 +60,9 @@ } .searchDescription { - margin-bottom: 44px; - @include font-size(1.6rem); + margin-bottom: 44px; color: rgb(255 255 255 / 64%); } diff --git a/src/components/Nav/SearchModal/SearchModal.tsx b/src/components/SearchModal/SearchModal.tsx similarity index 94% rename from src/components/Nav/SearchModal/SearchModal.tsx rename to src/components/SearchModal/SearchModal.tsx index c6416145..5545f1c2 100644 --- a/src/components/Nav/SearchModal/SearchModal.tsx +++ b/src/components/SearchModal/SearchModal.tsx @@ -1,19 +1,15 @@ -import type { Shout } from '../../../graphql/schema/core.gen' - import { For, Show, createResource, createSignal, onCleanup } from 'solid-js' import { debounce } from 'throttle-debounce' - +import { Button } from '~/components/_shared/Button' +import { Icon } from '~/components/_shared/Icon' import { useFeed } from '~/context/feed' import { useLocalize } from '~/context/localize' -import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll' -import { byScore } from '../../../utils/sortby' -import { FEED_PAGE_SIZE } from '../../Views/Feed/Feed' -import { Button } from '../../_shared/Button' -import { Icon } from '../../_shared/Icon' - -import { SearchResultItem } from './SearchResultItem' - +import type { Shout } from '~/graphql/schema/core.gen' +import { byScore } from '~/lib/sort' +import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll' +import { FEED_PAGE_SIZE } from '../Views/Feed/Feed' import styles from './SearchModal.module.scss' +import { SearchResultItem } from './SearchResultItem' // @@TODO handle empty article options after backend support (subtitle, cover, etc.) // @@TODO implement load more diff --git a/src/components/Nav/SearchModal/SearchResultItem.tsx b/src/components/SearchModal/SearchResultItem.tsx similarity index 85% rename from src/components/Nav/SearchModal/SearchResultItem.tsx rename to src/components/SearchModal/SearchResultItem.tsx index 948e37de..19ff6764 100644 --- a/src/components/Nav/SearchModal/SearchResultItem.tsx +++ b/src/components/SearchModal/SearchResultItem.tsx @@ -1,6 +1,6 @@ -import type { Shout } from '../../../graphql/schema/core.gen' +import type { Shout } from '~/graphql/schema/core.gen' -import { ArticleCard } from '../../Feed/ArticleCard' +import { ArticleCard } from '../Feed/ArticleCard' interface SearchCardProps { settings?: { diff --git a/src/components/Nav/Snackbar.module.scss b/src/components/Snackbar/Snackbar.module.scss similarity index 100% rename from src/components/Nav/Snackbar.module.scss rename to src/components/Snackbar/Snackbar.module.scss diff --git a/src/components/Nav/Snackbar.tsx b/src/components/Snackbar/Snackbar.tsx similarity index 100% rename from src/components/Nav/Snackbar.tsx rename to src/components/Snackbar/Snackbar.tsx diff --git a/src/components/Topic/Card.module.scss b/src/components/Topic/Card.module.scss index b0593cb8..8aba45fb 100644 --- a/src/components/Topic/Card.module.scss +++ b/src/components/Topic/Card.module.scss @@ -7,9 +7,6 @@ } .topic { - align-items: baseline; - margin-top: 3.2rem; - @include media-breakpoint-down(sm) { margin: 0; position: relative; @@ -31,6 +28,9 @@ } } } + + align-items: baseline; + margin-top: 3.2rem; } .topicDetails { @@ -38,6 +38,12 @@ } .topicInRow { + @include media-breakpoint-up(sm) { + .topicDescription { + margin-bottom: 0; + } + } + align-items: center; display: flex; justify-content: space-between; @@ -46,12 +52,6 @@ button { margin-top: 0; } - - @include media-breakpoint-up(sm) { - .topicDescription { - margin-bottom: 0; - } - } } .topicTitle { @@ -100,13 +100,13 @@ } .controlContainer { - margin-bottom: 1.6rem; - @include media-breakpoint-up(sm) { .topicCompact & { text-align: right; } } + + margin-bottom: 1.6rem; } .topicCompact { diff --git a/src/components/Topic/Card.tsx b/src/components/Topic/Card.tsx index a78ac6a9..6cac701c 100644 --- a/src/components/Topic/Card.tsx +++ b/src/components/Topic/Card.tsx @@ -1,11 +1,11 @@ import { clsx } from 'clsx' import { Show, createEffect, createMemo, createSignal, on } from 'solid-js' -import { useFollowing } from '../../context/following' -import { useLocalize } from '../../context/localize' -import { useSession } from '../../context/session' -import { Author, FollowingEntity, type Topic } from '../../graphql/schema/core.gen' -import { capitalize } from '../../utils/capitalize' +import { useFollowing } from '~/context/following' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import { Author, FollowingEntity, type Topic } from '~/graphql/schema/core.gen' +import { capitalize } from '~/utils/capitalize' import { CardTopic } from '../Feed/CardTopic' import { CheckButton } from '../_shared/CheckButton' import { FollowingButton } from '../_shared/FollowingButton' diff --git a/src/components/Topic/FloorHeader.scss b/src/components/Topic/FloorHeader.scss index 203e5408..36ace948 100644 --- a/src/components/Topic/FloorHeader.scss +++ b/src/components/Topic/FloorHeader.scss @@ -3,6 +3,10 @@ h3::first-letter { } .all-materials { + @include media-breakpoint-up(sm) { + text-align: right; + } + align-self: baseline; margin-bottom: 1em; white-space: nowrap; @@ -11,10 +15,6 @@ h3::first-letter { border: none; } - @include media-breakpoint-up(sm) { - text-align: right; - } - .icon { display: inline-block; height: 0.8em; diff --git a/src/components/Topic/FloorHeader.tsx b/src/components/Topic/FloorHeader.tsx index dcc201dd..6f237019 100644 --- a/src/components/Topic/FloorHeader.tsx +++ b/src/components/Topic/FloorHeader.tsx @@ -1,6 +1,6 @@ -import type { Topic } from '../../graphql/schema/core.gen' +import type { Topic } from '~/graphql/schema/core.gen' -import { useLocalize } from '../../context/localize' +import { useLocalize } from '~/context/localize' import { Icon } from '../_shared/Icon' import './FloorHeader.scss' diff --git a/src/components/Topic/Full.tsx b/src/components/Topic/Full.tsx index 679784ae..874f45d5 100644 --- a/src/components/Topic/Full.tsx +++ b/src/components/Topic/Full.tsx @@ -1,14 +1,15 @@ -import type { Author, Topic } from '../../graphql/schema/core.gen' +import type { Author, Topic } from '~/graphql/schema/core.gen' import { clsx } from 'clsx' -import { Show, createEffect, createSignal } from 'solid-js' +import { Show, createEffect, createMemo, createSignal } from 'solid-js' -import { useFollowing } from '../../context/following' -import { useLocalize } from '../../context/localize' -import { useSession } from '../../context/session' -import { FollowingEntity } from '../../graphql/schema/core.gen' +import { useFollowing } from '~/context/following' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import { FollowingEntity } from '~/graphql/schema/core.gen' import { Button } from '../_shared/Button' +import { capitalize } from '~/utils/capitalize' import { FollowingCounters } from '../_shared/FollowingCounters/FollowingCounters' import { Icon } from '../_shared/Icon' import styles from './Full.module.scss' @@ -20,11 +21,20 @@ type Props = { } export const FullTopic = (props: Props) => { - const { t } = useLocalize() + const { t, lang } = useLocalize() const { follows, changeFollowing } = useFollowing() const { requireAuthentication } = useSession() const [followed, setFollowed] = createSignal() + const title = createMemo(() => { + /* FIXME: use title translation*/ + return `#${capitalize( + lang() === 'en' + ? props.topic.slug.replace(/-/, ' ') + : props.topic.title || props.topic.slug.replace(/-/, ' '), + true + )}` + }) createEffect(() => { if (follows?.topics?.length !== 0) { const items = follows.topics || [] @@ -42,7 +52,7 @@ export const FullTopic = (props: Props) => { return (
        -

        #{props.topic?.title}

        +

        {title()}

        @@ -70,7 +80,7 @@ export const FullTopic = (props: Props) => { value={followed() ? t('Unfollow the topic') : t('Follow the topic')} class={styles.followControl} /> - + {t('Write about the topic')}
        diff --git a/src/components/Topic/TopicBadge/TopicBadge.module.scss b/src/components/Topic/TopicBadge/TopicBadge.module.scss index c75d4503..f4c205bc 100644 --- a/src/components/Topic/TopicBadge/TopicBadge.module.scss +++ b/src/components/Topic/TopicBadge/TopicBadge.module.scss @@ -139,9 +139,6 @@ .stats { @include font-size(1.5rem); - color: var(--secondary-color); - display: flex; - @include media-breakpoint-down(md) { flex-wrap: wrap; } @@ -150,6 +147,9 @@ margin-top: 0.5em; } + color: var(--secondary-color); + display: flex; + .statsItem { @include font-size(1.4rem); diff --git a/src/components/Topic/TopicBadge/TopicBadge.tsx b/src/components/Topic/TopicBadge/TopicBadge.tsx index 6ee8f572..201cb0ae 100644 --- a/src/components/Topic/TopicBadge/TopicBadge.tsx +++ b/src/components/Topic/TopicBadge/TopicBadge.tsx @@ -1,14 +1,14 @@ import { clsx } from 'clsx' import { Show, createEffect, createSignal, on } from 'solid-js' -import { mediaMatches } from '~/utils/media-query' -import { useFollowing } from '../../../context/following' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' -import { FollowingEntity, Topic } from '../../../graphql/schema/core.gen' -import { capitalize } from '../../../utils/capitalize' -import { getImageUrl } from '../../../utils/getImageUrl' -import { FollowingButton } from '../../_shared/FollowingButton' +import { FollowingButton } from '~/components/_shared/FollowingButton' +import { useFollowing } from '~/context/following' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import { FollowingEntity, Topic } from '~/graphql/schema/core.gen' +import { getImageUrl } from '~/lib/getThumbUrl' +import { mediaMatches } from '~/lib/mediaQuery' +import { capitalize } from '~/utils/capitalize' import styles from './TopicBadge.module.scss' type Props = { diff --git a/src/components/Nav/Topics/Topics.module.scss b/src/components/TopicsNav/TopicsNav.module.scss similarity index 87% rename from src/components/Nav/Topics/Topics.module.scss rename to src/components/TopicsNav/TopicsNav.module.scss index 128dc46f..2c436944 100644 --- a/src/components/Nav/Topics/Topics.module.scss +++ b/src/components/TopicsNav/TopicsNav.module.scss @@ -1,12 +1,4 @@ .Topics { - font-size: 1.6rem; - height: 6rem; - line-height: 6rem; - margin-bottom: 3rem; - overflow: hidden; - position: relative; - transform: translateY(-2px); - @include media-breakpoint-down(sm) { padding: 0 divide($container-padding-x, 2); } @@ -15,7 +7,21 @@ @include font-size(1.4rem); } + font-size: 1.6rem; + height: 6rem; + line-height: 6rem; + margin-bottom: 3rem; + overflow: hidden; + position: relative; + transform: translateY(-2px); + .list { + @include media-breakpoint-up(lg) { + flex-wrap: wrap; + overflow: hidden; + padding-right: 7em; + } + display: flex; font-weight: 500; list-style: none; @@ -23,18 +29,16 @@ overflow: auto; padding: 0; position: relative; - - @include media-breakpoint-up(lg) { - flex-wrap: wrap; - overflow: hidden; - padding-right: 7em; - } } .item { margin-right: 2.4rem; &.right { + @include media-breakpoint-up(lg) { + display: block; + } + display: none; margin-right: 0; position: absolute; @@ -42,10 +46,6 @@ top: 0; white-space: nowrap; - @include media-breakpoint-up(lg) { - display: block; - } - :global(.icon) { display: inline-block; margin-left: 0.3em; @@ -76,3 +76,17 @@ } } } + +.rightItem { + margin-right: 0; + position: absolute; + right: 0; + top: 0; +} + +.rightItemIcon { + display: inline-block; + margin-left: 0.3em; + position: relative; + top: 0.15em; +} diff --git a/src/components/TopicsNav/TopicsNav.tsx b/src/components/TopicsNav/TopicsNav.tsx new file mode 100644 index 00000000..2a4f5395 --- /dev/null +++ b/src/components/TopicsNav/TopicsNav.tsx @@ -0,0 +1,97 @@ +import { A, useMatch } from '@solidjs/router' +import { clsx } from 'clsx' +import { For, Show, createEffect, createSignal, on, onMount } from 'solid-js' +import { Icon } from '~/components/_shared/Icon' +import { useLocalize } from '~/context/localize' +import { useTopics } from '~/context/topics' +import type { Topic } from '~/graphql/schema/core.gen' +import { getRandomTopicsFromArray } from '~/lib/getRandomTopicsFromArray' +import styles from './TopicsNav.module.scss' + +export const RandomTopics = () => { + const { sortedTopics } = useTopics() + const { lang, t } = useLocalize() + const tag = (topic: Topic) => + /[ЁА-яё]/.test(topic.title || '') && lang() !== 'ru' ? topic.slug : topic.title + const [randomTopics, setRandomTopics] = createSignal([]) + createEffect( + on(sortedTopics, (ttt: Topic[]) => { + if (ttt?.length) { + setRandomTopics(getRandomTopicsFromArray(ttt)) + } + }) + ) + onMount(() => sortedTopics() && getRandomTopicsFromArray(sortedTopics())) + return ( + + ) +} + +export const TopicsNav = () => { + const { t } = useLocalize() + const matchExpo = useMatch(() => '/expo') + return ( + + ) +} diff --git a/src/components/TopicsNav/index.ts b/src/components/TopicsNav/index.ts new file mode 100644 index 00000000..37653d78 --- /dev/null +++ b/src/components/TopicsNav/index.ts @@ -0,0 +1 @@ +export { TopicsNav } from './TopicsNav' diff --git a/src/components/Views/AllAuthors/AllAuthors.module.scss b/src/components/Views/AllAuthors/AllAuthors.module.scss index 63188b2b..682f3c60 100644 --- a/src/components/Views/AllAuthors/AllAuthors.module.scss +++ b/src/components/Views/AllAuthors/AllAuthors.module.scss @@ -1,27 +1,26 @@ -.allAuthorsPage { - .group { - @include font-size(1.6rem); +.group { + @include font-size(1.6rem); - margin: 3em 0 9.6rem; - - @include media-breakpoint-down(sm) { - margin-bottom: 6.4rem; - } - - h2 { - margin-bottom: 3.2rem; - text-transform: capitalize; - - @include media-breakpoint-down(sm) { - margin-bottom: 1.6rem; - } - } - - .topic { - margin-bottom: 2.4rem; - } + @include media-breakpoint-down(sm) { + margin-bottom: 6.4rem; } + margin: 3em 0 9.6rem; + + h2 { + @include media-breakpoint-down(sm) { + margin-bottom: 1.6rem; + } + + margin-bottom: 3.2rem; + text-transform: capitalize; + } + + .topic { + margin-bottom: 2.4rem; + } +} + .container { width: auto; @@ -30,19 +29,18 @@ width: 100px !important; } } -} .loadMoreContainer { margin-top: 48px; text-align: center; .loadMoreButton { - padding: 0.6em 3em; - width: 100%; - @include media-breakpoint-up(sm) { width: auto; } + + padding: 0.6em 3em; + width: 100%; } } @@ -74,12 +72,12 @@ } .viewSwitcher { - margin-bottom: 2rem; - width: 100%; - @include media-breakpoint-down(sm) { overflow-x: auto; } + + margin-bottom: 2rem; + width: 100%; } diff --git a/src/components/Views/AllAuthors/AllAuthors.tsx b/src/components/Views/AllAuthors/AllAuthors.tsx index d064572b..d81aac9c 100644 --- a/src/components/Views/AllAuthors/AllAuthors.tsx +++ b/src/components/Views/AllAuthors/AllAuthors.tsx @@ -1,181 +1,263 @@ -import { Meta } from '@solidjs/meta' -import { clsx } from 'clsx' -import { For, Show, createMemo, createSignal, onMount } from 'solid-js' - -import { type SortFunction, useAuthors } from '../../../context/authors' -import { useLocalize } from '../../../context/localize' -import type { Author } from '../../../graphql/schema/core.gen' -import { getImageUrl } from '../../../utils/getImageUrl' -import { scrollHandler } from '../../../utils/scroll' -import { authorLetterReduce, translateAuthor } from '../../../utils/translate' -import { AuthorsList } from '../../AuthorsList' -import { Loading } from '../../_shared/Loading' -import { SearchField } from '../../_shared/SearchField' - import { useSearchParams } from '@solidjs/router' -import { byFirstChar, byStat } from '~/utils/sortby' +import { clsx } from 'clsx' +import { For, Show, createEffect, createMemo, createSignal } from 'solid-js' +import { AuthorBadge } from '~/components/Author/AuthorBadge' +import { Button } from '~/components/_shared/Button' +import { InlineLoader } from '~/components/_shared/InlineLoader' +import { Loading } from '~/components/_shared/Loading' +import { SearchField } from '~/components/_shared/SearchField' +import { useAuthors } from '~/context/authors' +import { useLocalize } from '~/context/localize' +import type { Author } from '~/graphql/schema/core.gen' +import { authorLetterReduce, translateAuthor } from '~/intl/translate' +import { dummyFilter } from '~/lib/dummyFilter' +import { byFirstChar, byStat } from '~/lib/sort' +import { scrollHandler } from '~/utils/scroll' import styles from './AllAuthors.module.scss' +import stylesAuthorList from './AuthorsList.module.scss' type Props = { authors: Author[] - topFollowedAuthors?: Author[] - topWritingAuthors?: Author[] + authorsByFollowers?: Author[] + authorsByShouts?: Author[] isLoaded: boolean } +export const AUTHORS_PER_PAGE = 20 +export const ABC = { + ru: 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ#', + en: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#' +} + export const AllAuthors = (props: Props) => { const { t, lang } = useLocalize() + const alphabet = createMemo(() => ABC[lang()] || ABC['ru']) + const [searchParams, changeSearchParams] = useSearchParams<{ by?: string }>() + const { authorsSorted, setAuthorsSort, loadAuthors } = useAuthors() + const [loading, setLoading] = createSignal(false) + const [_currentAuthors, setCurrentAuthors] = createSignal([]) + + // UPDATE Fetch authors initially and when searchParams.by changes + createEffect(() => { + fetchAuthors(searchParams.by || 'name', 0) + }) + + const authors = createMemo(() => { + let sortedAuthors = [...(props.authors || authorsSorted())] // Clone the array to avoid mutating the original + console.log('Before Sorting:', sortedAuthors.slice(0, 5)) // Log the first 5 authors for comparison + if (searchParams.by === 'name') { + sortedAuthors = sortedAuthors.sort(byFirstChar) + console.log('Sorted by Name:', sortedAuthors.slice(0, 5)) + } else if (searchParams.by === 'shouts') { + sortedAuthors = sortedAuthors.sort(byStat('shouts')) + console.log('Sorted by Shouts:', sortedAuthors.slice(0, 5)) + } else if (searchParams.by === 'followers') { + sortedAuthors = sortedAuthors.sort(byStat('followers')) + console.log('Sorted by Followers:', sortedAuthors.slice(0, 5)) + } + console.log('After Sorting:', sortedAuthors.slice(0, 5)) + return sortedAuthors + }) + + // Log authors data and searchParams for debugging + createEffect(() => { + console.log('Authors:', props.authors.slice(0, 5)) // Log the first 5 authors + console.log('Sorted Authors:', authors().slice(0, 5)) // Log the first 5 sorted authors + console.log('Search Params "by":', searchParams.by) + }) + + // filter const [searchQuery, setSearchQuery] = createSignal('') - const ALPHABET = - lang() === 'ru' ? [...'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ@'] : [...'ABCDEFGHIJKLMNOPQRSTUVWXYZ@'] - const [searchParams] = useSearchParams<{ by?: string }>() - const { authorsSorted, addAuthors, setSortBy } = useAuthors() - - onMount(() => { - addAuthors([...props.authors]) - const sortStat: string = searchParams?.by || 'name' - const sortfn = sortStat - ? (byStat(sortStat) as SortFunction) - : (byFirstChar as SortFunction) - setSortBy(sortfn) - }) - const filteredAuthors = createMemo(() => { - const query = searchQuery().toLowerCase() - return authorsSorted().filter((author: Author) => { - // Предполагаем, что у автора есть свойство name - return author?.name?.toLowerCase().includes(query) - }) - }) + const [filteredAuthors, setFilteredAuthors] = createSignal([]) + createEffect( + () => authors() && setFilteredAuthors(dummyFilter(authors(), searchQuery(), lang()) as Author[]) + ) + // store by first char const byLetterFiltered = createMemo<{ [letter: string]: Author[] }>(() => { + if (!(filteredAuthors()?.length > 0)) return {} + console.debug('[components.AllAuthors] update byLetterFiltered', filteredAuthors()?.length) return filteredAuthors().reduce( - (acc, author) => authorLetterReduce(acc, author, lang()), + (acc, author: Author) => authorLetterReduce(acc, author, lang()), {} as { [letter: string]: Author[] } ) }) const sortedKeys = createMemo(() => { - const keys = Object.keys(byLetterFiltered()) + const keys = Object.keys(byLetterFiltered() || {}) keys.sort() const fk = keys.shift() || '' fk && keys.push(fk) return keys }) - const ogImage = getImageUrl('production/image/logo_image.png') - const ogTitle = t('Authors') - const description = t('List of authors of the open editorial community') + const fetchAuthors = async (queryType: string, page: number) => { + try { + console.debug('[components.AuthorsList] fetching authors...') + setLoading(true) + setAuthorsSort?.(queryType) + const offset = AUTHORS_PER_PAGE * page + await loadAuthors({ + by: { order: queryType }, + limit: AUTHORS_PER_PAGE, + offset + }) + // UPDATE authors to currentAuthors state + setCurrentAuthors((prev) => [...prev, ...authorsSorted()]) + } catch (error) { + console.error('[components.AuthorsList] error fetching authors:', error) + } finally { + setLoading(false) + } + } - return ( -
        - - - - - - - - - - - }> -
        -
        -
        -

        {t('Authors')}

        -

        {t('Subscribe who you like to tune your personal feed')}

        - -
        -
        + const [currentPage, setCurrentPage] = createSignal<{ followers: number; shouts: number }>({ + followers: 0, + shouts: 0 + }) + const loadMoreAuthors = () => { + const by = searchParams?.by as 'followers' | 'shouts' | undefined + if (!by) return + const nextPage = currentPage()[by] + 1 + fetchAuthors(by, nextPage).then(() => setCurrentPage({ ...currentPage(), [by]: nextPage })) + } + + const TabNavigator = () => ( +
        +
        +

        {t('Authors')}

        +

        {t('Subscribe who you like to tune your personal feed')}

        + +
        +
        + ) + + const AbcNavigator = () => ( + + ) + + const AbcAuthorsList = () => ( + + {(letter) => ( +
        +

        {letter}

        +
        - - {(letter) => ( -
        -

        {letter}

        -
        -
        -
        -
        - - {(author) => ( -
        -
        - {translateAuthor(author, lang())} - - {author.stat?.shouts || 0} - -
        -
        - )} -
        +
        +
        + + {(author) => ( +
        +
        + {translateAuthor(author, lang())} + + {author.stat?.shouts || 0} +
        -
        -
        + )} +
        - )} - - - - +
        +
        +
        +
        + )} +
        + ) + + const AuthorsSortedList = () => ( +
        + + {(author) => ( +
        +
        + +
        +
        + )} +
        +
        +
        +
        + 0}> +
        +
        +
        +
        + ) + return ( + <> + }> +
        + + }> + +
        -
        + ) } diff --git a/src/components/AuthorsList/AuthorsList.module.scss b/src/components/Views/AllAuthors/AuthorsList.module.scss similarity index 100% rename from src/components/AuthorsList/AuthorsList.module.scss rename to src/components/Views/AllAuthors/AuthorsList.module.scss diff --git a/src/components/Views/AllTopics/AllTopics.module.scss b/src/components/Views/AllTopics/AllTopics.module.scss index cb4bb1d5..cda0f93b 100644 --- a/src/components/Views/AllTopics/AllTopics.module.scss +++ b/src/components/Views/AllTopics/AllTopics.module.scss @@ -1,48 +1,47 @@ -.allTopicsPage { - .group { - @include font-size(1.6rem); +.group { + @include font-size(1.6rem); - margin: 3em 0 9.6rem; - - @include media-breakpoint-down(sm) { - margin-bottom: 6.4rem; - } - - h2 { - margin-bottom: 3.2rem; - text-transform: capitalize; - - @include media-breakpoint-down(sm) { - margin-bottom: 1.6rem; - } - } - - .topic { - margin-bottom: 2.4rem; - } + @include media-breakpoint-down(sm) { + margin-bottom: 6.4rem; } - .container { - width: auto; + margin: 3em 0 9.6rem; - .search-input { - display: inline-block; - width: 100px !important; + h2 { + @include media-breakpoint-down(sm) { + margin-bottom: 1.6rem; } + + margin-bottom: 3.2rem; + text-transform: capitalize; } } +.topicTitle { + margin-bottom: 2.4rem; +} + +.container { + width: auto; + + .search-input { + display: inline-block; + width: 100px !important; + } +} + + .loadMoreContainer { margin-top: 48px; text-align: center; .loadMoreButton { - padding: 0.6em 3em; - width: 100%; - @include media-breakpoint-up(sm) { width: auto; } + + padding: 0.6em 3em; + width: 100%; } } diff --git a/src/components/Views/AllTopics/AllTopics.tsx b/src/components/Views/AllTopics/AllTopics.tsx index df91d57d..270e6f63 100644 --- a/src/components/Views/AllTopics/AllTopics.tsx +++ b/src/components/Views/AllTopics/AllTopics.tsx @@ -1,18 +1,14 @@ -import { Meta } from '@solidjs/meta' -import { useSearchParams } from '@solidjs/router' +import { A, useSearchParams } from '@solidjs/router' import { clsx } from 'clsx' import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js' - +import { Loading } from '~/components/_shared/Loading' +import { SearchField } from '~/components/_shared/SearchField' +import { useLocalize } from '~/context/localize' import { useTopics } from '~/context/topics' -import { useLocalize } from '../../../context/localize' -import type { Topic } from '../../../graphql/schema/core.gen' -import { capitalize } from '../../../utils/capitalize' -import { dummyFilter } from '../../../utils/dummyFilter' -import { getImageUrl } from '../../../utils/getImageUrl' -import { scrollHandler } from '../../../utils/scroll' +import type { Topic } from '~/graphql/schema/core.gen' +import { dummyFilter } from '~/lib/dummyFilter' +import { scrollHandler } from '~/utils/scroll' import { TopicBadge } from '../../Topic/TopicBadge' -import { Loading } from '../../_shared/Loading' -import { SearchField } from '../../_shared/SearchField' import styles from './AllTopics.module.scss' type Props = { @@ -30,9 +26,10 @@ export const AllTopics = (props: Props) => { const alphabet = createMemo(() => ABC[lang()]) const { setTopicsSort, sortedTopics } = useTopics() const topics = createMemo(() => sortedTopics() || props.topics) - const [searchParams] = useSearchParams<{ by?: string }>() + const [searchParams, changeSearchParams] = useSearchParams<{ by?: string }>() createEffect(on(() => searchParams?.by || 'shouts', setTopicsSort, { defer: true })) - onMount(() => setTopicsSort('shouts')) + onMount(() => !searchParams?.by && changeSearchParams({ by: 'shouts' })) + // sorted derivative const byLetter = createMemo<{ [letter: string]: Topic[] }>(() => { return topics().reduce( @@ -59,10 +56,6 @@ export const AllTopics = (props: Props) => { return keys }) - // limit/offset based pagination aka 'show more' logic - const [limit, setLimit] = createSignal(TOPICS_PER_PAGE) - const showMore = () => setLimit((oldLimit) => oldLimit + TOPICS_PER_PAGE) - // filter const [searchQuery, setSearchQuery] = createSignal('') const [filteredResults, setFilteredResults] = createSignal([]) @@ -79,13 +72,13 @@ export const AllTopics = (props: Props) => {
        ) - - // meta - const ogImage = getImageUrl('production/image/logo_image.png') - const ogTitle = t('Themes and plots') - const description = t( - 'Thematic table of contents of the magazine. Here you can find all the topics that the community authors wrote about' + const AllTopicAlphabeticallyHead = () => ( + + ) + const AllTopicAlphabetically = () => ( + + {(letter) => ( +
        +

        {letter}

        +
        +
        +
        + + {(topic) => ( +
        + {topic.title || topic.slug} + + {topic.stat?.shouts || 0} + +
        + )} +
        +
        +
        +
        +
        + )} +
        ) - return ( -
        - - - - - - - - - - + <> }>
        @@ -123,80 +146,23 @@ export const AllTopics = (props: Props) => { 0}> - - - - {(letter) => ( -
        -

        {letter}

        -
        -
        -
        - - {(topic) => ( - - )} - -
        -
        -
        -
        - )} -
        + +
        - - {(topic) => ( - <> - - - )} + + {(topic) => }
        - - limit() && searchParams?.by !== 'title'}> -
        - -
        -
        -
        + ) } diff --git a/src/components/Views/Author/Author.module.scss b/src/components/Views/Author/Author.module.scss index 52f09b79..7d410641 100644 --- a/src/components/Views/Author/Author.module.scss +++ b/src/components/Views/Author/Author.module.scss @@ -8,24 +8,24 @@ } .groupControls { - margin-bottom: 2rem !important; - margin-top: 0 !important; - @include media-breakpoint-up(md) { margin-bottom: 6rem !important; } + + margin-bottom: 2rem !important; + margin-top: 0 !important; } } .authorHeader { + @include media-breakpoint-up(lg) { + margin-top: -3.2rem; + } + border-bottom: 2px solid var(--default-color); margin-bottom: 2.4rem; margin-top: 1.8rem; padding-bottom: 4rem; - - @include media-breakpoint-up(lg) { - margin-top: -3.2rem; - } } .ratingContainer { @@ -44,13 +44,13 @@ } .additionalControls { - display: none; - white-space: nowrap; - @include media-breakpoint-up(md) { display: block; text-align: right; } + + display: none; + white-space: nowrap; } .loadingWrapper { diff --git a/src/components/Views/Author/Author.tsx b/src/components/Views/Author/Author.tsx index 724a1aef..6cef65a1 100644 --- a/src/components/Views/Author/Author.tsx +++ b/src/components/Views/Author/Author.tsx @@ -1,24 +1,17 @@ -import { Meta, Title } from '@solidjs/meta' -import { A, useLocation, useMatch } from '@solidjs/router' +import { A, useLocation } from '@solidjs/router' import { clsx } from 'clsx' -import { For, Match, Show, Switch, createEffect, createMemo, createSignal, on, onMount } from 'solid-js' +import { For, Match, Show, Switch, createEffect, createMemo, createSignal, on } from 'solid-js' +import { Loading } from '~/components/_shared/Loading' import { useAuthors } from '~/context/authors' +import { useFollowing } from '~/context/following' import { useGraphQL } from '~/context/graphql' -import { useUI } from '~/context/ui' -import { useFeed } from '../../../context/feed' -import { useFollowing } from '../../../context/following' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' -import loadShoutsQuery from '../../../graphql/query/core/articles-load-by' -import getAuthorFollowersQuery from '../../../graphql/query/core/author-followers' -import getAuthorFollowsQuery from '../../../graphql/query/core/author-follows' -import loadReactionsBy from '../../../graphql/query/core/reactions-load-by' -import type { Author, Reaction, Shout, Topic } from '../../../graphql/schema/core.gen' -import { getImageUrl } from '../../../utils/getImageUrl' -import { getDescription } from '../../../utils/meta' -import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll' -import { byCreated } from '../../../utils/sortby' -import { splitToPages } from '../../../utils/splitToPages' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import getAuthorFollowersQuery from '~/graphql/query/core/author-followers' +import getAuthorFollowsQuery from '~/graphql/query/core/author-follows' +import type { Author, Reaction, Shout, Topic } from '~/graphql/schema/core.gen' +import { byCreated } from '~/lib/sort' +import { paginate } from '~/utils/paginate' import stylesArticle from '../../Article/Article.module.scss' import { Comment } from '../../Article/Comment' import { AuthorCard } from '../../Author/AuthorCard' @@ -27,11 +20,11 @@ import { Placeholder } from '../../Feed/Placeholder' import { Row1 } from '../../Feed/Row1' import { Row2 } from '../../Feed/Row2' import { Row3 } from '../../Feed/Row3' -import { Loading } from '../../_shared/Loading' import styles from './Author.module.scss' -type Props = { +type AuthorViewProps = { authorSlug: string + selectedTab: string shouts?: Shout[] author?: Author } @@ -39,143 +32,100 @@ type Props = { export const PRERENDERED_ARTICLES_COUNT = 12 const LOAD_MORE_PAGE_SIZE = 9 -export const AuthorView = (props: Props) => { +export const AuthorView = (props: AuthorViewProps) => { + // contexts const { t } = useLocalize() - const { followers: myFollowers, follows: myFollows } = useFollowing() - const { session } = useSession() - const me = createMemo(() => session()?.user?.app_data?.profile as Author) - const [slug, setSlug] = createSignal(props.authorSlug) - const { sortedFeed } = useFeed() - const { modal, hideModal } = useUI() const loc = useLocation() - const matchAuthor = useMatch(() => '/author') - const matchComments = useMatch(() => '/author/:authorId/comments') - const matchAbout = useMatch(() => '/author/:authorId/about') - const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) - const [isBioExpanded, setIsBioExpanded] = createSignal(false) + const { session } = useSession() + const { query } = useGraphQL() const { loadAuthor, authorsEntities } = useAuthors() + const { followers: myFollowers, follows: myFollows } = useFollowing() + + // signals + const [isBioExpanded, setIsBioExpanded] = createSignal(false) const [author, setAuthor] = createSignal() const [followers, setFollowers] = createSignal([] as Author[]) const [following, changeFollowing] = createSignal>([] as Array) // flat AuthorFollowsResult const [showExpandBioControl, setShowExpandBioControl] = createSignal(false) - const [commented, setCommented] = createSignal() - const { query } = useGraphQL() + const [commented, setCommented] = createSignal([]) - // пагинация загрузки ленты постов - const loadMore = async () => { - saveScrollPosition() - const resp = await query(loadShoutsQuery, { - filters: { author: props.authorSlug }, - limit: LOAD_MORE_PAGE_SIZE, - offset: sortedFeed().length - }) - const hasMore = resp?.data?.load_shouts_by?.hasMore - setIsLoadMoreButtonVisible(hasMore) - restoreScrollPosition() - } + // derivatives + const me = createMemo(() => session()?.user?.app_data?.profile as Author) + const pages = createMemo(() => + paginate((props.shouts || []).slice(1), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE) + ) // 1 // проверяет не собственный ли это профиль, иначе - загружает const [isFetching, setIsFetching] = createSignal(false) createEffect( - on([() => session()?.user?.app_data?.profile, () => props.authorSlug || ''], async ([me, s]) => { - const my = s && me?.slug === s - if (my) { - console.debug('[Author] my profile precached') - if (me) { - setAuthor(me) - if (myFollowers()) setFollowers((myFollowers() || []) as Author[]) - changeFollowing([...(myFollows?.topics || []), ...(myFollows?.authors || [])]) + on( + [() => session()?.user?.app_data?.profile, () => props.authorSlug || ''], + async ([me, slug]) => { + console.debug('check if my profile') + const my = slug && me?.slug === slug + if (my) { + console.debug('[Author] my profile precached') + if (me) { + setAuthor(me) + if (myFollowers()) setFollowers((myFollowers() || []) as Author[]) + changeFollowing([...(myFollows?.topics || []), ...(myFollows?.authors || [])]) + } + } else if (slug && !isFetching()) { + setIsFetching(true) + await loadAuthor({ slug }) + setIsFetching(false) // Сброс состояния загрузки после завершения } - } else if (s && !isFetching()) { - setIsFetching(true) - setSlug(s) - await loadAuthor(s) - setIsFetching(false) // Сброс состояния загрузки после завершения - } - }) + }, + { defer: true } + ) ) - // 3 // after fetch loading following data + + // 2 // догружает подписки автора createEffect( on( - [followers, () => authorsEntities()[slug()]], - async ([current, found]) => { - if (current) return + () => authorsEntities()[props.author?.slug || props.authorSlug || ''], + async (found) => { if (!found) return setAuthor(found) - console.info(`[Author] profile for @${slug()} fetched`) - const followsResp = await query(getAuthorFollowsQuery, { slug: slug() }).toPromise() + console.info(`[Author] profile for @${found.slug} fetched`) + const followsResp = await query(getAuthorFollowsQuery, { slug: found.slug }).toPromise() const follows = followsResp?.data?.get_author_followers || {} changeFollowing([...(follows?.authors || []), ...(follows?.topics || [])]) - console.info(`[Author] follows for @${slug()} fetched`) - const followersResp = await query(getAuthorFollowersQuery, { slug: slug() }).toPromise() + console.info(`[Author] follows for @${found.slug} fetched`) + const followersResp = await query(getAuthorFollowersQuery, { slug: found.slug }).toPromise() setFollowers(followersResp?.data?.get_author_followers || []) - console.info(`[Author] followers for @${slug()} fetched`) + console.info(`[Author] followers for @${found.slug} fetched`) setIsFetching(false) }, { defer: true } ) ) - // догружает ленту и комментарии - createEffect( - on( - () => author() as Author, - async (profile: Author) => { - if (!commented() && profile) { - await loadMore() - - const resp = await query(loadReactionsBy, { - by: { comment: true, created_by: profile.id } - }).toPromise() - const ccc = resp?.data?.load_reactions_by - if (ccc) setCommented(ccc) - } - } - // { defer: true }, - ) - ) - + // event handlers let bioContainerRef: HTMLDivElement let bioWrapperRef: HTMLDivElement - const checkBioHeight = () => { - if (bioContainerRef) { - setShowExpandBioControl(bioContainerRef.offsetHeight > bioWrapperRef.offsetHeight) - } + const checkBioHeight = (bio = bioWrapperRef) => { + if (!bio) return + const showExpand = bioContainerRef.offsetHeight > bio.offsetHeight + setShowExpandBioControl(showExpand) + console.debug('[AuthorView] mounted, show expand bio container:', showExpand) } - onMount(() => { - if (!modal()) hideModal() - checkBioHeight() - }) - - const pages = createMemo(() => - splitToPages(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE) - ) - - const ogImage = createMemo(() => - author()?.pic - ? getImageUrl(author()?.pic || '', { width: 1200 }) - : getImageUrl('production/image/logo_image.png') - ) - const description = createMemo(() => getDescription(author()?.bio || '')) const handleDeleteComment = (id: number) => { setCommented((prev) => (prev || []).filter((comment) => comment.id !== id)) } + // on load + createEffect(on(() => bioContainerRef, checkBioHeight)) + createEffect( + on( + () => props.selectedTab, + (tab) => tab && console.log('[views.Author] profile tab switched') + ) + ) + return (
        - - {author()?.name} - - - - - - - - - -
        }> <> @@ -189,21 +139,21 @@ export const AuthorView = (props: Props) => {
        - +
        @@ -246,7 +196,7 @@ export const AuthorView = (props: Props) => {
        - +
        @@ -272,25 +222,25 @@ export const AuthorView = (props: Props) => {
        - +
        - 0}> - + 0 && props.shouts[0]}> + - 1}> + 1}> - - + + - - + + - 3}> + 3}> {(page) => ( <> @@ -306,14 +256,6 @@ export const AuthorView = (props: Props) => { - - -

        - -

        -
        diff --git a/src/components/Views/Create.module.scss b/src/components/Views/Create.module.scss index b28ce5d4..2ffa1157 100644 --- a/src/components/Views/Create.module.scss +++ b/src/components/Views/Create.module.scss @@ -13,16 +13,16 @@ } .saveBlock { + @include media-breakpoint-up(md) { + padding: 3.2rem 8rem; + } + background: #f1f1f1; line-height: 1.4; margin-top: 6.4rem; padding: 1.6rem 3.2rem; text-align: center; - @include media-breakpoint-up(md) { - padding: 3.2rem 8rem; - } - .button { margin: 0 divide($container-padding-x, 2); } diff --git a/src/components/Views/DraftsView/DraftsView.tsx b/src/components/Views/DraftsView/DraftsView.tsx index dc569bc2..cdaae1e1 100644 --- a/src/components/Views/DraftsView/DraftsView.tsx +++ b/src/components/Views/DraftsView/DraftsView.tsx @@ -1,46 +1,19 @@ -import { clsx } from 'clsx' -import { For, Show, createEffect, createMemo, createSignal, on } from 'solid-js' - import { useNavigate } from '@solidjs/router' -import { useGraphQL } from '~/context/graphql' -import getDraftsQuery from '~/graphql/query/core/articles-load-drafts' -import { useEditorContext } from '../../../context/editor' -import { useSession } from '../../../context/session' -import { Shout } from '../../../graphql/schema/core.gen' -import { Draft } from '../../Draft' -import { Loading } from '../../_shared/Loading' +import { clsx } from 'clsx' +import { For, Show, createMemo, createSignal } from 'solid-js' +import { Draft } from '~/components/Draft' +import { Loading } from '~/components/_shared/Loading' +import { useEditorContext } from '~/context/editor' +import { useSession } from '~/context/session' +import { Shout } from '~/graphql/schema/core.gen' import styles from './DraftsView.module.scss' -export const DraftsView = () => { +export const DraftsView = (props: { drafts: Shout[] }) => { + const [drafts, setDrafts] = createSignal(props.drafts || []) const { session } = useSession() const authorized = createMemo(() => Boolean(session()?.access_token)) const navigate = useNavigate() - const [drafts, setDrafts] = createSignal([]) - const [loading, setLoading] = createSignal(false) - const { query } = useGraphQL() - - createEffect( - on( - () => Boolean(session()?.access_token), - async (s) => { - if (s) { - setLoading(true) - const resp = await query(getDraftsQuery, {}).toPromise() - const result = resp?.data?.get_shouts_drafts - if (result) { - const { error, drafts: loadedDrafts } = result - if (error) console.warn(error) - if (loadedDrafts) setDrafts(loadedDrafts) - } - setLoading(false) - } - }, - { defer: true } - ) - ) - const { publishShoutById, deleteShout } = useEditorContext() - const handleDraftDelete = async (shout: Shout) => { const success = await deleteShout(shout.id) if (success) { @@ -55,7 +28,7 @@ export const DraftsView = () => { return (
        - }> + }>
        diff --git a/src/components/Views/EditView/EditSettingsView.tsx b/src/components/Views/EditView/EditSettingsView.tsx new file mode 100644 index 00000000..72843f2b --- /dev/null +++ b/src/components/Views/EditView/EditSettingsView.tsx @@ -0,0 +1,204 @@ +import { clsx } from 'clsx' +import deepEqual from 'fast-deep-equal' +import { Show, createEffect, createSignal, on, onCleanup, onMount } from 'solid-js' +import { createStore } from 'solid-js/store' +import { debounce } from 'throttle-debounce' +import { Icon } from '~/components/_shared/Icon' +import { InviteMembers } from '~/components/_shared/InviteMembers' +import { ShoutForm, useEditorContext } from '~/context/editor' +import { useGraphQL } from '~/context/graphql' +import { useLocalize } from '~/context/localize' +import getMyShoutQuery from '~/graphql/query/core/article-my' +import type { Shout, Topic } from '~/graphql/schema/core.gen' +import { isDesktop } from '~/lib/mediaQuery' +import { clone } from '~/utils/clone' +import { Panel } from '../../Editor' +import { AutoSaveNotice } from '../../Editor/AutoSaveNotice' +import { Modal } from '../../_shared/Modal' +import { TableOfContents } from '../../_shared/TableOfContents' +import { PublishSettings } from '../PublishSettings' +import styles from './EditView.module.scss' + +type Props = { + shout: Shout +} + +export const MAX_HEADER_LIMIT = 100 +export const EMPTY_TOPIC: Topic = { + id: -1, + slug: '' +} + +const AUTO_SAVE_DELAY = 3000 + +const handleScrollTopButtonClick = (ev: MouseEvent | TouchEvent) => { + ev.preventDefault() + window?.scrollTo({ + top: 0, + behavior: 'smooth' + }) +} + +export const EditSettingsView = (props: Props) => { + const { t } = useLocalize() + const [isScrolled, setIsScrolled] = createSignal(false) + const { query } = useGraphQL() + const { form, setForm, saveDraft, saveDraftToLocalStorage, getDraftFromLocalStorage } = useEditorContext() + const [shoutTopics, setShoutTopics] = createSignal([]) + const [draft, setDraft] = createSignal() + const [prevForm, setPrevForm] = createStore(clone(form)) + const [saving, setSaving] = createSignal(false) + + createEffect( + on( + () => props.shout, + (shout) => { + if (shout) { + console.debug(`[EditView] shout is loaded: ${shout}`) + setShoutTopics((shout.topics as Topic[]) || []) + const stored = getDraftFromLocalStorage(shout.id) + if (stored) { + console.info(`[EditView] got stored shout: ${stored}`) + setDraft(stored) + } else { + if (!shout.slug) { + console.warn(`[EditView] shout has no slug! ${shout}`) + } + const draftForm = { + slug: shout.slug || '', + shoutId: shout.id || 0, + title: shout.title || '', + lead: shout.lead || '', + description: shout.description || '', + subtitle: shout.subtitle || '', + selectedTopics: (shoutTopics() || []) as Topic[], + mainTopic: shoutTopics()[0] || '', + body: shout.body || '', + coverImageUrl: shout.cover || '', + media: shout.media || '', + layout: shout.layout + } + setForm((_) => draftForm) + console.debug('draft from props data: ', draftForm) + } + } + }, + { defer: true } + ) + ) + + createEffect( + on( + draft, + (d) => { + if (d) { + const draftForm = Object.keys(d).length !== 0 ? d : { shoutId: props.shout.id } + setForm(draftForm) + console.debug('draft from localstorage: ', draftForm) + } + }, + { defer: true } + ) + ) + + createEffect( + on( + () => props.shout?.id, + async (shoutId) => { + if (shoutId) { + const resp = await query(getMyShoutQuery, { shout_id: shoutId }) + const result = resp?.data?.get_my_shout + if (result) { + console.debug('[EditView] getMyShout result: ', result) + const { shout: loadedShout, error } = result + setDraft(loadedShout) + console.debug('[EditView] loadedShout:', loadedShout) + console.log(error) + } + } + }, + { defer: true } + ) + ) + + onMount(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 0) + } + + window.addEventListener('scroll', handleScroll, { passive: true }) + onCleanup(() => { + window.removeEventListener('scroll', handleScroll) + }) + + const handleBeforeUnload = (event: BeforeUnloadEvent) => { + if (!deepEqual(prevForm, form)) { + event.returnValue = t( + 'There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?' + ) + } + } + + window.addEventListener('beforeunload', handleBeforeUnload) + onCleanup(() => window.removeEventListener('beforeunload', handleBeforeUnload)) + }) + const [hasChanges, setHasChanges] = createSignal(false) + const autoSave = async () => { + console.log('autoSave called') + if (hasChanges()) { + console.debug('saving draft', form) + setSaving(true) + saveDraftToLocalStorage(form) + await saveDraft(form) + setPrevForm(clone(form)) + setSaving(false) + setHasChanges(false) + } + } + + const debouncedAutoSave = debounce(AUTO_SAVE_DELAY, autoSave) + + onMount(() => { + onCleanup(() => { + debouncedAutoSave.cancel() + }) + }) + + return ( + <> +
        +
        +
        + + + + +
        + + + +
        +
        +
        +
        + + + + + + + + + + ) +} + +export default EditSettingsView diff --git a/src/components/Views/EditView/EditView.module.scss b/src/components/Views/EditView/EditView.module.scss index 15e26228..30cc877d 100644 --- a/src/components/Views/EditView/EditView.module.scss +++ b/src/components/Views/EditView/EditView.module.scss @@ -51,13 +51,6 @@ @include font-size(1.4rem); appearance: textfield; - - &::-webkit-outer-spin-button, - &::-webkit-inner-spin-button { - appearance: none; - margin: 0; - } - font-weight: 600; padding: 0; margin: 14px 0 0; @@ -65,6 +58,12 @@ border: none; outline: none; + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + appearance: none; + margin: 0; + } + &::placeholder { color: var(--secondary-color); } @@ -131,6 +130,14 @@ } .scrollTopButton { + @include media-breakpoint-down(md) { + display: none; + } + + @include media-breakpoint-up(xl) { + left: 0; + } + pointer-events: none; user-select: none; cursor: pointer; @@ -147,14 +154,6 @@ pointer-events: all; } - @include media-breakpoint-down(md) { - display: none; - } - - @include media-breakpoint-up(xl) { - left: 0; - } - &:hover { .icon { opacity: 1; diff --git a/src/components/Views/EditView/EditView.tsx b/src/components/Views/EditView/EditView.tsx index 1bc25c72..dc7cd775 100644 --- a/src/components/Views/EditView/EditView.tsx +++ b/src/components/Views/EditView/EditView.tsx @@ -1,4 +1,3 @@ -import { useMatch } from '@solidjs/router' import { clsx } from 'clsx' import deepEqual from 'fast-deep-equal' import { @@ -14,34 +13,33 @@ import { } from 'solid-js' import { createStore } from 'solid-js/store' import { debounce } from 'throttle-debounce' +import { DropArea } from '~/components/_shared/DropArea' +import { Icon } from '~/components/_shared/Icon' +import { InviteMembers } from '~/components/_shared/InviteMembers' +import { Loading } from '~/components/_shared/Loading' +import { Popover } from '~/components/_shared/Popover' +import { EditorSwiper } from '~/components/_shared/SolidSwiper' +import { ShoutForm, useEditorContext } from '~/context/editor' import { useGraphQL } from '~/context/graphql' +import { useLocalize } from '~/context/localize' import getMyShoutQuery from '~/graphql/query/core/article-my' +import type { Shout, Topic } from '~/graphql/schema/core.gen' +import { slugify } from '~/intl/translit' +import { getImageUrl } from '~/lib/getThumbUrl' +import { isDesktop } from '~/lib/mediaQuery' import { LayoutType } from '~/types/common' import { MediaItem } from '~/types/mediaitem' -import { ShoutForm, useEditorContext } from '../../../context/editor' -import { useLocalize } from '../../../context/localize' -import type { Shout, Topic } from '../../../graphql/schema/core.gen' -import { clone } from '../../../utils/clone' -import { getImageUrl } from '../../../utils/getImageUrl' -import { isDesktop } from '../../../utils/media-query' -import { slugify } from '../../../utils/slugify' +import { clone } from '~/utils/clone' import { Editor, Panel } from '../../Editor' import { AudioUploader } from '../../Editor/AudioUploader' import { AutoSaveNotice } from '../../Editor/AutoSaveNotice' import { VideoUploader } from '../../Editor/VideoUploader' -import { Modal } from '../../Nav/Modal' -import { TableOfContents } from '../../TableOfContents' -import { DropArea } from '../../_shared/DropArea' -import { Icon } from '../../_shared/Icon' -import { InviteMembers } from '../../_shared/InviteMembers' -import { Loading } from '../../_shared/Loading' -import { Popover } from '../../_shared/Popover' -import { EditorSwiper } from '../../_shared/SolidSwiper' -import { PublishSettings } from '../PublishSettings' +import { Modal } from '../../_shared/Modal' +import { TableOfContents } from '../../_shared/TableOfContents' import styles from './EditView.module.scss' const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) -const GrowingTextarea = lazy(() => import('../../_shared/GrowingTextarea/GrowingTextarea')) +const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea')) type Props = { shout: Shout @@ -57,7 +55,7 @@ const AUTO_SAVE_DELAY = 3000 const handleScrollTopButtonClick = (ev: MouseEvent | TouchEvent) => { ev.preventDefault() - window.scrollTo({ + window?.scrollTo({ top: 0, behavior: 'smooth' }) @@ -272,8 +270,6 @@ export const EditView = (props: Props) => { setIsLeadVisible(true) } - const matchEdit = useMatch(() => 'edit') - const matchEditSettings = useMatch(() => 'edit/:shoutId/settings') return ( <>
        @@ -299,7 +295,7 @@ export const EditView = (props: Props) => {
        - +
        @@ -455,7 +451,7 @@ export const EditView = (props: Props) => {
        - }> + }> {
        - - - diff --git a/src/components/Views/Expo/Expo.tsx b/src/components/Views/Expo/Expo.tsx index ff6281ce..4ccf75a8 100644 --- a/src/components/Views/Expo/Expo.tsx +++ b/src/components/Views/Expo/Expo.tsx @@ -1,26 +1,27 @@ -import { clsx } from 'clsx' -import { For, Show, createEffect, createSignal, on, onCleanup, onMount } from 'solid-js' - import { A } from '@solidjs/router' +import { clsx } from 'clsx' +import { For, Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js' +import { ConditionalWrapper } from '~/components/_shared/ConditionalWrapper' +import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper' +import { Loading } from '~/components/_shared/Loading' +import { ArticleCardSwiper } from '~/components/_shared/SolidSwiper/ArticleCardSwiper' +import { useFeed } from '~/context/feed' import { useGraphQL } from '~/context/graphql' -import getShoutsQuery from '~/graphql/query/core/articles-load-by' +import { useLocalize } from '~/context/localize' +import { loadShouts } from '~/graphql/api/public' import getRandomTopShoutsQuery from '~/graphql/query/core/articles-load-random-top' -import { useLocalize } from '../../../context/localize' -import { LoadShoutsFilters, LoadShoutsOptions, Shout } from '../../../graphql/schema/core.gen' -import { getUnixtime } from '../../../utils/getServerDate' -import { restoreScrollPosition, saveScrollPosition } from '../../../utils/scroll' +import { LoadShoutsFilters, LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen' +import { SHOUTS_PER_PAGE } from '~/routes/(main)' +import { LayoutType } from '~/types/common' +import { getUnixtime } from '~/utils/date' import { ArticleCard } from '../../Feed/ArticleCard' -import { Button } from '../../_shared/Button' -import { ConditionalWrapper } from '../../_shared/ConditionalWrapper' -import { Loading } from '../../_shared/Loading' -import { ArticleCardSwiper } from '../../_shared/SolidSwiper/ArticleCardSwiper' import styles from './Expo.module.scss' -export type LayoutType = 'music' | 'literature' | 'video' | 'article' | 'image' - type Props = { shouts: Shout[] - layout: LayoutType + topMonthShouts?: Shout[] + topRatedShouts?: Shout[] + layout?: LayoutType } export const PRERENDERED_ARTICLES_COUNT = 36 @@ -29,52 +30,28 @@ const LOAD_MORE_PAGE_SIZE = 12 export const Expo = (props: Props) => { const { t } = useLocalize() const { query } = useGraphQL() - const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [favoriteTopArticles, setFavoriteTopArticles] = createSignal([]) const [reactedTopMonthArticles, setReactedTopMonthArticles] = createSignal([]) - const [articlesEndPage, setArticlesEndPage] = createSignal(PRERENDERED_ARTICLES_COUNT) const [expoShouts, setExpoShouts] = createSignal([]) - const getLoadShoutsFilters = (additionalFilters: LoadShoutsFilters = {}): LoadShoutsFilters => { - const filters = { ...additionalFilters } + const { feedByLayout, expoFeed, setExpoFeed } = useFeed() + const layouts = createMemo(() => + props.layout ? [props.layout] : ['audio', 'video', 'image', 'literature'] + ) - if (!filters.layouts) filters.layouts = [] - if (props.layout) { - filters.layouts.push(props.layout) - } else { - filters.layouts.push('audio', 'video', 'image', 'literature') - } - - return filters - } - - const loadMore = async (count: number) => { - const options: LoadShoutsOptions = { - filters: getLoadShoutsFilters(), - limit: count, - offset: expoShouts().length - } - - options.filters = props.layout - ? { layouts: [props.layout] } - : { layouts: ['audio', 'video', 'image', 'literature'] } - - const resp = await query(getShoutsQuery, options).toPromise() - const result = resp?.data?.load_shouts || [] - const hasMore = result.length !== options.limit + 1 && result.length !== 0 - setIsLoadMoreButtonVisible(hasMore) - - setExpoShouts((prev) => [...prev, ...result]) - } - - const loadMoreWithoutScrolling = async (count: number) => { - saveScrollPosition() - await loadMore(count) - restoreScrollPosition() + const loadMoreFiltered = async () => { + const limit = SHOUTS_PER_PAGE + const offset = (props.layout ? feedByLayout()[props.layout] : expoFeed())?.length + const filters: LoadShoutsFilters = { layouts: layouts(), featured: true } + const options: LoadShoutsOptions = { filters, limit, offset } + const shoutsFetcher = loadShouts(options) + const result = await shoutsFetcher() + result && setExpoFeed(result) + return result as LoadMoreItems } const loadRandomTopArticles = async () => { const options: LoadShoutsOptions = { - filters: { ...getLoadShoutsFilters(), featured: true }, + filters: { layouts: layouts(), featured: true }, limit: 10, random_limit: 100 } @@ -85,19 +62,16 @@ export const Expo = (props: Props) => { const loadRandomTopMonthArticles = async () => { const now = new Date() const after = getUnixtime(new Date(now.setMonth(now.getMonth() - 1))) - const options: LoadShoutsOptions = { - filters: { ...getLoadShoutsFilters({ after }), reacted: true }, + filters: { layouts: layouts(), after, reacted: true }, limit: 10, random_limit: 10 } - const resp = await query(getRandomTopShoutsQuery, { options }).toPromise() setReactedTopMonthArticles(resp?.data?.load_shouts_random_top || []) } onMount(() => { - loadMore(PRERENDERED_ARTICLES_COUNT + LOAD_MORE_PAGE_SIZE) loadRandomTopArticles() loadRandomTopMonthArticles() }) @@ -107,11 +81,8 @@ export const Expo = (props: Props) => { () => props.layout, () => { setExpoShouts([]) - setIsLoadMoreButtonVisible(false) setFavoriteTopArticles([]) setReactedTopMonthArticles([]) - setArticlesEndPage(PRERENDERED_ARTICLES_COUNT) - loadMore(PRERENDERED_ARTICLES_COUNT + LOAD_MORE_PAGE_SIZE) loadRandomTopArticles() loadRandomTopMonthArticles() } @@ -121,108 +92,106 @@ export const Expo = (props: Props) => { onCleanup(() => { setExpoShouts([]) }) + const ExpoTabs = () => ( +
        + +
        + ) + const ExpoGrid = () => ( +
        +
        + + {(shout) => ( +
        + +
        + )} +
        + 0} keyed={true}> + + + + {(shout) => ( +
        + +
        + )} +
        + 0} keyed={true}> + + + + {(shout) => ( +
        + +
        + )} +
        +
        +
        + ) - const handleLoadMoreClick = () => { - loadMoreWithoutScrolling(LOAD_MORE_PAGE_SIZE) - setArticlesEndPage((prev) => prev + LOAD_MORE_PAGE_SIZE) - } - console.log(props.layout) return (
        -
        - -
        + 0} fallback={}> -
        -
        - - {(shout) => ( -
        - -
        - )} -
        - 0} keyed={true}> - - - - {(shout) => ( -
        - -
        - )} -
        - 0} keyed={true}> - - - - {(shout) => ( -
        - -
        - )} -
        -
        - -
        -
        -
        -
        + + +
        ) diff --git a/src/components/Views/Feed/Feed.module.scss b/src/components/Views/Feed/Feed.module.scss index 1381b468..d887266a 100644 --- a/src/components/Views/Feed/Feed.module.scss +++ b/src/components/Views/Feed/Feed.module.scss @@ -10,11 +10,11 @@ font-weight: 500; h4 { - margin-bottom: 0.8em; - @include media-breakpoint-down(md) { display: none; } + + margin-bottom: 0.8em; } ul, @@ -192,17 +192,17 @@ } .filtersContainer { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 4rem; - @include media-breakpoint-down(sm) { margin: 1rem 0 0; flex-direction: column; gap: 1rem; } + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 4rem; + .feedFilter { margin-top: 0; margin-bottom: 0; diff --git a/src/components/Views/Feed/Feed.tsx b/src/components/Views/Feed/Feed.tsx index 658796b8..c49e6d41 100644 --- a/src/components/Views/Feed/Feed.tsx +++ b/src/components/Views/Feed/Feed.tsx @@ -1,20 +1,23 @@ +import { A, createAsync, useLocation, useNavigate, useSearchParams } from '@solidjs/router' import { clsx } from 'clsx' -import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js' - -import { Meta } from '@solidjs/meta' -import { A, useLocation, useSearchParams } from '@solidjs/router' +import { For, Show, createEffect, createMemo, createSignal, on } from 'solid-js' +import { DropDown } from '~/components/_shared/DropDown' +import { Option } from '~/components/_shared/DropDown/DropDown' +import { Icon } from '~/components/_shared/Icon' +import { InviteMembers } from '~/components/_shared/InviteMembers' +import { Loading } from '~/components/_shared/Loading' +import { ShareModal } from '~/components/_shared/ShareModal' +import { useAuthors } from '~/context/authors' import { useGraphQL } from '~/context/graphql' +import { useLocalize } from '~/context/localize' +import { useReactions } from '~/context/reactions' +import { useSession } from '~/context/session' +import { useTopics } from '~/context/topics' import { useUI } from '~/context/ui' -import getUnratedShoutsQuery from '~/graphql/query/core/articles-load-unrated' -import { useAuthors } from '../../../context/authors' -import { useFeed } from '../../../context/feed' -import { useLocalize } from '../../../context/localize' -import { useReactions } from '../../../context/reactions' -import { useSession } from '../../../context/session' -import { useTopics } from '../../../context/topics' -import type { Author, LoadShoutsOptions, Reaction, Shout } from '../../../graphql/schema/core.gen' -import { getImageUrl } from '../../../utils/getImageUrl' -import { byCreated } from '../../../utils/sortby' +import { loadUnratedShouts } from '~/graphql/api/private' +import type { Author, Reaction, Shout } from '~/graphql/schema/core.gen' +import { byCreated } from '~/lib/sort' +import { FeedSearchParams } from '~/routes/feed/[...order]' import { CommentDate } from '../../Article/CommentDate' import { getShareUrl } from '../../Article/SharePopup' import { AuthorBadge } from '../../Author/AuthorBadge' @@ -24,218 +27,85 @@ import stylesBeside from '../../Feed/Beside.module.scss' import stylesTopic from '../../Feed/CardTopic.module.scss' import { Placeholder } from '../../Feed/Placeholder' import { Sidebar } from '../../Feed/Sidebar' -import { Modal } from '../../Nav/Modal' -import { DropDown } from '../../_shared/DropDown' -import { Icon } from '../../_shared/Icon' -import { InviteMembers } from '../../_shared/InviteMembers' -import { Loading } from '../../_shared/Loading' -import { ShareModal } from '../../_shared/ShareModal' +import { Modal } from '../../_shared/Modal' import styles from './Feed.module.scss' export const FEED_PAGE_SIZE = 20 -const UNRATED_ARTICLES_COUNT = 5 +export type PeriodType = 'week' | 'month' | 'year' -type FeedPeriod = 'week' | 'month' | 'year' -type VisibilityMode = 'all' | 'community' | 'featured' - -type PeriodItem = { - value: FeedPeriod - title: string +export type FeedProps = { + shouts: Shout[] + mode?: 'followed' | 'discussed' | 'coauthored' | 'unrated' | 'all' + order?: '' | 'likes' | 'hot' } -type VisibilityItem = { - value: VisibilityMode - title: string +const PERIODS = { + day: 24 * 60 * 60, + month: 30 * 24 * 60 * 60, + year: 365 * 24 * 60 * 60 } -type FeedSearchParams = { - by: 'publish_date' | 'likes' | 'last_comment' - period: FeedPeriod - visibility: VisibilityMode -} - -type Props = { - loadShouts: (options: LoadShoutsOptions) => Promise<{ - hasMore: boolean - newShouts: Shout[] - }> -} - -const getFromDate = (period: FeedPeriod): number => { - const now = new Date() - let d: Date = now - switch (period) { - case 'week': { - d = new Date(now.setDate(now.getDate() - 7)) - break - } - case 'month': { - d = new Date(now.setMonth(now.getMonth() - 1)) - break - } - case 'year': { - d = new Date(now.setFullYear(now.getFullYear() - 1)) - break - } - } - return Math.floor(d.getTime() / 1000) -} - -export const FeedView = (props: Props) => { +export const FeedView = (props: FeedProps) => { const { t } = useLocalize() - - const monthPeriod: PeriodItem = { value: 'month', title: t('This month') } - - const periods: PeriodItem[] = [ - { value: 'week', title: t('This week') }, - monthPeriod, - { value: 'year', title: t('This year') } - ] - - const visibilities: VisibilityItem[] = [ - { value: 'community', title: t('All') }, - { value: 'featured', title: t('Published') } - ] - const { query } = useGraphQL() - const [searchParams, changeSearchParams] = useSearchParams() const loc = useLocation() + const client = useGraphQL() + const unrated = createAsync(async () => { + if (client) { + const shoutsLoader = loadUnratedShouts(client, { limit: 5 }) + return await shoutsLoader() + } + }) + const navigate = useNavigate() const { showModal } = useUI() const [isLoading, setIsLoading] = createSignal(false) const [isRightColumnLoaded, setIsRightColumnLoaded] = createSignal(false) const { session } = useSession() const { loadReactionsBy } = useReactions() - const { sortedFeed } = useFeed() const { topTopics } = useTopics() const { topAuthors } = useAuthors() - const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [topComments, setTopComments] = createSignal([]) - const [unratedArticles, setUnratedArticles] = createSignal([]) - const [_searchResults, setSearchResults] = createSignal([]) - - const currentPeriod = createMemo(() => { - const period = periods.find((p) => p.value === searchParams?.period) - if (!period) { - return monthPeriod - } - return period - }) - - const currentVisibility = createMemo(() => { - const visibility = visibilities.find((v) => v.value === searchParams?.visibility) - if (!visibility) { - return visibilities[0] - } - return visibility - }) - - const loadUnratedArticles = async () => { - if (session()) { - const resp = await query(getUnratedShoutsQuery, { limit: UNRATED_ARTICLES_COUNT }).toPromise() - setUnratedArticles(resp?.data?.load_shouts_unrated || []) - } - } - + const [searchParams, changeSearchParams] = useSearchParams() const loadTopComments = async () => { const comments = await loadReactionsBy({ by: { comment: true }, limit: 50 }) setTopComments(comments.sort(byCreated).reverse()) } - onMount(() => { - loadMore() - // eslint-disable-next-line promise/catch-or-return - Promise.all([loadTopComments()]).finally(() => setIsRightColumnLoaded(true)) - }) - + // post-load createEffect( on( - [() => session(), unratedArticles], - ([s, seen]) => { - if (s?.access_token && !(seen?.length > 0)) loadUnratedArticles() + () => props.shouts, + (sss?: Shout[]) => { + if (sss && Array.isArray(sss)) { + setIsLoading(true) + Promise.all([ + loadTopComments(), + loadReactionsBy({ by: { shouts: sss.map((s: Shout) => s.slug) } }) + ]).finally(() => { + console.debug('[views.feed] finally loaded reactions, data loading finished') + setIsRightColumnLoaded(true) + setIsLoading(false) + }) + } }, { defer: true } ) ) - // TODO: declare some details - createEffect( - on( - () => searchParams, - (_p) => { - setSearchResults([]) - loadMore() - }, - { defer: true } - ) - ) - - const loadFeedShouts = () => { - const options: LoadShoutsOptions = { - limit: FEED_PAGE_SIZE, - offset: sortedFeed()?.length || 0 - } - - if (searchParams?.by) { - options.order_by = searchParams?.by - } - - const visibilityMode = searchParams?.visibility - - if (visibilityMode === 'all') { - options.filters = { ...options.filters } - } else if (visibilityMode) { - options.filters = { - ...options.filters, - featured: visibilityMode === 'featured' - } - } - - if (searchParams?.by && searchParams?.by !== 'publish_date') { - const period = searchParams?.period || 'month' - options.filters = { after: getFromDate(period) } - } - - return props.loadShouts(options) - } - - const loadMore = async () => { - setIsLoading(true) - const { hasMore, newShouts } = await loadFeedShouts() - - setIsLoading(false) - - loadReactionsBy({ - by: { - shouts: newShouts.map((s) => s.slug) - } - }) - - setIsLoadMoreButtonVisible(hasMore) - } - - const ogImage = getImageUrl('production/image/logo_image.png') - const description = t( - 'Independent media project about culture, science, art and society with horizontal editing' - ) - const ogTitle = t('Feed') - const [shareData, setShareData] = createSignal() const handleShare = (shared: Shout | undefined) => { showModal('share') setShareData(shared) } + const asOption = (o: string) => { + const value = Math.floor(Date.now() / 1000) - PERIODS[o as keyof typeof PERIODS] + return { value, title: t(o) } + } + const asOptions = (opts: string[]) => opts.map(asOption) + const currentPeriod = createMemo(() => asOption(searchParams?.period || '')) + return ( -
        - - - - - - - - - - +
        @@ -246,64 +116,54 @@ export const FeedView = (props: Props) => { - +
        - + changeSearchParams({ period: period.value })} + onChange={(period: Option) => changeSearchParams({ period: period.value })} /> - changeSearchParams({ visibility: visibility.value }) - } + onChange={(mode: Option) => navigate(`/feed/${mode.value}`)} />
        }> - 0}> - + 0}> + {(article) => ( handleShare(shared)} @@ -318,7 +178,7 @@ export const FeedView = (props: Props) => {

        {t('Popular authors')}

        - + {t('All authors')} @@ -335,20 +195,12 @@ export const FeedView = (props: Props) => {
    - + {(article) => ( )} - - -

    - -

    -
    @@ -412,10 +264,10 @@ export const FeedView = (props: Props) => { - +

    {t('Be the first to rate')}

    - + {(article) => ( )} diff --git a/src/components/Views/FeedSettings.tsx b/src/components/Views/FeedSettings.tsx index 22cc9109..05a49e5e 100644 --- a/src/components/Views/FeedSettings.tsx +++ b/src/components/Views/FeedSettings.tsx @@ -1,4 +1,4 @@ -import { useLocalize } from '../../context/localize' +import { useLocalize } from '~/context/localize' import styles from '../../styles/FeedSettings.module.scss' @@ -14,18 +14,18 @@ export const FeedSettingsView = () => { @@ -35,7 +35,7 @@ export const FeedSettingsView = () => {
    - +
    - +
    - +
    - } + <> + 0}> + + + + SHOUTS_PER_PAGE}> + + + + + + + + + + + {t('Top commented')}} + nodate={true} + /> + + +
    {capitalize(randomTopic()?.title || '', true)}
    + +
    + } + /> + + + + + + + + + + + - - - - - - - - - - + + {(page) => ( + <> + + + + + + + + + )} + - - {(page) => ( - <> - - - - - - - - - )} - - + ) } diff --git a/src/components/Views/Inbox/Inbox.tsx b/src/components/Views/Inbox/Inbox.tsx index 3c9e9655..6f89cff0 100644 --- a/src/components/Views/Inbox/Inbox.tsx +++ b/src/components/Views/Inbox/Inbox.tsx @@ -1,27 +1,27 @@ import { clsx } from 'clsx' import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js' +import { Icon } from '~/components/_shared/Icon' +import { InviteMembers } from '~/components/_shared/InviteMembers' +import { Popover } from '~/components/_shared/Popover' +import { useInbox } from '~/context/inbox' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' import { useUI } from '~/context/ui' -import { useInbox } from '../../../context/inbox' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' import type { Chat, ChatMember, Message as MessageType, MutationCreate_MessageArgs -} from '../../../graphql/schema/chat.gen' -import type { Author } from '../../../graphql/schema/core.gen' +} from '~/graphql/schema/chat.gen' +import type { Author } from '~/graphql/schema/core.gen' import SimplifiedEditor from '../../Editor/SimplifiedEditor' import DialogCard from '../../Inbox/DialogCard' import DialogHeader from '../../Inbox/DialogHeader' import { Message } from '../../Inbox/Message' import MessagesFallback from '../../Inbox/MessagesFallback' import Search from '../../Inbox/Search' -import { Modal } from '../../Nav/Modal' -import { Icon } from '../../_shared/Icon' -import { InviteMembers } from '../../_shared/InviteMembers' -import { Popover } from '../../_shared/Popover' +import { Modal } from '../../_shared/Modal' import { useSearchParams } from '@solidjs/router' import styles from './Inbox.module.scss' @@ -307,7 +307,7 @@ export const InboxView = (props: Props) => { smallHeight={true} imageEnabled={true} isCancelButtonVisible={false} - placeholder={t('Write message')} + placeholder={t('New message')} setClear={isClear()} onSubmit={(message) => handleSubmit(message)} submitByCtrlEnter={true} diff --git a/src/components/Views/Profile/ProfileSecurity.tsx b/src/components/Views/Profile/ProfileSecurity.tsx new file mode 100644 index 00000000..bb0bf0d7 --- /dev/null +++ b/src/components/Views/Profile/ProfileSecurity.tsx @@ -0,0 +1,305 @@ +import { UpdateProfileInput } from '@authorizerdev/authorizer-js' +import { clsx } from 'clsx' +import { Show, createEffect, createSignal, on } from 'solid-js' +import { AuthGuard } from '~/components/AuthGuard' +import { PasswordField } from '~/components/AuthModal/PasswordField' +import { ProfileSettingsNavigation } from '~/components/ProfileNav' +import { Button } from '~/components/_shared/Button' +import { Icon } from '~/components/_shared/Icon' +import { Loading } from '~/components/_shared/Loading' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import { DEFAULT_HEADER_OFFSET, useSnackbar, useUI } from '~/context/ui' +import { validateEmail } from '~/utils/validate' +import styles from './Settings.module.scss' + +type FormField = 'oldPassword' | 'newPassword' | 'newPasswordConfirm' | 'email' +type FormData = Record + +// biome-ignore lint/suspicious/noExplicitAny: +export const ProfileSecurityView = (_props: any) => { + const { t } = useLocalize() + const { updateProfile, session, isSessionLoaded } = useSession() + const { showConfirm } = useUI() + const { showSnackbar } = useSnackbar() + const [newPasswordError, setNewPasswordError] = createSignal() + const [oldPasswordError, setOldPasswordError] = createSignal() + const [emailError, setEmailError] = createSignal() + const [isSubmitting, setIsSubmitting] = createSignal() + const [isFloatingPanelVisible, setIsFloatingPanelVisible] = createSignal(false) + + const initialState = { + oldPassword: undefined, + newPassword: undefined, + newPasswordConfirm: undefined, + email: undefined + } as FormData + + const [formData, setFormData] = createSignal(initialState) + let oldPasswordRef: HTMLDivElement | undefined + let newPasswordRepeatRef: HTMLDivElement | undefined + + createEffect( + on( + () => session()?.user?.email, + (email) => { + setFormData((prevData: FormData) => ({ ...prevData, email }) as FormData) + } + ) + ) + const handleInputChange = (name: FormField, value: string) => { + if ( + name === 'email' || + (name === 'newPasswordConfirm' && value && value?.length > 0 && !emailError() && !newPasswordError()) + ) { + setIsFloatingPanelVisible(true) + } else { + setIsFloatingPanelVisible(false) + } + setFormData((prevData) => ({ + ...prevData, + [name]: value + })) + } + + const handleCancel = async () => { + const isConfirmed = await showConfirm({ + confirmBody: t('Do you really want to reset all changes?'), + confirmButtonVariant: 'primary', + declineButtonVariant: 'secondary' + }) + if (isConfirmed) { + setEmailError() + setFormData({ + ...initialState, + ['email']: session()?.user?.email + }) + setIsFloatingPanelVisible(false) + } + } + const handleChangeEmail = (_value: string) => { + if (formData() && !validateEmail(formData()['email'] || '')) { + setEmailError(t('Invalid email')) + return + } + } + const handleCheckNewPassword = (value: string) => { + handleInputChange('newPasswordConfirm', value) + if (newPasswordRepeatRef && value !== formData()['newPassword']) { + const rect = newPasswordRepeatRef.getBoundingClientRect() + const topPosition = (window?.scrollY || 0) + rect.top - DEFAULT_HEADER_OFFSET * 2 + window?.scrollTo({ + top: topPosition, + left: 0, + behavior: 'smooth' + }) + showSnackbar({ type: 'error', body: t('Incorrect new password confirm') }) + setNewPasswordError(t('Passwords are not equal')) + } + } + + const handleSubmit = async () => { + setIsSubmitting(true) + + const options: UpdateProfileInput = { + old_password: formData()['oldPassword'], + new_password: formData()['newPassword'] || formData()['oldPassword'], + confirm_new_password: formData()['newPassword'] || formData()['oldPassword'], + email: formData()['email'] + } + + try { + const result = await updateProfile(options) + if (result) { + // FIXME: const { errors } = result + if (oldPasswordRef) { + // && errors.some((obj: Error) => obj.message === 'incorrect old password')) { + setOldPasswordError(t('Incorrect old password')) + showSnackbar({ type: 'error', body: t('Incorrect old password') }) + const rect = oldPasswordRef.getBoundingClientRect() + const topPosition = (window?.scrollY || 0) + rect.top - DEFAULT_HEADER_OFFSET * 2 + window?.scrollTo({ + top: topPosition, + left: 0, + behavior: 'smooth' + }) + setIsFloatingPanelVisible(false) + } + return + } + showSnackbar({ type: 'success', body: t('Profile successfully saved') }) + } catch (error) { + console.error(error) + } finally { + setIsSubmitting(false) + } + } + return ( + + }> +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +

    {t('Login and security')}

    +

    {t('Settings for account, email, password and login methods.')}

    + +
    +

    {t('Email')}

    +
    + setEmailError()} + onInput={(event) => handleChangeEmail(event.target.value)} + /> + + +
    + {emailError()} +
    +
    +
    + +

    {t('Change password')}

    +
    {t('Current password')}
    + +
    (oldPasswordRef = el)}> + setOldPasswordError()} + setError={oldPasswordError()} + onInput={(value: string) => handleInputChange('oldPassword', value)} + value={formData()['oldPassword'] || undefined} + disabled={isSubmitting()} + /> +
    + +
    {t('New password')}
    + { + handleInputChange('newPassword', value) + handleInputChange('newPasswordConfirm', '') + }} + value={formData()['newPassword'] ?? ''} + disabled={isSubmitting()} + disableAutocomplete={true} + /> + +
    {t('Confirm your new password')}
    +
    (newPasswordRepeatRef = el)}> + setNewPasswordError()} + setError={newPasswordError()} + onInput={handleCheckNewPassword} + disabled={isSubmitting()} + disableAutocomplete={true} + /> +
    +

    {t('Social networks')}

    +
    Google
    +
    +

    + +

    +
    + +
    VK
    +
    +

    + +

    +
    + +
    Facebook
    +
    +

    + +

    +
    + +
    Apple
    +
    +

    + +

    +
    + +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + ) +} diff --git a/src/components/ProfileSettings/ProfileSettings.tsx b/src/components/Views/Profile/ProfileSettings.tsx similarity index 89% rename from src/components/ProfileSettings/ProfileSettings.tsx rename to src/components/Views/Profile/ProfileSettings.tsx index 7563b716..034e0002 100644 --- a/src/components/ProfileSettings/ProfileSettings.tsx +++ b/src/components/Views/Profile/ProfileSettings.tsx @@ -14,29 +14,28 @@ import { onMount } from 'solid-js' import { createStore } from 'solid-js/store' +import { useLocalize } from '~/context/localize' +import { useProfile } from '~/context/profile' +import { useSession } from '~/context/session' +import { useSnackbar, useUI } from '~/context/ui' +import { InputMaybe, ProfileInput } from '~/graphql/schema/core.gen' +import { getImageUrl } from '~/lib/getThumbUrl' +import { handleImageUpload } from '~/lib/handleImageUpload' +import { profileSocialLinks } from '~/lib/profileSocialLinks' +import { clone } from '~/utils/clone' +import { validateUrl } from '~/utils/validate' +import { ProfileSettingsNavigation } from '../../ProfileNav' +import { Button } from '../../_shared/Button' +import { Icon } from '../../_shared/Icon' +import { ImageCropper } from '../../_shared/ImageCropper' +import { Loading } from '../../_shared/Loading' +import { Modal } from '../../_shared/Modal' +import { Popover } from '../../_shared/Popover' +import { SocialNetworkInput } from '../../_shared/SocialNetworkInput' +import styles from './Settings.module.scss' -import { useLocalize } from '../../context/localize' -import { useProfile } from '../../context/profile' -import { useSession } from '../../context/session' -import { useSnackbar, useUI } from '../../context/ui' -import { InputMaybe, ProfileInput } from '../../graphql/schema/core.gen' -import styles from '../../pages/profile/Settings.module.scss' -import { clone } from '../../utils/clone' -import { getImageUrl } from '../../utils/getImageUrl' -import { handleImageUpload } from '../../utils/handleImageUpload' -import { profileSocialLinks } from '../../utils/profileSocialLinks' -import { validateUrl } from '../../utils/validateUrl' -import { Modal } from '../Nav/Modal' -import { ProfileSettingsNavigation } from '../Nav/ProfileSettingsNavigation' -import { Button } from '../_shared/Button' -import { Icon } from '../_shared/Icon' -import { ImageCropper } from '../_shared/ImageCropper' -import { Loading } from '../_shared/Loading' -import { Popover } from '../_shared/Popover' -import { SocialNetworkInput } from '../_shared/SocialNetworkInput' - -const SimplifiedEditor = lazy(() => import('../../components/Editor/SimplifiedEditor')) -const GrowingTextarea = lazy(() => import('../../components/_shared/GrowingTextarea/GrowingTextarea')) +const SimplifiedEditor = lazy(() => import('~/components/Editor/SimplifiedEditor')) +const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea')) function filterNulls(arr: InputMaybe[]): string[] { return arr.filter((item): item is string => item !== null && item !== undefined) @@ -189,6 +188,14 @@ export const ProfileSettings = () => { updateFormField('links', link, true) } + const slugUpdate = (ev: InputEvent) => { + const input = (ev.target || ev.currentTarget) as HTMLInputElement + const value = input.value + const newValue = value.startsWith('@') || value.startsWith('!') ? value.substring(1) : value + input.value = newValue + updateFormField('slug', newValue) + } + return ( 0 && isFormInitialized()} fallback={}> <> @@ -294,7 +301,7 @@ export const ProfileSettings = () => {

    {t('Address on Discours')}

    - +
    { id="user-address" data-lpignore="true" autocomplete="one-time-code2" - onInput={(event) => updateFormField('slug', event.currentTarget.value)} + onInput={slugUpdate} value={form.slug || ''} ref={(el) => (slugInputRef = el)} class="nolabel" @@ -324,15 +331,15 @@ export const ProfileSettings = () => { maxLength={120} /> -

    {t('About')}

    +

    {t('About the author')}

    updateFormField('about', value)} diff --git a/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.module.scss b/src/components/Views/Profile/ProfileSubscriptions.module.scss similarity index 100% rename from src/components/Views/ProfileSubscriptions/ProfileSubscriptions.module.scss rename to src/components/Views/Profile/ProfileSubscriptions.module.scss diff --git a/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx b/src/components/Views/Profile/ProfileSubscriptions.tsx similarity index 85% rename from src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx rename to src/components/Views/Profile/ProfileSubscriptions.tsx index c53d02d5..f460f278 100644 --- a/src/components/Views/ProfileSubscriptions/ProfileSubscriptions.tsx +++ b/src/components/Views/Profile/ProfileSubscriptions.tsx @@ -1,19 +1,16 @@ import { clsx } from 'clsx' import { For, Show, createEffect, createSignal, on } from 'solid-js' - -import { FollowsFilter, useFollowing } from '../../../context/following' -import { useLocalize } from '../../../context/localize' -import { Author, Topic } from '../../../graphql/schema/core.gen' -import { dummyFilter } from '../../../utils/dummyFilter' -import { isAuthor } from '../../../utils/isAuthor' -import { AuthorBadge } from '../../Author/AuthorBadge' -import { ProfileSettingsNavigation } from '../../Nav/ProfileSettingsNavigation' -import { TopicBadge } from '../../Topic/TopicBadge' -import { Loading } from '../../_shared/Loading' -import { SearchField } from '../../_shared/SearchField' - -import styles from '../../../pages/profile/Settings.module.scss' +import { Loading } from '~/components/_shared/Loading' +import { SearchField } from '~/components/_shared/SearchField' +import { FollowsFilter, useFollowing } from '~/context/following' +import { useLocalize } from '~/context/localize' +import { Author, Topic } from '~/graphql/schema/core.gen' +import { dummyFilter } from '~/lib/dummyFilter' import stylesSettings from '../../../styles/FeedSettings.module.scss' +import { AuthorBadge } from '../../Author/AuthorBadge' +import { ProfileSettingsNavigation } from '../../ProfileNav' +import { TopicBadge } from '../../Topic/TopicBadge' +import styles from '../Profile/Settings.module.scss' export const ProfileSubscriptions = () => { const { t, lang } = useLocalize() @@ -100,10 +97,10 @@ export const ProfileSubscriptions = () => { {(followingItem) => (
    - {isAuthor(followingItem) ? ( - + {'name' in followingItem ? ( + ) : ( - + )}
    )} diff --git a/src/pages/profile/Settings.module.scss b/src/components/Views/Profile/Settings.module.scss similarity index 100% rename from src/pages/profile/Settings.module.scss rename to src/components/Views/Profile/Settings.module.scss index 38babb4d..b1e3fdef 100644 --- a/src/pages/profile/Settings.module.scss +++ b/src/components/Views/Profile/Settings.module.scss @@ -73,8 +73,6 @@ h5 { } .discoursName { - display: flex; - @include media-breakpoint-down(sm) { flex-wrap: wrap; @@ -83,6 +81,8 @@ h5 { } } + display: flex; + label { margin: 0.6em 0.5em 0 0; } @@ -298,11 +298,11 @@ h5 { } .cancelLabelMobile { - display: none; - @include media-breakpoint-down(sm) { display: block; } + + display: none; } :global(.row) > * { diff --git a/src/components/Views/ProfileSubscriptions/index.ts b/src/components/Views/ProfileSubscriptions/index.ts deleted file mode 100644 index 17849841..00000000 --- a/src/components/Views/ProfileSubscriptions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ProfileSubscriptions } from './ProfileSubscriptions' diff --git a/src/components/Views/PublishSettings/PublishSettings.tsx b/src/components/Views/PublishSettings/PublishSettings.tsx index 56302262..f4b0a46b 100644 --- a/src/components/Views/PublishSettings/PublishSettings.tsx +++ b/src/components/Views/PublishSettings/PublishSettings.tsx @@ -2,17 +2,17 @@ import { clsx } from 'clsx' import { Show, createEffect, createMemo, createSignal, lazy, onMount } from 'solid-js' import { createStore } from 'solid-js/store' +import { Button } from '~/components/_shared/Button' +import { Icon } from '~/components/_shared/Icon' +import { Image } from '~/components/_shared/Image' +import { ShoutForm, useEditorContext } from '~/context/editor' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import { useTopics } from '~/context/topics' import { useSnackbar, useUI } from '~/context/ui' -import { ShoutForm, useEditorContext } from '../../../context/editor' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' -import { useTopics } from '../../../context/topics' -import { Topic } from '../../../graphql/schema/core.gen' +import { Topic } from '~/graphql/schema/core.gen' import { TopicSelect, UploadModalContent } from '../../Editor' -import { Modal } from '../../Nav/Modal' -import { Button } from '../../_shared/Button' -import { Icon } from '../../_shared/Icon' -import { Image } from '../../_shared/Image' +import { Modal } from '../../_shared/Modal' import { useNavigate } from '@solidjs/router' import { UploadedFile } from '~/types/upload' @@ -20,7 +20,7 @@ import stylesBeside from '../../Feed/Beside.module.scss' import styles from './PublishSettings.module.scss' const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) -const GrowingTextarea = lazy(() => import('../../_shared/GrowingTextarea/GrowingTextarea')) +const GrowingTextarea = lazy(() => import('~/components/_shared/GrowingTextarea/GrowingTextarea')) const DESCRIPTION_MAX_LENGTH = 400 type Props = { @@ -144,7 +144,12 @@ export const PublishSettings = (props: Props) => { const handleSaveDraft = () => { saveShout({ ...props.form, ...settingsForm }) } - + const removeSpecial = (ev: InputEvent) => { + const input = ev.target as HTMLInputElement + const value = input.value + const newValue = value.startsWith('@') || value.startsWith('!') ? value.substring(1) : value + input.value = newValue + } return (
    @@ -235,7 +240,7 @@ export const PublishSettings = (props: Props) => {

    {t('Slug')}

    - +
    diff --git a/src/components/Views/Search.tsx b/src/components/Views/Search.tsx index 22952d5b..3bdc2eb2 100644 --- a/src/components/Views/Search.tsx +++ b/src/components/Views/Search.tsx @@ -1,10 +1,10 @@ import { For, Show, createSignal, onMount } from 'solid-js' import { useSearchParams } from '@solidjs/router' -import { useFeed } from '../../context/feed' -import { useLocalize } from '../../context/localize' -import type { SearchResult } from '../../graphql/schema/core.gen' -import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll' +import { useFeed } from '~/context/feed' +import { useLocalize } from '~/context/localize' +import type { SearchResult } from '~/graphql/schema/core.gen' +import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll' import { ArticleCard } from '../Feed/ArticleCard' import '../../styles/Search.scss' diff --git a/src/components/Views/StaticPage.tsx b/src/components/Views/StaticPage.tsx index 479d0672..d6eebfb9 100644 --- a/src/components/Views/StaticPage.tsx +++ b/src/components/Views/StaticPage.tsx @@ -1,30 +1,33 @@ -import { JSX } from 'solid-js' - -import { TableOfContents } from '../TableOfContents' +import { JSX, onMount } from 'solid-js' +import { processPrepositions } from '~/intl/prepositions' import { PageLayout } from '../_shared/PageLayout' +import { TableOfContents } from '../_shared/TableOfContents' type Props = { title: string + desc?: string children: JSX.Element } -export const StaticPage = (props: Props) => { - let articleBodyElement: HTMLElement | null = null +export const StaticPage = (props: Props) => { + let bodyEl: HTMLElement | undefined + onMount(() => { + if (bodyEl) bodyEl.innerHTML = processPrepositions(bodyEl.innerHTML) + }) return ( - -
    (articleBodyElement = el)} - > + +
    (bodyEl = el)}>
    -
    {props.children}
    +
    +

    {props.title}

    + {props.children} +
    diff --git a/src/components/Views/Topic.tsx b/src/components/Views/Topic.tsx index 0e514d54..102b1bf7 100644 --- a/src/components/Views/Topic.tsx +++ b/src/components/Views/Topic.tsx @@ -1,141 +1,131 @@ -import { Author, AuthorsBy, LoadShoutsOptions, Shout, Topic } from '../../graphql/schema/core.gen' - -import { Meta } from '@solidjs/meta' -import { clsx } from 'clsx' -import { For, Show, createEffect, createMemo, createSignal, on, onMount } from 'solid-js' - import { useSearchParams } from '@solidjs/router' -import { useGraphQL } from '~/context/graphql' -import getRandomTopShoutsQuery from '~/graphql/query/core/articles-load-random-top' -import loadShoutsRandomQuery from '~/graphql/query/core/articles-load-random-topic' -import loadAuthorsByQuery from '~/graphql/query/core/authors-load-by' -import getTopicFollowersQuery from '~/graphql/query/core/topic-followers' -import { useAuthors } from '../../context/authors' -import { useFeed } from '../../context/feed' -import { useLocalize } from '../../context/localize' -import { useTopics } from '../../context/topics' +import { clsx } from 'clsx' +import { For, Show, Suspense, createEffect, createMemo, createSignal, on, onMount } from 'solid-js' +import { useAuthors } from '~/context/authors' +import { useFeed } from '~/context/feed' +import { useLocalize } from '~/context/localize' +import { useTopics } from '~/context/topics' +import { loadAuthors, loadFollowersByTopic, loadShouts } from '~/graphql/api/public' +import { Author, AuthorsBy, LoadShoutsOptions, Shout, Topic } from '~/graphql/schema/core.gen' +import { SHOUTS_PER_PAGE } from '~/routes/(main)' +import { getUnixtime } from '~/utils/date' +import { paginate } from '~/utils/paginate' +import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll' import styles from '../../styles/Topic.module.scss' -import { capitalize } from '../../utils/capitalize' -import { getImageUrl } from '../../utils/getImageUrl' -import { getUnixtime } from '../../utils/getServerDate' -import { getDescription } from '../../utils/meta' -import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll' -import { splitToPages } from '../../utils/splitToPages' import { Beside } from '../Feed/Beside' import { Row1 } from '../Feed/Row1' import { Row2 } from '../Feed/Row2' import { Row3 } from '../Feed/Row3' import { FullTopic } from '../Topic/Full' +import { Loading } from '../_shared/Loading' import { ArticleCardSwiper } from '../_shared/SolidSwiper/ArticleCardSwiper' -type TopicsPageSearchParams = { - by: 'comments' | '' | 'recent' | 'viewed' | 'rating' | 'commented' -} +// FIXME: should be 'last_comment' and 'comments_stat' or just one? +export type TopicFeedSortBy = 'comments' | '' | 'recent' | 'viewed' | 'rating' | 'commented' interface Props { topic: Topic shouts: Shout[] topicSlug: string followers?: Author[] + selectedTab?: TopicFeedSortBy } export const PRERENDERED_ARTICLES_COUNT = 28 const LOAD_MORE_PAGE_SIZE = 9 // Row3 + Row3 + Row3 export const TopicView = (props: Props) => { - const { t, lang } = useLocalize() - const { query } = useGraphQL() - const [searchParams, changeSearchParams] = useSearchParams() - const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) - const { feedByTopic, loadShouts } = useFeed() - const sortedFeed = createMemo(() => feedByTopic()[topic()?.slug || ''] || []) + const { t } = useLocalize() + const { feedByTopic, addFeed } = useFeed() const { topicEntities } = useTopics() const { authorsByTopic } = useAuthors() + const [searchParams, changeSearchParams] = useSearchParams<{ by: TopicFeedSortBy }>() + const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [favoriteTopArticles, setFavoriteTopArticles] = createSignal([]) const [reactedTopMonthArticles, setReactedTopMonthArticles] = createSignal([]) - const [topic, setTopic] = createSignal() - createEffect( - on([() => props.topicSlug, topic, topicEntities], async ([slug, t, ttt]) => { - if (slug && !t && ttt) { - const current = slug in ttt ? ttt[slug] : null - console.debug(current) - setTopic(current as Topic) - await loadTopicFollowers() - await loadTopicAuthors() - loadRandom() - } - }) - ) - const [followers, setFollowers] = createSignal(props.followers || []) + const sortedFeed = createMemo(() => feedByTopic()[topic()?.slug || ''] || []) // TODO: filter + sort + const loadTopicFollowers = async () => { - const resp = await query(getTopicFollowersQuery, { slug: props.topicSlug }).toPromise() - setFollowers(resp?.data?.get_topic_followers || []) + const topicFollowersFetcher = loadFollowersByTopic(props.topicSlug) + const topicFollowers = await topicFollowersFetcher() + topicFollowers && setFollowers(topicFollowers) } + const [topicAuthors, setTopicAuthors] = createSignal([]) const loadTopicAuthors = async () => { const by: AuthorsBy = { topic: props.topicSlug } - const resp = await query(loadAuthorsByQuery, { by, limit: 10, offset: 0 }).toPromise() - setTopicAuthors(resp?.data?.load_authors_by || []) + const topicAuthorsFetcher = await loadAuthors({ by, limit: 10, offset: 0 }) + const result = await topicAuthorsFetcher() + result && setTopicAuthors(result) } - const loadFavoriteTopArticles = async (topic: string) => { + const loadFavoriteTopArticles = async () => { const options: LoadShoutsOptions = { - filters: { featured: true, topic: topic }, + filters: { featured: true, topic: props.topicSlug }, limit: 10, random_limit: 100 } - const resp = await query(getRandomTopShoutsQuery, { options }).toPromise() - setFavoriteTopArticles(resp?.data?.l) + const topicRandomShoutsFetcher = loadShouts(options) + const result = await topicRandomShoutsFetcher() + result && setFavoriteTopArticles(result) } - const loadReactedTopMonthArticles = async (topic: string) => { + const loadReactedTopMonthArticles = async () => { const now = new Date() const after = getUnixtime(new Date(now.setMonth(now.getMonth() - 1))) const options: LoadShoutsOptions = { - filters: { after: after, featured: true, topic: topic }, + filters: { after: after, featured: true, topic: props.topicSlug }, limit: 10, random_limit: 10 } - const resp = await query(loadShoutsRandomQuery, { options }).toPromise() - setReactedTopMonthArticles(resp?.data?.load_shouts_random) + const reactedTopMonthShoutsFetcher = loadShouts(options) + const result = await reactedTopMonthShoutsFetcher() + result && setReactedTopMonthArticles(result) } - const loadRandom = () => { - if (topic()) { - loadFavoriteTopArticles((topic() as Topic).slug) - loadReactedTopMonthArticles((topic() as Topic).slug) - } - } - - const title = createMemo( - () => - `#${capitalize( - lang() === 'en' - ? (topic() as Topic)?.slug.replace(/-/, ' ') - : (topic() as Topic)?.title || (topic() as Topic)?.slug.replace(/-/, ' '), - true - )}` + // второй этап начальной загрузки данных + createEffect( + on( + topicEntities, + (ttt: Record) => { + if (props.topicSlug in ttt) { + Promise.all([ + loadFavoriteTopArticles(), + loadReactedTopMonthArticles(), + loadTopicAuthors(), + loadTopicFollowers() + ]).finally(() => { + setTopic(ttt[props.topicSlug]) + }) + } + }, + { defer: true } + ) ) + // дозагрузка const loadMore = async () => { saveScrollPosition() - - const { hasMore } = await loadShouts({ - filters: { topic: topic()?.slug }, - limit: LOAD_MORE_PAGE_SIZE, - offset: sortedFeed().length // FIXME: use feedByTopic + const amountBefore = feedByTopic()?.[props.topicSlug]?.length || 0 + const topicShoutsFetcher = loadShouts({ + filters: { topic: props.topicSlug }, + limit: SHOUTS_PER_PAGE, + offset: amountBefore }) - setIsLoadMoreButtonVisible(hasMore) - + const result = await topicShoutsFetcher() + if (result) { + addFeed(result) + const amountAfter = feedByTopic()[props.topicSlug].length + setIsLoadMoreButtonVisible(amountBefore !== amountAfter) + } restoreScrollPosition() } onMount(() => { - loadRandom() if (sortedFeed() || [].length === PRERENDERED_ARTICLES_COUNT) { loadMore() } @@ -150,125 +140,109 @@ export const TopicView = (props: Props) => { }) */ const pages = createMemo(() => - splitToPages(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE) + paginate(sortedFeed(), PRERENDERED_ARTICLES_COUNT, LOAD_MORE_PAGE_SIZE) ) - - const ogImage = () => - topic()?.pic - ? getImageUrl(topic()?.pic || '', { width: 1200 }) - : getImageUrl('production/image/logo_image.png') - const description = () => - topic()?.body - ? getDescription(topic()?.body || '') - : t('The most interesting publications on the topic', { topicName: title() }) - return (
    - - - - - - - - - - - -
    -
    -
    -
      -
    • - -
    • - {/*TODO: server sort*/} - {/*
    • */} - {/* */} - {/*
    • */} - {/*
    • */} - {/* */} - {/*
    • */} - {/*
    • */} - {/* */} - {/*
    • */} -
    -
    -
    -
    - {`${t('Show')} `} - {t('All posts')} + + + {/*TODO: server sort*/} + {/*
  • */} + {/* */} + {/*
  • */} + {/*
  • */} + {/* */} + {/*
  • */} + {/*
  • */} + {/* */} + {/*
  • */} + +
    +
    +
    + {`${t('Show')} `} + {t('All posts')} +
    -
    - - + + - - 0} keyed={true}> - - - + + 0} keyed={true}> + + + - - + + - 0} keyed={true}> - - - 15}> - - - + 0} keyed={true}> + + + 15}> + + + - - {(page) => ( - <> - - - - - )} - + + {(page) => ( + <> + + + + + )} + - -

    - -

    -
    + +

    + +

    +
    +
    ) } diff --git a/src/components/_shared/Button/Button.tsx b/src/components/_shared/Button/Button.tsx index 66172c0d..11434e0b 100644 --- a/src/components/_shared/Button/Button.tsx +++ b/src/components/_shared/Button/Button.tsx @@ -6,6 +6,7 @@ import styles from './Button.module.scss' export type ButtonVariant = 'primary' | 'secondary' | 'bordered' | 'inline' | 'light' | 'outline' | 'danger' type Props = { + title?: string value: string | JSX.Element size?: 'S' | 'M' | 'L' variant?: ButtonVariant @@ -28,6 +29,7 @@ export const Button = (props: Props) => { } props.ref = el }} + title={props.title || (typeof props.value === 'string' ? props.value : '')} onClick={props.onClick} type={props.type ?? 'button'} disabled={props.loading || props.disabled} diff --git a/src/components/Nav/ConfirmModal/ConfirmModal.module.scss b/src/components/_shared/ConfirmModal/ConfirmModal.module.scss similarity index 100% rename from src/components/Nav/ConfirmModal/ConfirmModal.module.scss rename to src/components/_shared/ConfirmModal/ConfirmModal.module.scss index a0f030b8..75156ca8 100644 --- a/src/components/Nav/ConfirmModal/ConfirmModal.module.scss +++ b/src/components/_shared/ConfirmModal/ConfirmModal.module.scss @@ -5,14 +5,14 @@ .confirmModalTitle { @include font-size(3.2rem); + @include media-breakpoint-up(sm) { + margin: 0 10%; + } + color: var(--default-color); font-weight: 700; margin: 0 3rem; text-align: center; - - @include media-breakpoint-up(sm) { - margin: 0 10%; - } } .confirmModalActions { diff --git a/src/components/Nav/ConfirmModal/ConfirmModal.tsx b/src/components/_shared/ConfirmModal/ConfirmModal.tsx similarity index 87% rename from src/components/Nav/ConfirmModal/ConfirmModal.tsx rename to src/components/_shared/ConfirmModal/ConfirmModal.tsx index d3c565c1..79f3cfca 100644 --- a/src/components/Nav/ConfirmModal/ConfirmModal.tsx +++ b/src/components/_shared/ConfirmModal/ConfirmModal.tsx @@ -1,6 +1,6 @@ -import { useLocalize } from '../../../context/localize' -import { useUI } from '../../../context/ui' -import { Button } from '../../_shared/Button' +import { Button } from '~/components/_shared/Button' +import { useLocalize } from '~/context/localize' +import { useUI } from '~/context/ui' import styles from './ConfirmModal.module.scss' diff --git a/src/components/Nav/ConfirmModal/index.ts b/src/components/_shared/ConfirmModal/index.ts similarity index 100% rename from src/components/Nav/ConfirmModal/index.ts rename to src/components/_shared/ConfirmModal/index.ts diff --git a/src/components/_shared/DarkModeToggle/DarkModeToggle.tsx b/src/components/_shared/DarkModeToggle/DarkModeToggle.tsx index 1961529d..522b28a7 100644 --- a/src/components/_shared/DarkModeToggle/DarkModeToggle.tsx +++ b/src/components/_shared/DarkModeToggle/DarkModeToggle.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx' import { createSignal, onCleanup, onMount } from 'solid-js' -import { useLocalize } from '../../../context/localize' +import { useLocalize } from '~/context/localize' import { Icon } from '../Icon' import styles from './DarkModeToggle.module.scss' @@ -10,14 +10,13 @@ type Props = { class?: string } -const editorDarkModeSelected = localStorage.getItem('editorDarkMode') -const editorDarkModeAttr = document.documentElement.getAttribute('editorDarkMode') - export const DarkModeToggle = (props: Props) => { const { t } = useLocalize() const [editorDarkMode, setEditorDarkMode] = createSignal(false) onMount(() => { + const editorDarkModeSelected = localStorage?.getItem('editorDarkMode') + const editorDarkModeAttr = document.documentElement.getAttribute('editorDarkMode') if (editorDarkModeSelected === 'true') { setEditorDarkMode(true) document.documentElement.dataset.editorDarkMode = 'true' @@ -27,7 +26,7 @@ export const DarkModeToggle = (props: Props) => { } if (!(editorDarkModeAttr || editorDarkModeSelected)) { - localStorage.setItem('editorDarkMode', 'false') + localStorage?.setItem('editorDarkMode', 'false') document.documentElement.dataset.editorDarkMode = 'false' } @@ -39,7 +38,7 @@ export const DarkModeToggle = (props: Props) => { const handleSwitchTheme = () => { setEditorDarkMode(!editorDarkMode()) - localStorage.setItem('editorDarkMode', editorDarkMode() ? 'true' : 'false') + localStorage?.setItem('editorDarkMode', editorDarkMode() ? 'true' : 'false') document.documentElement.dataset.editorDarkMode = editorDarkMode() ? 'true' : 'false' } diff --git a/src/components/_shared/DropArea/DropArea.tsx b/src/components/_shared/DropArea/DropArea.tsx index eab31c71..e334d3de 100644 --- a/src/components/_shared/DropArea/DropArea.tsx +++ b/src/components/_shared/DropArea/DropArea.tsx @@ -2,11 +2,11 @@ import { UploadFile, createDropzone, createFileUploader } from '@solid-primitive import { clsx } from 'clsx' import { JSX, Show, createSignal } from 'solid-js' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' -import { handleFileUpload } from '../../../utils/handleFileUpload' -import { handleImageUpload } from '../../../utils/handleImageUpload' -import { validateFiles } from '../../../utils/validateFile' +import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import { handleFileUpload } from '~/lib/handleFileUpload' +import { handleImageUpload } from '~/lib/handleImageUpload' +import { validateUploads } from '~/lib/validateUploads' import styles from './DropArea.module.scss' @@ -62,7 +62,7 @@ export const DropArea = (props: Props) => { setDropAreaError(t('Many files, choose only one')) return } - const isValid = validateFiles(props.fileType, selectedFiles) + const isValid = validateUploads(props.fileType, selectedFiles) if (isValid) { await runUpload(selectedFiles) } else { diff --git a/src/components/_shared/DropdownSelect/DropdownSelect.tsx b/src/components/_shared/DropdownSelect/DropdownSelect.tsx index dcfca82f..af39bd93 100644 --- a/src/components/_shared/DropdownSelect/DropdownSelect.tsx +++ b/src/components/_shared/DropdownSelect/DropdownSelect.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx' import { For, Show, createSignal } from 'solid-js' -import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler' +import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler' import styles from './DropdownSelect.module.scss' diff --git a/src/components/_shared/FloatingPanel/FloatingPanel.module.scss b/src/components/_shared/FloatingPanel/FloatingPanel.module.scss index f5e72ec7..62907ed2 100644 --- a/src/components/_shared/FloatingPanel/FloatingPanel.module.scss +++ b/src/components/_shared/FloatingPanel/FloatingPanel.module.scss @@ -1,4 +1,12 @@ .PanelWrapper { + @include media-breakpoint-down(sm) { + flex-wrap: wrap; + + input { + min-width: 250px; + } + } + position: fixed; bottom: 20px; left: 0; @@ -13,14 +21,6 @@ padding: 14px; background-color: var(--background-color); border: 2px solid black; - - @include media-breakpoint-down(sm) { - flex-wrap: wrap; - - input { - min-width: 250px; - } - } } .PanelWrapperVisible { diff --git a/src/components/_shared/FollowingButton/FollowingButton.tsx b/src/components/_shared/FollowingButton/FollowingButton.tsx index 797c8acc..4e099f48 100644 --- a/src/components/_shared/FollowingButton/FollowingButton.tsx +++ b/src/components/_shared/FollowingButton/FollowingButton.tsx @@ -1,6 +1,6 @@ import { clsx } from 'clsx' import { Show, createMemo } from 'solid-js' -import { useLocalize } from '../../../context/localize' +import { useLocalize } from '~/context/localize' import { Button } from '../Button' import { CheckButton } from '../CheckButton' import { Icon } from '../Icon' diff --git a/src/components/_shared/FollowingCounters/FollowingCounters.tsx b/src/components/_shared/FollowingCounters/FollowingCounters.tsx index f7795e94..2808d2c3 100644 --- a/src/components/_shared/FollowingCounters/FollowingCounters.tsx +++ b/src/components/_shared/FollowingCounters/FollowingCounters.tsx @@ -1,8 +1,8 @@ import { For, Show, createMemo } from 'solid-js' -import { useLocalize } from '../../../context/localize' +import { useLocalize } from '~/context/localize' -import { Author, Topic } from '../../../graphql/schema/core.gen' +import { Author, Topic } from '~/graphql/schema/core.gen' import { Userpic } from '../../Author/Userpic' import styles from './FollowingCounters.module.scss' diff --git a/src/components/_shared/GroupAvatar/GroupAvatar.tsx b/src/components/_shared/GroupAvatar/GroupAvatar.tsx index 1f196ce9..a929cfad 100644 --- a/src/components/_shared/GroupAvatar/GroupAvatar.tsx +++ b/src/components/_shared/GroupAvatar/GroupAvatar.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx' import { For } from 'solid-js' -import { Author } from '../../../graphql/schema/core.gen' +import { Author } from '~/graphql/schema/core.gen' import { Userpic } from '../../Author/Userpic' import styles from './GroupAvatar.module.scss' diff --git a/src/components/_shared/Icon/Icon.module.scss b/src/components/_shared/Icon/Icon.module.scss index f197f704..bcf6c09e 100644 --- a/src/components/_shared/Icon/Icon.module.scss +++ b/src/components/_shared/Icon/Icon.module.scss @@ -31,6 +31,10 @@ .notificationsCounter { + @include media-breakpoint-up(md) { + left: 1.8rem; + } + align-items: center; background-color: #E84500; border-radius: 0.8rem; @@ -46,8 +50,4 @@ position: absolute; text-align: center; top: -0.5rem; - - @include media-breakpoint-up(md) { - left: 1.8rem; - } } diff --git a/src/components/_shared/Image/Image.tsx b/src/components/_shared/Image/Image.tsx index 95d17601..01966289 100644 --- a/src/components/_shared/Image/Image.tsx +++ b/src/components/_shared/Image/Image.tsx @@ -3,7 +3,7 @@ import type { JSX } from 'solid-js' import { Link } from '@solidjs/meta' import { splitProps } from 'solid-js' -import { getImageUrl } from '../../../utils/getImageUrl' +import { getImageUrl } from '~/lib/getThumbUrl' type Props = JSX.ImgHTMLAttributes & { width: number diff --git a/src/components/_shared/ImageCropper/ImageCropper.tsx b/src/components/_shared/ImageCropper/ImageCropper.tsx index 50f53536..30a2863a 100644 --- a/src/components/_shared/ImageCropper/ImageCropper.tsx +++ b/src/components/_shared/ImageCropper/ImageCropper.tsx @@ -4,7 +4,7 @@ import { UploadFile } from '@solid-primitives/upload' import Cropper from 'cropperjs' import { Show, createSignal, onMount } from 'solid-js' -import { useLocalize } from '../../../context/localize' +import { useLocalize } from '~/context/localize' import { Button } from '../Button' import styles from './ImageCropper.module.scss' diff --git a/src/components/InlineLoader/InlineLoader.module.scss b/src/components/_shared/InlineLoader/InlineLoader.module.scss similarity index 100% rename from src/components/InlineLoader/InlineLoader.module.scss rename to src/components/_shared/InlineLoader/InlineLoader.module.scss diff --git a/src/components/InlineLoader/InlineLoader.tsx b/src/components/_shared/InlineLoader/InlineLoader.tsx similarity index 77% rename from src/components/InlineLoader/InlineLoader.tsx rename to src/components/_shared/InlineLoader/InlineLoader.tsx index 335e1f82..702fa7f5 100644 --- a/src/components/InlineLoader/InlineLoader.tsx +++ b/src/components/_shared/InlineLoader/InlineLoader.tsx @@ -1,5 +1,5 @@ -import { useLocalize } from '../../context/localize' -import { Loading } from '../_shared/Loading' +import { useLocalize } from '~/context/localize' +import { Loading } from '../Loading' import styles from './InlineLoader.module.scss' type Props = { diff --git a/src/components/InlineLoader/index.ts b/src/components/_shared/InlineLoader/index.ts similarity index 100% rename from src/components/InlineLoader/index.ts rename to src/components/_shared/InlineLoader/index.ts diff --git a/src/components/_shared/InviteMembers/InviteMembers.tsx b/src/components/_shared/InviteMembers/InviteMembers.tsx index 1a851bc3..a273fd8d 100644 --- a/src/components/_shared/InviteMembers/InviteMembers.tsx +++ b/src/components/_shared/InviteMembers/InviteMembers.tsx @@ -2,15 +2,15 @@ import { createInfiniteScroll } from '@solid-primitives/pagination' import { clsx } from 'clsx' import { For, Show, createEffect, createSignal, on } from 'solid-js' +import { useAuthors } from '~/context/authors' +import { useInbox } from '~/context/inbox' +import { useLocalize } from '~/context/localize' import { useUI } from '~/context/ui' -import { useAuthors } from '../../../context/authors' -import { useInbox } from '../../../context/inbox' -import { useLocalize } from '../../../context/localize' -import { Author } from '../../../graphql/schema/core.gen' +import { Author } from '~/graphql/schema/core.gen' import { AuthorBadge } from '../../Author/AuthorBadge' -import { InlineLoader } from '../../InlineLoader' import { Button } from '../Button' import { DropdownSelect } from '../DropdownSelect' +import { InlineLoader } from '../InlineLoader' import styles from './InviteMembers.module.scss' @@ -48,7 +48,7 @@ export const InviteMembers = (props: Props) => { const fetcher = async (page: number) => { await new Promise((resolve, reject) => { const checkDataLoaded = () => { - if (authorsSorted().length > 0) { + if ((authorsSorted?.().length || 0) > 0) { resolve(true) } else { setTimeout(checkDataLoaded, 100) diff --git a/src/components/_shared/Lightbox/Lightbox.tsx b/src/components/_shared/Lightbox/Lightbox.tsx index e4daa313..b4c6b02c 100644 --- a/src/components/_shared/Lightbox/Lightbox.tsx +++ b/src/components/_shared/Lightbox/Lightbox.tsx @@ -1,8 +1,8 @@ import { clsx } from 'clsx' import { Show, createEffect, createMemo, createSignal, on, onCleanup } from 'solid-js' -import { getImageUrl } from '../../../utils/getImageUrl' -import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler' +import { getImageUrl } from '~/lib/getThumbUrl' +import { useEscKeyDownHandler } from '~/lib/useEscKeyDownHandler' import { Icon } from '../Icon' import styles from './Lightbox.module.scss' diff --git a/src/components/_shared/LoadMoreWrapper.tsx b/src/components/_shared/LoadMoreWrapper.tsx new file mode 100644 index 00000000..9bb7887d --- /dev/null +++ b/src/components/_shared/LoadMoreWrapper.tsx @@ -0,0 +1,67 @@ +import { JSX, Show, createEffect, createSignal, on, onMount } from 'solid-js' +import { Button } from '~/components/_shared/Button' +import { useLocalize } from '~/context/localize' +import { Author, Reaction, Shout } from '~/graphql/schema/core.gen' +import { byCreated } from '~/lib/sort' +import { SortFunction } from '~/types/common' +import { restoreScrollPosition, saveScrollPosition } from '~/utils/scroll' + +export type LoadMoreItems = Shout[] | Author[] | Reaction[] + +type LoadMoreProps = { + loadFunction: (offset: number) => Promise + pageSize: number + hidden?: boolean + children: JSX.Element +} + +export const LoadMoreWrapper = (props: LoadMoreProps) => { + const { t } = useLocalize() + const [items, setItems] = createSignal([]) + const [offset, setOffset] = createSignal(0) + const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) + const [isLoading, setIsLoading] = createSignal(false) + + createEffect( + on(items, (iii) => { + if (Array.isArray(iii)) { + setIsLoadMoreButtonVisible(iii.length - offset() >= 0) + setOffset(iii.length) + } + }) + ) + + const loadItems = async () => { + setIsLoading(true) + saveScrollPosition() + const newItems = await props.loadFunction(offset()) + if (!Array.isArray(newItems)) return + console.debug('[_share] load more items', newItems) + setItems( + (prev) => + Array.from(new Set([...prev, ...newItems])).sort( + byCreated as SortFunction + ) as LoadMoreItems + ) + setIsLoading(false) + restoreScrollPosition() + } + + onMount(loadItems) + + return ( + <> + {props.children} + +
    +
    +
    + + ) +} diff --git a/src/components/Nav/Modal/Modal.module.scss b/src/components/_shared/Modal/Modal.module.scss similarity index 99% rename from src/components/Nav/Modal/Modal.module.scss rename to src/components/_shared/Modal/Modal.module.scss index 97baac02..84291317 100644 --- a/src/components/Nav/Modal/Modal.module.scss +++ b/src/components/_shared/Modal/Modal.module.scss @@ -20,14 +20,18 @@ z-index: 1; &:not([class*='col-']) { - width: 100%; - @include media-breakpoint-up(sm) { width: 80%; } + + width: 100%; } .close { + @include media-breakpoint-up(sm) { + right: 2rem; + } + position: absolute; top: 2rem; cursor: pointer; @@ -39,10 +43,6 @@ transition: opacity 0.3s; z-index: 1; - @include media-breakpoint-up(sm) { - right: 2rem; - } - &:hover { opacity: 0.5; } @@ -59,10 +59,6 @@ } &.narrow { - left: 50%; - transform: translateX(-50%); - width: 100%; - @include media-breakpoint-up(sm) { max-width: 600px; } @@ -71,6 +67,10 @@ width: 65%; } + left: 50%; + transform: translateX(-50%); + width: 100%; + .close { right: 1.6rem; top: 1.6rem; @@ -83,21 +83,20 @@ } .modalInner { + @include media-breakpoint-up(sm) { + padding: 5rem; + } + height: 100%; overflow: auto; padding: 5.4rem 2.4rem 2.4rem; position: relative; text-align: left; - - &::-webkit-scrollbar { - display: none; - } - -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ - @include media-breakpoint-up(sm) { - padding: 5rem; + &::-webkit-scrollbar { + display: none; } } diff --git a/src/components/Nav/Modal/Modal.tsx b/src/components/_shared/Modal/Modal.tsx similarity index 84% rename from src/components/Nav/Modal/Modal.tsx rename to src/components/_shared/Modal/Modal.tsx index e9261522..666d5cfd 100644 --- a/src/components/Nav/Modal/Modal.tsx +++ b/src/components/_shared/Modal/Modal.tsx @@ -1,10 +1,11 @@ +import { redirect } from '@solidjs/router' import { clsx } from 'clsx' import type { JSX } from 'solid-js' import { Show } from 'solid-js' +import { Icon } from '~/components/_shared/Icon' import { useUI } from '~/context/ui' -import { isPortrait } from '~/utils/media-query' -import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler' -import { Icon } from '../../_shared/Icon' +import { isPortrait } from '~/lib/mediaQuery' +import { useEscKeyDownHandler } from '~/lib/useEscKeyDownHandler' import styles from './Modal.module.scss' interface Props { @@ -23,8 +24,11 @@ export const Modal = (props: Props) => { const handleHide = () => { console.debug('[Modal.handleHide]', modal()) - modal() && props.onClose?.() - hideModal() + if (modal()) { + if (props.hideClose) redirect('/') + props.onClose?.() + hideModal() + } } useEscKeyDownHandler(handleHide) diff --git a/src/components/Nav/Modal/Opener.tsx b/src/components/_shared/Modal/Opener.tsx similarity index 70% rename from src/components/Nav/Modal/Opener.tsx rename to src/components/_shared/Modal/Opener.tsx index a51a81f9..fd14a5b7 100644 --- a/src/components/Nav/Modal/Opener.tsx +++ b/src/components/_shared/Modal/Opener.tsx @@ -1,7 +1,7 @@ import type { JSX } from 'solid-js/jsx-runtime' import { type ModalType, useUI } from '~/context/ui' -export default (props: { name: ModalType; children: JSX.Element }) => { +export const Opener = (props: { name: ModalType; children: JSX.Element }) => { const { showModal } = useUI() return ( showModal(props.name)}> @@ -9,3 +9,5 @@ export default (props: { name: ModalType; children: JSX.Element }) => { ) } + +export default Opener diff --git a/src/components/Nav/Modal/index.ts b/src/components/_shared/Modal/index.ts similarity index 100% rename from src/components/Nav/Modal/index.ts rename to src/components/_shared/Modal/index.ts diff --git a/src/components/_shared/Newsletter/Newsletter.module.scss b/src/components/_shared/Newsletter/Newsletter.module.scss index 1160df51..fb8b92f0 100644 --- a/src/components/_shared/Newsletter/Newsletter.module.scss +++ b/src/components/_shared/Newsletter/Newsletter.module.scss @@ -1,23 +1,31 @@ .form { - display: flex; - flex-direction: column; - @include media-breakpoint-down(xxl) { margin-bottom: 2.4rem; } + + display: flex; + flex-direction: column; } .controls { - display: flex; - width: 100%; - @include media-breakpoint-down(xxl) { flex-direction: column; } + display: flex; + width: 100%; + .input { @include font-size(2rem); + @include media-breakpoint-up(xxl) { + border-right: none; + } + + @include media-breakpoint-down(xxl) { + border-bottom: none; + } + background: none; color: #fff; font-family: inherit; @@ -31,26 +39,18 @@ border-radius: 0; height: 4rem; - @include media-breakpoint-up(xxl) { - border-right: none; - } - - @include media-breakpoint-down(xxl) { - border-bottom: none; - } - &::placeholder { color: #858585; } } .button { - border-radius: 0; - flex-shrink: 0; - @include media-breakpoint-down(xxl) { width: 100%; } + + border-radius: 0; + flex-shrink: 0; } } diff --git a/src/components/_shared/Newsletter/Newsletter.tsx b/src/components/_shared/Newsletter/Newsletter.tsx index d4ebcf36..9f52b951 100644 --- a/src/components/_shared/Newsletter/Newsletter.tsx +++ b/src/components/_shared/Newsletter/Newsletter.tsx @@ -1,8 +1,8 @@ import { JSX, Show, createSignal } from 'solid-js' +import { useLocalize } from '~/context/localize' import { useSnackbar } from '~/context/ui' -import { useLocalize } from '../../../context/localize' -import { validateEmail } from '../../../utils/validateEmail' +import { validateEmail } from '~/utils/validate' import { Button } from '../Button' import { Icon } from '../Icon' @@ -44,7 +44,7 @@ export const Newsletter = (props: Props) => { if (!validate()) return - setTitle(t('subscribing...')) + setTitle(t('Subscribing...')) const requestOptions = { method: 'POST', @@ -106,7 +106,7 @@ export const Newsletter = (props: Props) => {
    -
    Подпишитесь на рассылку лучших публикаций
    +
    {t('Subscribe to the best publications newsletter')}
    {emailError()}
    diff --git a/src/components/_shared/PageLayout.tsx b/src/components/_shared/PageLayout.tsx index 7e804f70..b27ae4d4 100644 --- a/src/components/_shared/PageLayout.tsx +++ b/src/components/_shared/PageLayout.tsx @@ -1,20 +1,25 @@ -import type { JSX } from 'solid-js' - -import { Title } from '@solidjs/meta' +import { Meta, Title } from '@solidjs/meta' +import { useLocation } from '@solidjs/router' import { clsx } from 'clsx' -import { Show, createEffect, createSignal } from 'solid-js' - +import type { JSX } from 'solid-js' +import { Show, createEffect, createMemo, createSignal } from 'solid-js' +import { useLocalize } from '~/context/localize' +import { Shout } from '~/graphql/schema/core.gen' +import enKeywords from '~/intl/locales/en/keywords.json' +import ruKeywords from '~/intl/locales/ru/keywords.json' +import { getImageUrl, getOpenGraphImageUrl } from '~/lib/getThumbUrl' +import { descFromBody } from '~/utils/meta' import { FooterView } from '../Discours/Footer' -import { Header } from '../Nav/Header' - -import '../../styles/app.scss' +import { Header } from '../HeaderNav' import styles from './PageLayout.module.scss' -type Props = { +type PageLayoutProps = { title: string + desc?: string + keywords?: string headerTitle?: string slug?: string - articleBody?: string + article?: Shout cover?: string children: JSX.Element isHeaderFixed?: boolean @@ -23,29 +28,53 @@ type Props = { withPadding?: boolean zeroBottomPadding?: boolean scrollToComments?: (value: boolean) => void + key?: string } -export const PageLayout = (props: Props) => { - const isHeaderFixed = props.isHeaderFixed === undefined ? true : props.isHeaderFixed +export const PageLayout = (props: PageLayoutProps) => { + const isHeaderFixed = props.isHeaderFixed === undefined ? true : props.isHeaderFixed // FIXME: выглядит как костылек + const loc = useLocation() + const { t, lang } = useLocalize() + const imageUrl = props.cover ? getImageUrl(props.cover) : 'production/image/logo_image.png' + const ogImage = createMemo(() => + // NOTE: preview generation logic works only for one article view + props.article + ? getOpenGraphImageUrl(imageUrl, { + title: props.title, + topic: props.article?.topics?.[0]?.title || '', + author: props.article?.authors?.[0]?.name || '', + width: 1200 + }) + : imageUrl + ) + const description = createMemo(() => props.desc || (props.article && descFromBody(props.article.body))) + const keypath = createMemo(() => (props.key || loc?.pathname.split('/')[0]) as keyof typeof ruKeywords) + const keywords = createMemo( + () => props.keywords || (lang() === 'ru' ? ruKeywords[keypath()] : enKeywords[keypath()]) + ) const [scrollToComments, setScrollToComments] = createSignal(false) - - createEffect(() => { - if (props.scrollToComments) { - props.scrollToComments(scrollToComments()) - } - }) - + createEffect(() => props.scrollToComments?.(scrollToComments())) return ( <> - {props.title} + {props.article?.title || t(props.title)}
    setScrollToComments(value)} /> + + + + + + + + + +
    { })} classList={{ 'main-content--no-padding': !isHeaderFixed }} > - {props.children} +
    {props.children}
    diff --git a/src/components/_shared/Popup/Popup.module.scss b/src/components/_shared/Popup/Popup.module.scss index 3ae6d95c..3266d84b 100644 --- a/src/components/_shared/Popup/Popup.module.scss +++ b/src/components/_shared/Popup/Popup.module.scss @@ -51,13 +51,13 @@ } &.horizontalAnchorCenter { - right: 0; - @include media-breakpoint-up(md) { left: 50%; right: auto; transform: translateX(-50%); } + + right: 0; } &.horizontalAnchorRight { diff --git a/src/components/_shared/Popup/Popup.tsx b/src/components/_shared/Popup/Popup.tsx index bffe8ca2..0418b4ce 100644 --- a/src/components/_shared/Popup/Popup.tsx +++ b/src/components/_shared/Popup/Popup.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx' import { JSX, Show, createEffect, createSignal } from 'solid-js' -import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler' +import { useOutsideClickHandler } from '~/lib/useOutsideClickHandler' import styles from './Popup.module.scss' diff --git a/src/components/_shared/SearchField/SearchField.tsx b/src/components/_shared/SearchField/SearchField.tsx index 78c6bb81..e2b8ead7 100644 --- a/src/components/_shared/SearchField/SearchField.tsx +++ b/src/components/_shared/SearchField/SearchField.tsx @@ -1,6 +1,6 @@ import { clsx } from 'clsx' -import { useLocalize } from '../../../context/localize' +import { useLocalize } from '~/context/localize' import { Icon } from '../Icon' import styles from './SearchField.module.scss' diff --git a/src/components/_shared/ShareLinks/ShareLinks.tsx b/src/components/_shared/ShareLinks/ShareLinks.tsx index 496cb7b1..2a545eb6 100644 --- a/src/components/_shared/ShareLinks/ShareLinks.tsx +++ b/src/components/_shared/ShareLinks/ShareLinks.tsx @@ -2,8 +2,8 @@ import { FACEBOOK, TELEGRAM, TWITTER, VK, createSocialShare } from '@solid-primi import { clsx } from 'clsx' import { Show, createSignal } from 'solid-js' +import { useLocalize } from '~/context/localize' import { useSnackbar } from '~/context/ui' -import { useLocalize } from '../../../context/localize' import { Icon } from '../Icon' import { Popover } from '../Popover' diff --git a/src/components/_shared/ShareModal/ShareModal.tsx b/src/components/_shared/ShareModal/ShareModal.tsx index 6a59b5cf..c9568688 100644 --- a/src/components/_shared/ShareModal/ShareModal.tsx +++ b/src/components/_shared/ShareModal/ShareModal.tsx @@ -1,6 +1,6 @@ +import { useLocalize } from '~/context/localize' import { useUI } from '~/context/ui' -import { useLocalize } from '../../../context/localize' -import { Modal } from '../../Nav/Modal' +import { Modal } from '../Modal' import { ShareLinks } from '../ShareLinks' type Props = { diff --git a/src/components/_shared/ShowIfAuthenticated.tsx b/src/components/_shared/ShowIfAuthenticated.tsx index 2bab9671..483d55c5 100644 --- a/src/components/_shared/ShowIfAuthenticated.tsx +++ b/src/components/_shared/ShowIfAuthenticated.tsx @@ -2,7 +2,7 @@ import type { JSX } from 'solid-js' import { Show } from 'solid-js' -import { useSession } from '../../context/session' +import { useSession } from '~/context/session' type ShowIfAuthenticatedProps = { children: JSX.Element diff --git a/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx b/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx index ebc29cd9..d35fc313 100644 --- a/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx +++ b/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx @@ -3,7 +3,7 @@ import { For, Show, onMount } from 'solid-js' import SwiperCore from 'swiper' import { Manipulation, Navigation, Pagination } from 'swiper/modules' -import { Shout } from '../../../graphql/schema/core.gen' +import { Shout } from '~/graphql/schema/core.gen' import { ArticleCard } from '../../Feed/ArticleCard' import { Icon } from '../Icon' import { ShowOnlyOnClient } from '../ShowOnlyOnClient' diff --git a/src/components/_shared/SolidSwiper/EditorSwiper.tsx b/src/components/_shared/SolidSwiper/EditorSwiper.tsx index 54f1edba..c65af966 100644 --- a/src/components/_shared/SolidSwiper/EditorSwiper.tsx +++ b/src/components/_shared/SolidSwiper/EditorSwiper.tsx @@ -4,12 +4,12 @@ import { For, Show, createEffect, createSignal, lazy, on, onMount } from 'solid- import SwiperCore from 'swiper' import { Manipulation, Navigation, Pagination } from 'swiper/modules' +import { useLocalize } from '~/context/localize' import { useSnackbar } from '~/context/ui' -import { useLocalize } from '../../../context/localize' -import { composeMediaItems } from '../../../utils/composeMediaItems' -import { getImageUrl } from '../../../utils/getImageUrl' -import { handleImageUpload } from '../../../utils/handleImageUpload' -import { validateFiles } from '../../../utils/validateFile' +import { composeMediaItems } from '~/lib/composeMediaItems' +import { getImageUrl } from '~/lib/getThumbUrl' +import { handleImageUpload } from '~/lib/handleImageUpload' +import { validateUploads } from '~/lib/validateUploads' import { DropArea } from '../DropArea' import { Icon } from '../Icon' import { Image } from '../Image' @@ -18,9 +18,9 @@ import { Popover } from '../Popover' import { SwiperRef } from './swiper' +import { useSession } from '~/context/session' import { MediaItem } from '~/types/mediaitem' import { UploadedFile } from '~/types/upload' -import { useSession } from '../../../context/session' import styles from './Swiper.module.scss' const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) @@ -90,7 +90,7 @@ export const EditorSwiper = (props: Props) => { }) const initUpload = async (selectedFiles: UploadFile[]) => { - const isValid = validateFiles('image', selectedFiles) + const isValid = validateUploads('image', selectedFiles) if (!isValid) { await showSnackbar({ type: 'error', body: t('Invalid file type') }) diff --git a/src/components/_shared/SolidSwiper/ImageSwiper.tsx b/src/components/_shared/SolidSwiper/ImageSwiper.tsx index 06d98bc0..fde4b448 100644 --- a/src/components/_shared/SolidSwiper/ImageSwiper.tsx +++ b/src/components/_shared/SolidSwiper/ImageSwiper.tsx @@ -4,8 +4,8 @@ import SwiperCore from 'swiper' import { HashNavigation, Manipulation, Navigation, Pagination } from 'swiper/modules' import { throttle } from 'throttle-debounce' +import { getImageUrl } from '~/lib/getThumbUrl' import { MediaItem } from '~/types/mediaitem' -import { getImageUrl } from '../../../utils/getImageUrl' import { Icon } from '../Icon' import { Image } from '../Image' import { SwiperRef } from './swiper' diff --git a/src/components/_shared/SolidSwiper/Swiper.module.scss b/src/components/_shared/SolidSwiper/Swiper.module.scss index f66de2fc..647c1bb9 100644 --- a/src/components/_shared/SolidSwiper/Swiper.module.scss +++ b/src/components/_shared/SolidSwiper/Swiper.module.scss @@ -1,11 +1,11 @@ :root { - --slide-height: 300px; - --navigation-reserve: 48px; - @include media-breakpoint-up(md) { --slide-height: 500px; --navigation-reserve: 56px; } + + --slide-height: 300px; + --navigation-reserve: 48px; } .Swiper { @@ -230,16 +230,16 @@ .body { @include font-size(1.7rem); + @include media-breakpoint-up(md) { + // margin-left: calc((100% + 130px) * 0.15); + margin-left: calc(15% + 24px); + } + margin-top: 24px; * { color: var(--default-color-invert) !important; // Force fix migration errors with inline styles } - - @include media-breakpoint-up(md) { - // margin-left: calc((100% + 130px) * 0.15); - margin-left: calc(15% + 24px); - } } } diff --git a/src/components/_shared/SoonChip/SoonChip.tsx b/src/components/_shared/SoonChip/SoonChip.tsx index a0acc2f7..34d5a967 100644 --- a/src/components/_shared/SoonChip/SoonChip.tsx +++ b/src/components/_shared/SoonChip/SoonChip.tsx @@ -1,6 +1,6 @@ import { clsx } from 'clsx' -import { useLocalize } from '../../../context/localize' +import { useLocalize } from '~/context/localize' import { Icon } from '../Icon' import styles from './SoonChip.module.scss' diff --git a/src/components/TableOfContents/TableOfContents.module.scss b/src/components/_shared/TableOfContents/TableOfContents.module.scss similarity index 100% rename from src/components/TableOfContents/TableOfContents.module.scss rename to src/components/_shared/TableOfContents/TableOfContents.module.scss index 8433e44d..0b3543d0 100644 --- a/src/components/TableOfContents/TableOfContents.module.scss +++ b/src/components/_shared/TableOfContents/TableOfContents.module.scss @@ -1,9 +1,4 @@ .TableOfContentsFixedWrapper { - min-height: 100%; - position: relative; - z-index: 1; - top: 0; - @include media-breakpoint-down(xl) { background: #000; bottom: 0; @@ -34,6 +29,11 @@ } } + min-height: 100%; + position: relative; + z-index: 1; + top: 0; + &:not(.TableOfContentsFixedWrapperLefted) { margin-top: -0.2em; @@ -63,14 +63,6 @@ } .TableOfContentsContainer { - top: 100px; - right: 20px; - display: flex; - width: 100%; - overflow: auto; - align-items: flex-start; - background-color: transparent; - @include media-breakpoint-up(xl) { flex-direction: column; height: calc(100vh - 120px); @@ -80,6 +72,14 @@ height: calc(100vh - 250px); } } + + top: 100px; + right: 20px; + display: flex; + width: 100%; + overflow: auto; + align-items: flex-start; + background-color: transparent; } .TableOfContentsContainerInner { @@ -107,16 +107,6 @@ } .TableOfContentsPrimaryButton { - position: absolute; - right: 0; - top: 0; - display: flex; - align-items: center; - justify-content: center; - background: transparent; - border: none; - cursor: pointer; - @include media-breakpoint-down(xl) { background: #fff; border: 1px solid #000; @@ -129,6 +119,16 @@ width: 40px; } + position: absolute; + right: 0; + top: 0; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: none; + cursor: pointer; + &:hover { filter: invert(1); } diff --git a/src/components/TableOfContents/TableOfContents.tsx b/src/components/_shared/TableOfContents/TableOfContents.tsx similarity index 93% rename from src/components/TableOfContents/TableOfContents.tsx rename to src/components/_shared/TableOfContents/TableOfContents.tsx index 3b5482cd..2f27b935 100644 --- a/src/components/TableOfContents/TableOfContents.tsx +++ b/src/components/_shared/TableOfContents/TableOfContents.tsx @@ -2,10 +2,10 @@ import { clsx } from 'clsx' import { For, Show, createEffect, createSignal, on, onCleanup, onMount } from 'solid-js' import { debounce, throttle } from 'throttle-debounce' +import { useLocalize } from '~/context/localize' import { DEFAULT_HEADER_OFFSET } from '~/context/ui' -import { useLocalize } from '../../context/localize' -import { isDesktop } from '../../utils/media-query' -import { Icon } from '../_shared/Icon' +import { isDesktop } from '~/lib/mediaQuery' +import { Icon } from '../Icon' import styles from './TableOfContents.module.scss' @@ -20,14 +20,13 @@ const isInViewport = (el: Element): boolean => { return rect.top <= DEFAULT_HEADER_OFFSET + 24 // default offset + 1.5em (default header margin-top) } const scrollToHeader = (element: HTMLElement) => { - if (window) - window.scrollTo({ - behavior: 'smooth', - top: - element.getBoundingClientRect().top - - document.body.getBoundingClientRect().top - - DEFAULT_HEADER_OFFSET - }) + window?.scrollTo({ + behavior: 'smooth', + top: + element.getBoundingClientRect().top - + document?.body.getBoundingClientRect().top - + DEFAULT_HEADER_OFFSET + }) } export const TableOfContents = (props: Props) => { diff --git a/src/components/TableOfContents/index.tsx b/src/components/_shared/TableOfContents/index.tsx similarity index 100% rename from src/components/TableOfContents/index.tsx rename to src/components/_shared/TableOfContents/index.tsx diff --git a/src/components/_shared/TimeAgo/TimeAgo.tsx b/src/components/_shared/TimeAgo/TimeAgo.tsx index 055b8e39..4f2309b5 100644 --- a/src/components/_shared/TimeAgo/TimeAgo.tsx +++ b/src/components/_shared/TimeAgo/TimeAgo.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx' import { createSignal, onCleanup, onMount } from 'solid-js' -import { useLocalize } from '../../../context/localize' +import { useLocalize } from '~/context/localize' import styles from './TimeAgo.module.scss' diff --git a/src/components/_shared/VideoPlayer/VideoPlayer.tsx b/src/components/_shared/VideoPlayer/VideoPlayer.tsx index a3d9b6c9..eb32837a 100644 --- a/src/components/_shared/VideoPlayer/VideoPlayer.tsx +++ b/src/components/_shared/VideoPlayer/VideoPlayer.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx' import { Match, Show, Switch, createEffect, createSignal } from 'solid-js' -import { useLocalize } from '../../../context/localize' +import { useLocalize } from '~/context/localize' import { Button } from '../Button' import { Icon } from '../Icon' import { Popover } from '../Popover' diff --git a/src/components/_shared/VotersList/VotersList.tsx b/src/components/_shared/VotersList/VotersList.tsx index d409002b..a2887f35 100644 --- a/src/components/_shared/VotersList/VotersList.tsx +++ b/src/components/_shared/VotersList/VotersList.tsx @@ -1,7 +1,7 @@ import { clsx } from 'clsx' import { For, Show } from 'solid-js' -import { Reaction, ReactionKind } from '../../../graphql/schema/core.gen' +import { Reaction, ReactionKind } from '~/graphql/schema/core.gen' import { Userpic } from '../../Author/Userpic' import styles from './VotersList.module.scss' @@ -28,7 +28,7 @@ export const VotersList = (props: Props) => { userpic={reaction.created_by.pic || ''} class={styles.userpic} /> - {reaction.created_by.name || ''} + {reaction.created_by.name || ''}
    {reaction.kind === ReactionKind.Like ? (
    +1
    diff --git a/src/config/config.ts b/src/config.ts similarity index 100% rename from src/config/config.ts rename to src/config.ts diff --git a/src/config/routes.ts b/src/config/routes.ts deleted file mode 100644 index 9b40149f..00000000 --- a/src/config/routes.ts +++ /dev/null @@ -1,38 +0,0 @@ -export const ROUTES = { - home: '/', - inbox: '/inbox', - connect: '/connect', - create: '/create', - edit: '/edit/:shoutId', - editSettings: '/edit/:shoutId/settings', - drafts: '/drafts', - topics: '/topics', - topic: '/topic/:slug', - authors: '/authors', - author: '/author/:slug', - authorComments: '/author/:slug/comments', - authorAbout: '/author/:slug/about', - feed: '/feed', - feedMy: '/feed/my', - feedNotifications: '/feed/notifications', - feedDiscussions: '/feed/discussions', - feedBookmarks: '/feed/bookmarks', - feedCollaborations: '/feed/collaborations', - search: '/search/:q?', - dogma: '/about/dogma', - discussionRules: '/about/discussion-rules', - guide: '/about/guide', - help: '/about/help', - manifest: '/about/manifest', - partners: '/about/partners', - principles: '/about/principles', - projects: '/about/projects', - termsOfUse: '/about/terms-of-use', - thanks: '/about/thanks', - expo: '/expo/:layout?', - profileSettings: '/profile/settings', - profileSecurity: '/profile/security', - profileSubscriptions: '/profile/subscriptions', - fourOuFour: '/404', - article: '/:slug' -} as const diff --git a/src/context/authors.tsx b/src/context/authors.tsx index 0a37c53c..8287b443 100644 --- a/src/context/authors.tsx +++ b/src/context/authors.tsx @@ -8,18 +8,21 @@ import { on, useContext } from 'solid-js' -import loadAuthorByQuery from '../graphql/query/core/author-by' -import loadAuthorsAllQuery from '../graphql/query/core/authors-all' -import loadAuthorsByQuery from '../graphql/query/core/authors-load-by' -import { Author, Maybe, QueryLoad_Authors_ByArgs, Shout, Topic } from '../graphql/schema/core.gen' +import { getAuthor, loadAuthors, loadAuthorsAll } from '~/graphql/api/public' +import { + Author, + Maybe, + QueryGet_AuthorArgs, + QueryLoad_Authors_ByArgs, + Shout, + Topic +} from '~/graphql/schema/core.gen' +import { byStat } from '~/lib/sort' +import { FilterFunction, SortFunction } from '~/types/common' import { useFeed } from './feed' -import { useGraphQL } from './graphql' const TOP_AUTHORS_COUNT = 5 -type FilterFunction = (a: Author) => boolean -export type SortFunction = (a: Author, b: Author) => number - // Универсальная функция фильтрации и сортировки function filterAndSort( items: Author[], @@ -34,11 +37,11 @@ type AuthorsContextType = { authorsSorted: Accessor addAuthors: (authors: Author[]) => void addAuthor: (author: Author) => void - loadAuthor: (slug: string) => Promise + loadAuthor: (args: QueryGet_AuthorArgs) => Promise loadAuthors: (args: QueryLoad_Authors_ByArgs) => Promise topAuthors: Accessor authorsByTopic: Accessor<{ [topicSlug: string]: Author[] }> - setSortBy: (sortfn: SortFunction) => void + setAuthorsSort: (stat: string) => void loadAllAuthors: () => Promise } @@ -51,7 +54,7 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => { const [authorsSorted, setAuthorsSorted] = createSignal([]) const [sortBy, setSortBy] = createSignal>() const { feedByAuthor } = useFeed() - const { query } = useGraphQL() + const setAuthorsSort = (stat: string) => setSortBy(() => byStat(stat) as SortFunction) // Эффект для отслеживания изменений сигнала sortBy и обновления authorsSorted createEffect( @@ -67,6 +70,7 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => { ) const addAuthors = (newAuthors: Author[]) => { + console.debug('[context.authors] storing new authors:', newAuthors) setAuthors((prevAuthors) => { const updatedAuthors = { ...prevAuthors } newAuthors.forEach((author) => { @@ -84,15 +88,26 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => { }) } - const loadAuthor = async (slug: string): Promise => { + const loadAuthor = async (opts: QueryGet_AuthorArgs): Promise => { try { - const resp = await query(loadAuthorByQuery, { slug }).toPromise() - if (resp) { - const author = resp.data.get_author - if (author?.id) addAuthor(author) - } + console.debug('[context.authors] load author', opts) + const fetcher = await getAuthor(opts) + const author = await fetcher() + if (author) addAuthor(author as Author) + console.debug('[context.authors]', author) } catch (error) { - console.error('Error loading author:', error) + console.error('[context.authors] Error loading author:', error) + throw error + } + } + + const loadAuthorsPaginated = async (args: QueryLoad_Authors_ByArgs): Promise => { + try { + const fetcher = await loadAuthors(args) + const data = await fetcher() + if (data) addAuthors(data as Author[]) + } catch (error) { + console.error('Error loading authors:', error) throw error } } @@ -121,19 +136,6 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => { return sortedTopAuthors }) - const loadAuthors = async (args: QueryLoad_Authors_ByArgs): Promise => { - try { - const resp = await query(loadAuthorsByQuery, { ...args }).toPromise() - if (resp) { - const author = resp.data.get_author - if (author?.id) addAuthor(author) - } - } catch (error) { - console.error('Error loading author:', error) - throw error - } - } - const authorsByTopic = createMemo(() => { const articlesByAuthorMap = feedByAuthor?.() || {} const result: { [topicSlug: string]: Author[] } = {} @@ -163,22 +165,24 @@ export const AuthorsProvider = (props: { children: JSX.Element }) => { return result }) - const loadAllAuthors = async (): Promise => { - const resp = await query(loadAuthorsAllQuery, {}).toPromise() - return resp?.data?.get_authors_all || [] + const loadAllAuthors = async () => { + const fetcher = loadAuthorsAll() + const data = await fetcher() + addAuthors(data || []) + return data || [] } const contextValue: AuthorsContextType = { - loadAllAuthors, authorsEntities, authorsSorted, addAuthors, addAuthor, loadAuthor, - loadAuthors, + loadAuthors: loadAuthorsPaginated, // with stat + loadAllAuthors, // without stat topAuthors, authorsByTopic, - setSortBy + setAuthorsSort } return {props.children} diff --git a/src/context/connect.tsx b/src/context/connect.tsx index 3b254e1a..a9f50a9a 100644 --- a/src/context/connect.tsx +++ b/src/context/connect.tsx @@ -1,11 +1,11 @@ import type { Accessor, JSX } from 'solid-js' -import type { Author, Reaction, Shout, Topic } from '../graphql/schema/core.gen' +import type { Author, Reaction, Shout, Topic } from '~/graphql/schema/core.gen' import { EventSource } from 'extended-eventsource' import { createContext, createEffect, createSignal, on, useContext } from 'solid-js' -import { sseUrl } from '../config/config' -import { Chat, Message } from '../graphql/schema/chat.gen' +import { Chat, Message } from '~/graphql/schema/chat.gen' +import { sseUrl } from '../config' import { useSession } from './session' const RECONNECT_TIMES = 2 @@ -73,7 +73,7 @@ export const ConnectProvider = (props: { children: JSX.Element }) => { setConnected(false) if (retried() < RECONNECT_TIMES) { setRetried((r) => r + 1) - } else throw Error('failed') + } else throw new Error('failed') } } catch (error) { console.error('[context.connect] SSE init failed:', error) diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 57cb1bcf..0462db5c 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -1,15 +1,14 @@ -import type { JSX } from 'solid-js' - import { useMatch, useNavigate } from '@solidjs/router' import { Editor } from '@tiptap/core' +import type { JSX } from 'solid-js' import { Accessor, createContext, createSignal, useContext } from 'solid-js' import { SetStoreFunction, createStore } from 'solid-js/store' import { useSnackbar } from '~/context/ui' +import deleteShoutQuery from '~/graphql/mutation/core/article-delete' +import updateShoutQuery from '~/graphql/mutation/core/article-update' +import { Topic, TopicInput } from '~/graphql/schema/core.gen' +import { slugify } from '~/intl/translit' import { useFeed } from '../context/feed' -import deleteShoutQuery from '../graphql/mutation/core/article-delete' -import updateShoutQuery from '../graphql/mutation/core/article-update' -import { Topic, TopicInput } from '../graphql/schema/core.gen' -import { slugify } from '../utils/slugify' import { useGraphQL } from './graphql' import { useLocalize } from './localize' @@ -68,14 +67,14 @@ const topic2topicInput = (topic: Topic): TopicInput => { } const saveDraftToLocalStorage = (formToSave: ShoutForm) => { - localStorage.setItem(`shout-${formToSave.shoutId}`, JSON.stringify(formToSave)) + localStorage?.setItem(`shout-${formToSave.shoutId}`, JSON.stringify(formToSave)) } const getDraftFromLocalStorage = (shoutId: number) => { - return JSON.parse(localStorage.getItem(`shout-${shoutId}`) || '{}') + return JSON.parse(localStorage?.getItem(`shout-${shoutId}`) || '{}') } const removeDraftFromLocalStorage = (shoutId: number) => { - localStorage.removeItem(`shout-${shoutId}`) + localStorage?.removeItem(`shout-${shoutId}`) } export const EditorProvider = (props: { children: JSX.Element }) => { @@ -179,7 +178,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { if (shout?.published_at) { navigate(`/article/${shout.slug}`) } else { - navigate('/drafts') + navigate('/edit') } } catch (error) { console.error('[saveShout]', error) diff --git a/src/context/feed.tsx b/src/context/feed.tsx index 74b7c4f0..7d1617aa 100644 --- a/src/context/feed.tsx +++ b/src/context/feed.tsx @@ -1,10 +1,8 @@ import { createLazyMemo } from '@solid-primitives/memo' import { makePersisted } from '@solid-primitives/storage' -import { Accessor, JSX, createContext, createSignal, useContext } from 'solid-js' -import getShoutBySlug from '~/graphql/query/core/article-load' -import loadShoutsByQuery from '~/graphql/query/core/articles-load-by' -import loadShoutsFeed from '~/graphql/query/core/articles-load-feed' -import loadShoutsSearchQuery from '~/graphql/query/core/articles-load-search' +import { Accessor, JSX, Setter, createContext, createSignal, useContext } from 'solid-js' +import { loadFollowedShouts } from '~/graphql/api/private' +import { loadShoutsSearch as fetchShoutsSearch, getShout, loadShouts } from '~/graphql/api/public' import { Author, LoadShoutsOptions, @@ -12,7 +10,7 @@ import { Shout, Topic } from '~/graphql/schema/core.gen' -import { byStat } from '../utils/sortby' +import { byStat } from '../lib/sort' import { useGraphQL } from './graphql' export const PRERENDERED_ARTICLES_COUNT = 5 @@ -20,8 +18,6 @@ export const PRERENDERED_ARTICLES_COUNT = 5 type FeedContextType = { sortedFeed: Accessor articleEntities: Accessor<{ [articleSlug: string]: Shout }> - topFeed: Accessor - topMonthFeed: Accessor feedByAuthor: Accessor<{ [authorSlug: string]: Shout[] }> feedByTopic: Accessor<{ [topicSlug: string]: Shout[] }> feedByLayout: Accessor<{ [layout: string]: Shout[] }> @@ -35,10 +31,28 @@ type FeedContextType = { options: QueryLoad_Shouts_SearchArgs ) => Promise<{ hasMore: boolean; newShouts: Shout[] }> resetSortedFeed: () => void - loadTopMonthFeed: () => Promise - loadTopFeed: () => Promise seen: Accessor<{ [slug: string]: number }> addSeen: (slug: string) => void + + // all + feed: Accessor + setFeed: Setter + + // featured + featuredFeed: Accessor + setFeaturedFeed: Setter + + // top month + loadTopMonthFeed: () => Promise + topMonthFeed: Accessor + + // top rated + loadTopFeed: () => Promise + topFeed: Accessor + + // expo + expoFeed: Accessor + setExpoFeed: Setter } const FeedContext = createContext({} as FeedContextType) @@ -48,10 +62,12 @@ export const useFeed = () => useContext(FeedContext) export const FeedProvider = (props: { children: JSX.Element }) => { const [sortedFeed, setSortedFeed] = createSignal([]) const [articleEntities, setArticleEntities] = createSignal<{ [articleSlug: string]: Shout }>({}) + const [feed, setFeed] = createSignal([]) + const [featuredFeed, setFeaturedFeed] = createSignal([]) + const [expoFeed, setExpoFeed] = createSignal([]) const [topFeed, setTopFeed] = createSignal([]) const [topMonthFeed, setTopMonthFeed] = createSignal([]) const [feedByLayout, _setFeedByLayout] = createSignal<{ [layout: string]: Shout[] }>({}) - const { query } = useGraphQL() const [seen, setSeen] = makePersisted(createSignal<{ [slug: string]: number }>({}), { name: 'discoursio-seen' }) @@ -133,9 +149,8 @@ export const FeedProvider = (props: { children: JSX.Element }) => { // Load a single shout by slug and update the articleEntities and sortedFeed state const loadShout = async (slug: string): Promise => { - const resp = await query(getShoutBySlug, { slug }).toPromise() - if (!resp) return - const newArticle = resp?.data?.get_shout as Shout + const fetcher = await getShout({ slug }) + const newArticle = (await fetcher()) as Shout addFeed([newArticle]) const newArticleIndex = sortedFeed().findIndex((s) => s.id === newArticle.id) if (newArticleIndex >= 0) { @@ -149,36 +164,26 @@ export const FeedProvider = (props: { children: JSX.Element }) => { const loadShoutsBy = async ( options: LoadShoutsOptions ): Promise<{ hasMore: boolean; newShouts: Shout[] }> => { - const resp = await query(loadShoutsByQuery, { options }).toPromise() - const result = resp?.data?.load_shouts_by || [] + const fetcher = await loadShouts(options) + const result = (await fetcher()) || [] const hasMore = result.length !== options.limit + 1 && result.length !== 0 - - if (hasMore) { - result.splice(-1) - } - + if (hasMore) result.splice(-1) addFeed(result) - return { hasMore, newShouts: result } } - + const client = useGraphQL() // Load the user's feed based on the provided options and update the articleEntities and sortedFeed state const loadMyFeed = async ( options: LoadShoutsOptions ): Promise<{ hasMore: boolean; newShouts: Shout[] }> => { if (!options.limit) options.limit = 0 options.limit += 1 - const resp = await query(loadShoutsFeed, options).toPromise() - const result = resp?.data?.load_shouts_feed + const fetcher = await loadFollowedShouts(client, options) + const result = (await fetcher()) || [] const hasMore = result.length === options.limit + 1 - - if (hasMore) { - resp.data.splice(-1) - } - - addFeed(resp.data || []) - - return { hasMore, newShouts: resp.data.load_shouts_by } + if (hasMore) result.splice(-1) + addFeed(result || []) + return { hasMore, newShouts: result } } // Load shouts based on the search query and update the articleEntities and sortedFeed state @@ -186,16 +191,11 @@ export const FeedProvider = (props: { children: JSX.Element }) => { options: QueryLoad_Shouts_SearchArgs ): Promise<{ hasMore: boolean; newShouts: Shout[] }> => { options.limit = options?.limit || 0 + 1 - const resp = await query(loadShoutsSearchQuery, options).toPromise() - const result = resp?.data?.load_shouts_search || [] + const fetcher = await fetchShoutsSearch(options) + const result = (await fetcher()) || [] const hasMore = result.length === (options?.limit || 0) + 1 - - if (hasMore) { - result.splice(-1) - } - + if (hasMore) result.splice(-1) addFeed(result) - return { hasMore, newShouts: result } } @@ -216,8 +216,8 @@ export const FeedProvider = (props: { children: JSX.Element }) => { order_by: 'likes_stat', limit: 10 } - const resp = await query(loadShoutsByQuery, options).toPromise() - const result = resp?.data?.load_shouts_by || [] + const fetcher = await loadShouts(options) + const result = (await fetcher()) || [] addFeed(result) setTopMonthFeed(result) } @@ -228,8 +228,8 @@ export const FeedProvider = (props: { children: JSX.Element }) => { order_by: 'likes_stat', limit: 10 } - const resp = await query(loadShoutsByQuery, options).toPromise() - const result = resp?.data?.load_shouts_by || [] + const fetcher = await loadShouts(options) + const result = (await fetcher()) || [] addFeed(result) setTopFeed(result) } @@ -255,7 +255,13 @@ export const FeedProvider = (props: { children: JSX.Element }) => { loadTopMonthFeed, loadTopFeed, seen, - addSeen + addSeen, + featuredFeed, + setFeaturedFeed, + expoFeed, + setExpoFeed, + feed, + setFeed }} > {props.children} diff --git a/src/context/following.tsx b/src/context/following.tsx index 273d97da..15512ef1 100644 --- a/src/context/following.tsx +++ b/src/context/following.tsx @@ -12,8 +12,8 @@ import { createStore } from 'solid-js/store' import followMutation from '~/graphql/mutation/core/follow' import unfollowMutation from '~/graphql/mutation/core/unfollow' -import loadAuthorFollowers from '../graphql/query/core/author-followers' -import { Author, Community, FollowingEntity, Topic } from '../graphql/schema/core.gen' +import loadAuthorFollowers from '~/graphql/query/core/author-followers' +import { Author, Community, FollowingEntity, Topic } from '~/graphql/schema/core.gen' import { useGraphQL } from './graphql' import { useSession } from './session' @@ -33,7 +33,21 @@ interface FollowingContextType { unfollow: (what: FollowingEntity, slug: string) => Promise } -const FollowingContext = createContext({} as FollowingContextType) +const defaultFollowing = { + slug: '', + type: 'follow' +} as FollowingData + +const FollowingContext = createContext({ + following: () => defaultFollowing, + followers: () => [], + loading: () => false, + setFollows: (_follows: AuthorFollowsResult) => undefined, + follows: {}, + loadFollows: () => undefined, + follow: (_what: FollowingEntity, _slug: string) => undefined, + unfollow: (_what: FollowingEntity, _slug: string) => undefined +} as unknown as FollowingContextType) export function useFollowing() { return useContext(FollowingContext) @@ -51,8 +65,6 @@ const EMPTY_SUBSCRIPTIONS: AuthorFollowsResult = { communities: [] as Community[] } -const defaultFollowing = { slug: '', type: 'follow' } as FollowingData - export const FollowingProvider = (props: { children: JSX.Element }) => { const [loading, setLoading] = createSignal(false) const [followers, setFollowers] = createSignal([] as Author[]) diff --git a/src/context/graphql.tsx b/src/context/graphql.tsx index 0e32ced4..d40f83dc 100644 --- a/src/context/graphql.tsx +++ b/src/context/graphql.tsx @@ -1,7 +1,7 @@ import { Client, ClientOptions, cacheExchange, createClient, fetchExchange } from '@urql/core' import { createContext, createEffect, createSignal, on, useContext } from 'solid-js' import { JSX } from 'solid-js/jsx-runtime' -import { chatApiUrl, coreApiUrl } from '../config/config' +import { chatApiUrl, coreApiUrl } from '../config' import { useSession } from './session' type GraphQLClientContextType = Record @@ -69,5 +69,5 @@ export const useGraphQL = (url: string = coreApiUrl) => { } } if (!c) c = clients[coreApiUrl] - return { query: c.query, mutation: c.mutation } + return c } diff --git a/src/context/inbox.tsx b/src/context/inbox.tsx index 1122d272..b05817ee 100644 --- a/src/context/inbox.tsx +++ b/src/context/inbox.tsx @@ -1,14 +1,14 @@ import type { Accessor, JSX } from 'solid-js' import { createContext, createSignal, useContext } from 'solid-js' -import { chatApiUrl } from '~/config/config' +import { chatApiUrl } from '~/config' import { useGraphQL } from '~/context/graphql' import createChatMutation from '~/graphql/mutation/chat/chat-create' import createMessageMutation from '~/graphql/mutation/chat/chat-message-create' import loadChatMessagesQuery from '~/graphql/query/chat/chat-messages-load-by' import loadChatsQuery from '~/graphql/query/chat/chats-load' +import type { Chat, Message, MessagesBy, MutationCreate_MessageArgs } from '~/graphql/schema/chat.gen' +import { Author } from '~/graphql/schema/core.gen' import { useAuthors } from '../context/authors' -import type { Chat, Message, MessagesBy, MutationCreate_MessageArgs } from '../graphql/schema/chat.gen' -import { Author } from '../graphql/schema/core.gen' import { SSEMessage, useConnect } from './connect' type InboxContextType = { diff --git a/src/context/localize.tsx b/src/context/localize.tsx index d86499bb..db4f93f0 100644 --- a/src/context/localize.tsx +++ b/src/context/localize.tsx @@ -10,7 +10,8 @@ import { onMount, useContext } from 'solid-js' -import { TimeAgo, type i18n, i18next, i18nextInit } from '~/lib/i18next' +import { TimeAgo, type i18n, i18next, i18nextInit } from '~/intl/i18next' +import { processPrepositions } from '~/intl/prepositions' i18nextInit() @@ -46,7 +47,7 @@ export const LocalizeProvider = (props: { children: JSX.Element }) => { }) createEffect( on(lang, (lng: Language) => { - localStorage.setItem('lng', lng || 'ru') + localStorage?.setItem('lng', lng || 'ru') i18next.changeLanguage(lng || 'ru') }) ) @@ -83,7 +84,13 @@ export const LocalizeProvider = (props: { children: JSX.Element }) => { const formatTimeAgo = (date: Date) => timeAgo().format(date) const value: LocalizeContextType = { - t: i18next.t, + t: ((...args) => { + try { + return i18next.t(...args) + } catch (_) { + return args?.length > 0 ? processPrepositions(args[0] as string) : '' + } + }) as i18n['t'], lang, setLang, formatTime, diff --git a/src/context/notifications.tsx b/src/context/notifications.tsx index f043296d..e989893f 100644 --- a/src/context/notifications.tsx +++ b/src/context/notifications.tsx @@ -9,9 +9,9 @@ import markSeenMutation from '~/graphql/mutation/notifier/mark-seen' import markSeenAfterMutation from '~/graphql/mutation/notifier/mark-seen-after' import markSeenThreadMutation from '~/graphql/mutation/notifier/mark-seen-thread' import getNotifications from '~/graphql/query/notifier/notifications-load' +import { NotificationGroup, QueryLoad_NotificationsArgs } from '~/graphql/schema/core.gen' import { NotificationsPanel } from '../components/NotificationsPanel' import { ShowIfAuthenticated } from '../components/_shared/ShowIfAuthenticated' -import { NotificationGroup, QueryLoad_NotificationsArgs } from '../graphql/schema/core.gen' import { SSEMessage, useConnect } from './connect' import { useGraphQL } from './graphql' import { useSession } from './session' @@ -89,7 +89,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => { if (data.entity === 'reaction' && authorized()) { console.info('[context.notifications] event', data) loadNotificationsGrouped({ - after: after() || Date.now(), + after: after() || now, limit: Math.max(PAGE_SIZE, loadedNotificationsCount()) }) } @@ -108,14 +108,14 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => { const markSeenAll = async () => { if (authorized()) { const _resp = await mutation(markSeenAfterMutation, { after: after() }).toPromise() - await loadNotificationsGrouped({ after: after() || Date.now(), limit: loadedNotificationsCount() }) + await loadNotificationsGrouped({ after: after() || now, limit: loadedNotificationsCount() }) } } const markSeen = async (notification_id: number) => { if (authorized()) { await mutation(markSeenMutation, { notification_id }).toPromise() - await loadNotificationsGrouped({ after: after() || Date.now(), limit: loadedNotificationsCount() }) + await loadNotificationsGrouped({ after: after() || now, limit: loadedNotificationsCount() }) } } diff --git a/src/context/profile.tsx b/src/context/profile.tsx index 12ab5df9..4c5b68b5 100644 --- a/src/context/profile.tsx +++ b/src/context/profile.tsx @@ -1,4 +1,4 @@ -import type { Author, ProfileInput } from '../graphql/schema/core.gen' +import type { Author, ProfileInput } from '~/graphql/schema/core.gen' import { AuthToken } from '@authorizerdev/authorizer-js' import { Accessor, JSX, createContext, createEffect, createSignal, on, useContext } from 'solid-js' @@ -76,16 +76,21 @@ export const ProfileProvider = (props: { children: JSX.Element }) => { } }) + // TODO: validation error for `!` and `@` + const updateFormField = (fieldName: string, value: string, remove?: boolean) => { + let val = value + if (fieldName === 'slug' && value.startsWith('@')) val = value.substring(1) + if (fieldName === 'slug' && value.startsWith('!')) val = value.substring(1) if (fieldName === 'links') { setForm((prev) => { const updatedLinks = remove - ? (prev.links || []).filter((item) => item !== value) - : [...(prev.links || []), value] + ? (prev.links || []).filter((item) => item !== val) + : [...(prev.links || []), val] return { ...prev, links: updatedLinks } }) } else { - setForm((prev) => ({ ...prev, [fieldName]: value })) + setForm((prev) => ({ ...prev, [fieldName]: val })) } } diff --git a/src/context/reactions.tsx b/src/context/reactions.tsx index d40ff8d9..b31515f8 100644 --- a/src/context/reactions.tsx +++ b/src/context/reactions.tsx @@ -2,27 +2,29 @@ import type { JSX } from 'solid-js' import { createContext, onCleanup, useContext } from 'solid-js' import { createStore, reconcile } from 'solid-js/store' +import { loadReactions } from '~/graphql/api/public' import createReactionMutation from '~/graphql/mutation/core/reaction-create' import destroyReactionMutation from '~/graphql/mutation/core/reaction-destroy' import updateReactionMutation from '~/graphql/mutation/core/reaction-update' -import getReactionsByQuery from '~/graphql/query/core/reactions-load-by' import { MutationCreate_ReactionArgs, MutationUpdate_ReactionArgs, QueryLoad_Reactions_ByArgs, Reaction, ReactionKind -} from '../graphql/schema/core.gen' +} from '~/graphql/schema/core.gen' import { useGraphQL } from './graphql' import { useLocalize } from './localize' import { useSnackbar } from './ui' type ReactionsContextType = { reactionEntities: Record + reactionsByShout: Record loadReactionsBy: (args: QueryLoad_Reactions_ByArgs) => Promise createReaction: (reaction: MutationCreate_ReactionArgs) => Promise updateReaction: (reaction: MutationUpdate_ReactionArgs) => Promise deleteReaction: (id: number) => Promise<{ error: string } | null> + addReactions: (rrr: Reaction[]) => void } const ReactionsContext = createContext({} as ReactionsContextType) @@ -33,21 +35,31 @@ export function useReactions() { export const ReactionsProvider = (props: { children: JSX.Element }) => { const [reactionEntities, setReactionEntities] = createStore>({}) + const [reactionsByShout, setReactionsByShout] = createStore>({}) const { t } = useLocalize() const { showSnackbar } = useSnackbar() - const { query, mutation } = useGraphQL() - - const loadReactionsBy = async (opts: QueryLoad_Reactions_ByArgs): Promise => { - const resp = await query(getReactionsByQuery, opts) - const result = resp?.data?.load_reactions_by || [] - const newReactionEntities = result.reduce( + const { mutation } = useGraphQL() + const addReactions = (rrr: Reaction[]) => { + const newReactionsByShout: Record = { ...reactionsByShout } + const newReactionEntities = rrr.reduce( (acc: { [reaction_id: number]: Reaction }, reaction: Reaction) => { acc[reaction.id] = reaction + if (!newReactionsByShout[reaction.shout.slug]) newReactionsByShout[reaction.shout.slug] = [] + newReactionsByShout[reaction.shout.slug].push(reaction) return acc }, - {} + { ...reactionEntities } ) setReactionEntities(newReactionEntities) + setReactionsByShout(newReactionsByShout) + } + + const loadReactionsBy = async (opts: QueryLoad_Reactions_ByArgs): Promise => { + !opts.by && console.warn('reactions provider got wrong opts') + const fetcher = await loadReactions(opts) + const result = (await fetcher()) || [] + console.debug('[context.reactions] loaded', result) + result && addReactions(result) return result } @@ -99,7 +111,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { const updateReaction = async (input: MutationUpdate_ReactionArgs): Promise => { const resp = await mutation(updateReactionMutation, input).toPromise() const result = resp?.data?.update_reaction - if (!result) throw Error('cannot update reaction') + if (!result) throw new Error('cannot update reaction') const { error, reaction } = result if (error) await showSnackbar({ type: 'error', body: t(error) }) if (reaction) setReactionEntities(reaction.id, reaction) @@ -112,10 +124,11 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { loadReactionsBy, createReaction, updateReaction, - deleteReaction + deleteReaction, + addReactions } - const value: ReactionsContextType = { reactionEntities, ...actions } + const value: ReactionsContextType = { reactionEntities, reactionsByShout, ...actions } return {props.children} } diff --git a/src/context/session.tsx b/src/context/session.tsx index 276dc499..c3e8fa87 100644 --- a/src/context/session.tsx +++ b/src/context/session.tsx @@ -25,7 +25,7 @@ import { useContext } from 'solid-js' import { type AuthModalSource, useSnackbar, useUI } from '~/context/ui' -import { authApiUrl } from '../config/config' +import { authApiUrl } from '../config' import { useLocalize } from './localize' const defaultConfig: ConfigType = { @@ -159,7 +159,7 @@ export const SessionProvider = (props: { clearSearchParams() // Set session expiration time in local storage const expires_at = new Date(Date.now() + s.data.expires_in * 1000) - localStorage.setItem('expires_at', `${expires_at.getTime()}`) + localStorage?.setItem('expires_at', `${expires_at.getTime()}`) // Set up session expiration check timer minuteLater = setTimeout(checkSessionIsExpired, 60 * 1000) @@ -191,7 +191,7 @@ export const SessionProvider = (props: { }) const checkSessionIsExpired = () => { - const expires_at_data = localStorage.getItem('expires_at') + const expires_at_data = localStorage?.getItem('expires_at') if (expires_at_data) { const expires_at = Number.parseFloat(expires_at_data) diff --git a/src/context/topics.tsx b/src/context/topics.tsx index e94c6fdf..a577370b 100644 --- a/src/context/topics.tsx +++ b/src/context/topics.tsx @@ -5,15 +5,15 @@ import { createContext, createEffect, createMemo, - createReaction, createSignal, on, + onMount, useContext } from 'solid-js' -import { loadTopics } from '~/lib/api' -import { getRandomTopicsFromArray } from '~/utils/getRandomTopicsFromArray' -import { Topic } from '../graphql/schema/core.gen' -import { byTopicStatDesc } from '../utils/sortby' +import { loadTopics } from '~/graphql/api/public' +import { Topic } from '~/graphql/schema/core.gen' +import { getRandomTopicsFromArray } from '~/lib/getRandomTopicsFromArray' +import { byTopicStatDesc } from '../lib/sort' type TopicsContextType = { topicEntities: Accessor<{ [topicSlug: string]: Topic }> @@ -31,7 +31,8 @@ const TopicsContext = createContext({ topTopics: () => [] as Topic[], setTopicsSort: (_s: string) => undefined, addTopics: (_ttt: Topic[]) => undefined, - loadTopics: async () => [] as Topic[] + loadTopics: async () => [] as Topic[], + randomTopic: () => undefined } as TopicsContextType) export function useTopics() { @@ -44,7 +45,7 @@ const STORE_NAME = 'topics' const CACHE_LIFETIME = 24 * 60 * 60 * 1000 // один день в миллисекундах const setupIndexedDB = async () => { - if (!('indexedDB' in window)) { + if (window && !('indexedDB' in window)) { console.error("This browser doesn't support IndexedDB") return } @@ -105,15 +106,15 @@ const saveTopicsToIndexedDB = async (db: IDBDatabase, topics: Topic[]) => { await tx.done } } -export type TopicSort = 'shouts' | 'followers' | 'authors' | 'title' | '' +export type TopicSort = 'shouts' | 'followers' | 'authors' | 'title' export const TopicsProvider = (props: { children: JSX.Element }) => { const [topicEntities, setTopicEntities] = createSignal<{ [topicSlug: string]: Topic }>({}) const [sortedTopics, setSortedTopics] = createSignal([]) - const [sortAllBy, setSortAllBy] = createSignal('') + const [sortAllBy, setSortAllBy] = createSignal('shouts') createEffect(() => { const topics = Object.values(topicEntities()) - console.debug('[context.topics] effect trig', topics) + // console.debug('[context.topics] effect trig', topics) switch (sortAllBy()) { case 'followers': { topics.sort(byTopicStatDesc('followers')) @@ -162,21 +163,26 @@ export const TopicsProvider = (props: { children: JSX.Element }) => { } }) } - const [db, setDb] = createSignal() + createEffect( + on( + () => window?.indexedDB, + async (_raw) => { + const initialized = await setupIndexedDB() + setDb(initialized) + }, + { defer: true } + ) + ) const loadAllTopics = async () => { const topicsLoader = loadTopics() const ttt = await topicsLoader() + ttt && addTopics(ttt) if (db()) await saveTopicsToIndexedDB(db() as IDBDatabase, ttt as Topic[]) return ttt || [] } - createReaction(async () => { - setDb(await setupIndexedDB()) - console.info('[context.topics] idb loaded') - }) - const [randomTopic, setRandomTopic] = createSignal() createEffect( on( @@ -197,6 +203,20 @@ export const TopicsProvider = (props: { children: JSX.Element }) => { ) ) + const getCachedOrLoadTopics = async () => { + const { topics: stored } = await getTopicsFromIndexedDB(db() as IDBDatabase) + if (stored) { + setSortedTopics(stored) + return stored + } + const loaded = await loadAllTopics() + if (loaded) setSortedTopics(loaded) + return loaded + } + + // preload all topics + onMount(getCachedOrLoadTopics) + const value: TopicsContextType = { setTopicsSort: setSortAllBy, topicEntities, diff --git a/src/graphql/api/private.ts b/src/graphql/api/private.ts new file mode 100644 index 00000000..390740f4 --- /dev/null +++ b/src/graphql/api/private.ts @@ -0,0 +1,73 @@ +import { cache } from '@solidjs/router' +import { Client } from '@urql/core' +import loadShoutsBookmarkedQuery from '~/graphql/query/core/articles-load-bookmarked' +import loadShoutsCoauthoredQuery from '~/graphql/query/core/articles-load-coauthored' +import loadShoutsDiscussedQuery from '~/graphql/query/core/articles-load-discussed' +import loadShoutsFollowedQuery from '~/graphql/query/core/articles-load-followed' +import loadShoutsUnratedQuery from '~/graphql/query/core/articles-load-unrated' + +import { + QueryLoad_Shouts_FollowedArgs, + QueryLoad_Shouts_UnratedArgs, + Shout +} from '~/graphql/schema/core.gen' + +export const loadUnratedShouts = ( + signedClient: Client | undefined, + options: QueryLoad_Shouts_UnratedArgs +) => { + const page = `${options.offset || 0}-${(options?.limit || 0) + (options.offset || 0)}` + return cache(async () => { + const resp = await signedClient?.query(loadShoutsUnratedQuery, { ...options }).toPromise() + const result = resp?.data?.load_shouts_unrated + if (result) return result as Shout[] + }, `shouts-unrated-${page}`) +} + +export const loadFollowedShouts = ( + signedClient: Client | undefined, + options: QueryLoad_Shouts_FollowedArgs +) => { + const page = `${options.offset || 0}-${(options?.limit || 0) + (options.offset || 0)}` + return cache(async () => { + const resp = await signedClient?.query(loadShoutsFollowedQuery, { ...options }).toPromise() + const result = resp?.data?.load_shouts_followed + if (result) return result as Shout[] + }, `shouts-followed-${page}`) +} + +export const loadBookmarkedShouts = ( + signedClient: Client | undefined, + options: QueryLoad_Shouts_FollowedArgs +) => { + const page = `${options.offset || 0}-${(options?.limit || 0) + (options.offset || 0)}` + return cache(async () => { + const resp = await signedClient?.query(loadShoutsBookmarkedQuery, { ...options }).toPromise() + const result = resp?.data?.load_shouts_bookmarked + if (result) return result as Shout[] + }, `shouts-bookmarked-${page}`) +} + +export const loadDiscussedShouts = ( + signedClient: Client | undefined, + options: QueryLoad_Shouts_FollowedArgs +) => { + const page = `${options.offset || 0}-${(options?.limit || 0) + (options.offset || 0)}` + return cache(async () => { + const resp = await signedClient?.query(loadShoutsDiscussedQuery, { ...options }).toPromise() + const result = resp?.data?.load_shouts_discussed + if (result) return result as Shout[] + }, `shouts-discussed-${page}`) +} + +export const loadCoauthoredShouts = ( + signedClient: Client | undefined, + options: QueryLoad_Shouts_FollowedArgs +) => { + const page = `${options.offset || 0}-${(options?.limit || 0) + (options.offset || 0)}` + return cache(async () => { + const resp = await signedClient?.query(loadShoutsCoauthoredQuery, { ...options }).toPromise() + const result = resp?.data?.load_shouts_coauthored + if (result) return result as Shout[] + }, `shouts-coauthored-${page}`) +} diff --git a/src/lib/api.ts b/src/graphql/api/public.ts similarity index 61% rename from src/lib/api.ts rename to src/graphql/api/public.ts index 7efe2cff..02fc58e9 100644 --- a/src/lib/api.ts +++ b/src/graphql/api/public.ts @@ -1,13 +1,18 @@ import { cache } from '@solidjs/router' import { defaultClient } from '~/context/graphql' +import getShoutQuery from '~/graphql/query/core/article-load' import loadShoutsByQuery from '~/graphql/query/core/articles-load-by' import loadShoutsSearchQuery from '~/graphql/query/core/articles-load-search' +import getAuthorQuery from '~/graphql/query/core/author-by' +import loadAuthorsAllQuery from '~/graphql/query/core/authors-all' import loadAuthorsByQuery from '~/graphql/query/core/authors-load-by' import loadReactionsByQuery from '~/graphql/query/core/reactions-load-by' +import loadFollowersByTopicQuery from '~/graphql/query/core/topic-followers' import loadTopicsQuery from '~/graphql/query/core/topics-all' import { Author, LoadShoutsOptions, + QueryGet_AuthorArgs, QueryGet_ShoutArgs, QueryLoad_Authors_ByArgs, QueryLoad_Reactions_ByArgs, @@ -29,11 +34,19 @@ export const loadAuthors = (options: QueryLoad_Authors_ByArgs) => { const filter = new URLSearchParams(options.by as Record) return cache(async () => { const resp = await defaultClient.query(loadAuthorsByQuery, { ...options }).toPromise() - const result = resp?.data?.load_shouts_by + const result = resp?.data?.load_authors_by if (result) return result as Author[] }, `authors-${filter}-${page}`) } +export const loadAuthorsAll = () => { + return cache(async () => { + const resp = await defaultClient.query(loadAuthorsAllQuery, {}).toPromise() + const result = resp?.data?.get_authors_all + if (result) return result as Author[] + }, 'authors-all') +} + export const loadShouts = (options: LoadShoutsOptions) => { const page = `${options.offset || 0}-${options.limit + (options.offset || 0)}` const filter = new URLSearchParams(options.filters as Record) @@ -45,19 +58,27 @@ export const loadShouts = (options: LoadShoutsOptions) => { } export const loadReactions = (options: QueryLoad_Reactions_ByArgs) => { + if (!options.by) { + console.debug(options) + throw new Error('[api] wrong loadReactions call') + } + const kind = options.by?.comment ? 'comments' : options.by?.rating ? 'votes' : 'reactions' + const allorone = options.by?.shout ? `shout-${options.by.shout}` : 'all' const page = `${options.offset || 0}-${(options?.limit || 0) + (options.offset || 0)}` const filter = new URLSearchParams(options.by as Record) + // console.debug(options) return cache(async () => { - const resp = await defaultClient.query(loadReactionsByQuery, { ...options }).toPromise() + const resp = await defaultClient.query(loadReactionsByQuery, options).toPromise() const result = resp?.data?.load_reactions_by if (result) return result as Reaction[] - }, `reactions-${filter}-${page}`) + }, `${allorone}-${kind}-${filter}-${page}`) } export const getShout = (options: QueryGet_ShoutArgs) => { + // console.debug('[lib.api] get shout options', options) return cache( async () => { - const resp = await defaultClient.query(loadReactionsByQuery, { ...options }).toPromise() + const resp = await defaultClient.query(getShoutQuery, { ...options }).toPromise() const result = resp?.data?.get_shout if (result) return result as Shout }, @@ -65,6 +86,17 @@ export const getShout = (options: QueryGet_ShoutArgs) => { ) } +export const getAuthor = (options: QueryGet_AuthorArgs) => { + return cache( + async () => { + const resp = await defaultClient.query(getAuthorQuery, { ...options }).toPromise() + const result = resp?.data?.get_author + if (result) return result as Author + }, + `author-${options?.slug || options?.author_id}` + ) +} + export const loadShoutsSearch = (options: QueryLoad_Shouts_SearchArgs) => { const page = `${options.offset || 0}-${(options?.limit || 0) + (options.offset || 0)}` return cache(async () => { @@ -73,3 +105,12 @@ export const loadShoutsSearch = (options: QueryLoad_Shouts_SearchArgs) => { if (result) return result as Shout[] }, `search-${options.text}-${page}`) } + +export const loadFollowersByTopic = (slug: string) => { + // TODO: paginate topic followers + return cache(async () => { + const resp = await defaultClient.query(loadFollowersByTopicQuery, { slug }).toPromise() + const result = resp?.data?.get_topic_followers + if (result) return result as Author[] + }, `topic-${slug}`) +} diff --git a/src/graphql/query/core/articles-load-bookmarked.ts b/src/graphql/query/core/articles-load-bookmarked.ts new file mode 100644 index 00000000..e0b37082 --- /dev/null +++ b/src/graphql/query/core/articles-load-bookmarked.ts @@ -0,0 +1,44 @@ +import { gql } from '@urql/core' + +export default gql` + query LoadBookmarkedShoutsQuery($limit: Int, $offset: Int) { + load_shouts_bookmarked(limit: $limit, offset: $offset) { + id + title + description + subtitle + slug + layout + cover + cover_caption + main_topic + topics { + id + title + body + slug + stat { + shouts + authors + followers + } + } + authors { + id + name + slug + pic + created_at + bio + } + created_at + published_at + featured_at + stat { + viewed + rating + commented + } + } + } +` diff --git a/src/graphql/query/core/articles-load-coauthored.ts b/src/graphql/query/core/articles-load-coauthored.ts new file mode 100644 index 00000000..bb8dfb61 --- /dev/null +++ b/src/graphql/query/core/articles-load-coauthored.ts @@ -0,0 +1,45 @@ +import { gql } from '@urql/core' + +export default gql` + query LoadCoauthoredShoutsQuery($limit: Int, $offset: Int) { + load_shouts_coauthored(limit: $limit, offset: $offset) { + id + title + # lead + description + subtitle + slug + layout + cover + cover_caption + main_topic + topics { + id + title + body + slug + stat { + shouts + authors + followers + } + } + authors { + id + name + slug + pic + created_at + bio + } + created_at + published_at + featured_at + stat { + viewed + rating + commented + } + } + } +` diff --git a/src/graphql/query/core/articles-load-discussed.ts b/src/graphql/query/core/articles-load-discussed.ts new file mode 100644 index 00000000..c1fbc582 --- /dev/null +++ b/src/graphql/query/core/articles-load-discussed.ts @@ -0,0 +1,44 @@ +import { gql } from '@urql/core' + +export default gql` + query LoadDiscussedShoutsQuery($limit: Int, $offset: Int) { + load_shouts_discussed(limit: $limit, offset: $offset) { + id + title + description + subtitle + slug + layout + cover + cover_caption + main_topic + topics { + id + title + body + slug + stat { + shouts + authors + followers + } + } + authors { + id + name + slug + pic + created_at + bio + } + created_at + published_at + featured_at + stat { + viewed + rating + commented + } + } + } +` diff --git a/src/graphql/query/core/articles-load-followed-by.ts b/src/graphql/query/core/articles-load-followed-by.ts new file mode 100644 index 00000000..9d1b20f6 --- /dev/null +++ b/src/graphql/query/core/articles-load-followed-by.ts @@ -0,0 +1,37 @@ +import { gql } from '@urql/core' + +export default gql` + query ShoutsFollowedByUserQuery($slug: String!, $limit: Int!, $offset: Int!) { + load_shouts_followed_by(slug: String!, limit: Int, offset: Int) { + title + subtitle + layout + slug + cover + main_topic + topics { + title + body + slug + stat { + shouts + authors + followers + } + } + authors { + id + name + slug + pic + } + created_at + published_at + featured_at + stat { + viewed + rating + } + } + } +` diff --git a/src/graphql/query/core/articles-load-followed.ts b/src/graphql/query/core/articles-load-followed.ts index 07a18468..e2fdf8cc 100644 --- a/src/graphql/query/core/articles-load-followed.ts +++ b/src/graphql/query/core/articles-load-followed.ts @@ -1,17 +1,15 @@ import { gql } from '@urql/core' export default gql` - query ShoutsReactedByUserQuery($slug: String!, $limit: Int!, $offset: Int!) { - load_shouts_followed(slug: String!, limit: Int, offset: Int) { + query ShoutsFollowedQuery($limit: Int!, $offset: Int!) { + load_shouts_followed(limit: Int, offset: Int) { title subtitle layout slug cover - # cover_caption main_topic topics { - # id title body slug @@ -32,7 +30,6 @@ export default gql` featured_at stat { viewed - rating } } diff --git a/src/graphql/query/core/authors-all.ts b/src/graphql/query/core/authors-all.ts index cfdc3d93..b039bf6e 100644 --- a/src/graphql/query/core/authors-all.ts +++ b/src/graphql/query/core/authors-all.ts @@ -1,7 +1,7 @@ import { gql } from '@urql/core' export default gql` - query { + query GetAuthorsAllQuery { get_authors_all { id slug diff --git a/src/graphql/query/core/authors-load-by.ts b/src/graphql/query/core/authors-load-by.ts index 87d29db2..0f24300d 100644 --- a/src/graphql/query/core/authors-load-by.ts +++ b/src/graphql/query/core/authors-load-by.ts @@ -1,8 +1,8 @@ import { gql } from '@urql/core' export default gql` - query AuthorsAllQuery($by: AuthorsBy!, $limit: Int, $offset: Int) { - get_authors_nostat(by: $by, limit: $limit, offset: $offset) { + query LoadAuthorsBy($by: AuthorsBy!, $limit: Int, $offset: Int) { + load_authors_by(by: $by, limit: $limit, offset: $offset) { id slug name diff --git a/src/intl/Translatable.tsx b/src/intl/Translatable.tsx new file mode 100644 index 00000000..77d50ae2 --- /dev/null +++ b/src/intl/Translatable.tsx @@ -0,0 +1,80 @@ +import { JSX, createSignal, onMount } from 'solid-js' + +// see: https://github.com/WICG/translation-api + +interface TranslationWrapperProps { + children: string + targetLanguage: string + sourceLanguage?: string +} + +// Define the Translation API types +interface TranslationAPI { + canTranslate: (options: { + sourceLanguage?: string + targetLanguage: string + }) => Promise<'yes' | 'no' | 'limited'> + createTranslator: (options: { + sourceLanguage?: string + targetLanguage: string + }) => Promise +} + +interface Translator { + translate: (text: string) => Promise +} +// Extend the Window interface to include the translation property +declare global { + interface Window { + translation?: TranslationAPI + } +} + +export default function TranslationWrapper(props: TranslationWrapperProps): JSX.Element { + const [translatedText, setTranslatedText] = createSignal(null) + const [isTranslating, setIsTranslating] = createSignal(false) + + onMount(async () => { + if (window && 'translation' in window) { + const translation = (window as Window)?.translation + + const canTranslate = await translation?.canTranslate({ + sourceLanguage: props.sourceLanguage || 'ru', + targetLanguage: props.targetLanguage + }) + + if (translation && canTranslate !== 'no') { + setIsTranslating(true) + + try { + const translator = await translation?.createTranslator({ + sourceLanguage: props.sourceLanguage, + targetLanguage: props.targetLanguage + }) + + const result = await translator?.translate(props.children) + + if (typeof result === 'string') { + setTranslatedText(result) + } else if (result && 'result' in result) { + setTranslatedText(result.result) + } + } catch (error) { + console.error('Translation failed:', error) + } finally { + setIsTranslating(false) + } + } + } + }) + + return ( + <> + {isTranslating() ? ( + {props.children} + ) : ( + translatedText() || props.children + )} + + ) +} diff --git a/src/config/translit.json b/src/intl/abc-translit.json similarity index 100% rename from src/config/translit.json rename to src/intl/abc-translit.json diff --git a/src/lib/i18next.ts b/src/intl/i18next.ts similarity index 75% rename from src/lib/i18next.ts rename to src/intl/i18next.ts index c4249c19..0b3dcc29 100644 --- a/src/lib/i18next.ts +++ b/src/intl/i18next.ts @@ -4,8 +4,8 @@ import ICU from 'i18next-icu' import TimeAgo from 'javascript-time-ago' import enTime from 'javascript-time-ago/locale/en' import ruTime from 'javascript-time-ago/locale/ru' -import en from './locales/en/translation.json' -import ru from './locales/ru/translation.json' +import en from '~/intl/locales/en/translation.json' +import ru from '~/intl/locales/ru/translation.json' TimeAgo.addLocale(enTime) TimeAgo.addLocale(ruTime) @@ -13,27 +13,29 @@ TimeAgo.addLocale(ruTime) export const i18nextInit = async (lng = 'ru') => { if (!i18next.isInitialized) { console.debug('[i18next] initializing...') - // eslint-disable-next-line import/no-named-as-default-member + await i18next .use(HttpApi) .use(ICU) .init({ // debug: true, supportedLngs: ['ru', 'en'], - fallbackLng: lng, + fallbackLng: 'en', lng, load: 'languageOnly', initImmediate: false, resources: { ru: { translation: ru }, en: { translation: en } - } + }, + interpolation: { + escapeValue: false + }, + parseMissingKeyHandler: (key: string) => key }) - // console.debug(i18next) } else if (i18next.language !== lng) { await i18next.changeLanguage(lng) } - - // console.debug(`[i18next] using <${lng}>...`) } + export { TimeAgo, i18next, type i18n } diff --git a/src/intl/locales/en/keywords.json b/src/intl/locales/en/keywords.json new file mode 100644 index 00000000..d77521c2 --- /dev/null +++ b/src/intl/locales/en/keywords.json @@ -0,0 +1,8 @@ +{ + "dogma": "Discours.io, dogma, editorial principles, code of ethics, journalism, community", + "guide": "discours.io, guide, help, how to start, reference, tutorial", + "home": "Discours.io, Discours magazine, Discours, culture, science, art, society, independent journalism, literature, music, cinema, video, photography", + "principles": "Discours.io, communities, values, editorial rules, polyphony, creation", + "terms-of-use": "Discours.io, site rules, terms of use", + "topic": "{topic}, Discours.io, articles, journalism, research" +} diff --git a/src/intl/locales/en/translation.json b/src/intl/locales/en/translation.json new file mode 100644 index 00000000..c713d8f1 --- /dev/null +++ b/src/intl/locales/en/translation.json @@ -0,0 +1,27 @@ +{ + "and some more authors": "{restUsersCount, plural, =0 {} one { and one more user} other { and more {restUsersCount} users}}", + "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 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.
    We are convinced that one voice is good, but many is better. We create the most amazing stories together", + "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", + "Invalid email": "Check if your email is correct", + "Join our maillist": "To receive the best postings, just enter your email", + "Join the global community of authors!": "Join the global community of authors from all over the world!", + "shout": "post", + "some authors": "{count} {count, plural, one {author} other {authors}}", + "some comments": "{count, plural, =0 {{count} comments} one {{count} comment} few {{count} comments} other {{count} comments}}", + "some followers": "{count} {count, plural, one {follower} other {followers}}", + "some followings": "{count, plural, =0 {no subscriptions} one {{count} subscription} other {{count} subscriptions}}", + "Some new comments to your publication": "{commentsCount, plural, one {New comment} other {{commentsCount} comments}} to your publication", + "Some new replies to your comment": "{commentsCount, plural, one {New reply} other {{commentsCount} replays}} to your publication", + "some posts": "{count, plural, =0 {no publications} one {{count} publication} other {{count} publications}}", + "some shouts": "{count} {count, plural, one {post} other {posts}}", + "some views": "{count} {count, plural, one {view} other {views}}", + "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", + "The most interesting publications on the topic": "The most interesting publications on the topic {topicName}", + "To find publications, art, comments, authors and topics of interest to you, just start typing your query": "To find publications, art, comments, authors and topics of interest to you, just start typing your query", + "We couldn't find anything for your request": "We couldn't find anything for your request", + "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.", + "Where": "From", + "Write your colleagues name or email": "Write your colleague's name or email" +} diff --git a/src/intl/locales/ru/keywords.json b/src/intl/locales/ru/keywords.json new file mode 100644 index 00000000..a2c7ca63 --- /dev/null +++ b/src/intl/locales/ru/keywords.json @@ -0,0 +1,8 @@ +{ + "dogma": "discours.io, догма, принципы редактирования, этический кодекс, журналистика, сообщество", + "guide": "discours.io, гид, помощь, как начать, справочник, туториал", + "home": "discours.io, Дискурс журнал, Дискурс, культура, наука, искусство, общество, независимая журналистика, литература, музыка, кино, видео, фотография", + "principles": "discours.io, сообщества, ценности, принципы редактировани, плюрализм мнений, сотворчество", + "terms-of-use": "discours.io, правила сайта, правила, пользовательское соглашение", + "topic": "discours.io, Дискурс, статьи, журналистика, исследование" +} diff --git a/src/lib/locales/ru/translation.json b/src/intl/locales/ru/translation.json similarity index 90% rename from src/lib/locales/ru/translation.json rename to src/intl/locales/ru/translation.json index 36b98a2f..f1bf6452 100644 --- a/src/lib/locales/ru/translation.json +++ b/src/intl/locales/ru/translation.json @@ -1,7 +1,7 @@ { "A guide to horizontal editorial: how an open journal works": "Гид по горизонтальной редакции: как работает открытый журнал", "A short introduction to keep the reader interested": "Добавьте вступление, чтобы заинтересовать читателя", - "About": "О себе", + "About the author": "Об авторе", "About the project": "О проекте", "actions": "действия", "Add": "Добавить", @@ -16,7 +16,6 @@ "Add image": "Добавить изображение", "Add images": "Добавить изображения", "Add intro": "Добавить вступление", - "add link": "добавить ссылку", "Add link": "Добавить ссылку", "Add rule": "Добавить разделитель", "Add signature": "Добавить подпись", @@ -33,7 +32,6 @@ "All authors": "Все авторы", "All posts": "Все публикации", "All posts rating": "Рейтинг всех постов", - "all topics": "все темы", "All topics": "Все темы", "Almost done! Check your email.": "Почти готово! Осталось подтвердить вашу почту.", "and some more authors": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}", @@ -41,14 +39,13 @@ "Are you sure you want to delete this draft?": "Уверены, что хотите удалить этот черновик?", "Are you sure you want to to proceed the action?": "Вы уверены, что хотите продолжить?", "Art": "Искусство", - "article": "статья", + "Article": "Статья", "Artist": "Исполнитель", "Artist...": "Исполнитель...", "Artworks": "Артворки", "Audio": "Аудио", - "author": "автор", "Author": "Автор", - "authors": "авторы", + "author profile was not found": "не удалось найти профиль автора", "Authors": "Авторы", "Autotypograph": "Автотипограф", "Back": "Назад", @@ -57,10 +54,8 @@ "back to menu": "назад в меню", "Be the first to rate": "Оцените первым", "Become an author": "Стать автором", - "bold": "жирный", "Bold": "Жирный", "Bookmarked": "Сохранено", - "bookmarks": "закладки", "Bookmarks": "Закладки", "Bullet list": "Маркированный список", "By alphabet": "По алфавиту", @@ -78,7 +73,6 @@ "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": "Отмена", "Cancel changes": "Отменить изменения", "Change password": "Сменить пароль", @@ -91,7 +85,6 @@ "Co-author": "Соавтор", "Collaborate": "Помочь редактировать", "Collaborators": "Соавторы", - "collections": "коллекции", "Collections": "Коллекции", "Come up with a subtitle for your story": "Придумайте подзаголовок вашей истории", "Come up with a title for your story": "Придумайте заголовок вашей истории", @@ -99,37 +92,35 @@ "Comment": "Комментировать", "Comment successfully deleted": "Комментарий успешно удален", "Commentator": "Комментатор", + "Commented": "Комментируемое", "Commenting": "Комментирование", "Comments": "Комментарии", - "Common feed": "Общая лента", "Communities": "Сообщества", "community": "сообщество", "Community Discussion Rules": "Правила дискуссий в сообществе", - "Community Principles": "Принципы сообщества", "Community values and rules of engagement for the open editorial team": "Ценности сообщества и правила взаимодействия открытой редакции", "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 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 chat": "Создать чат", "Create gallery": "Создать галерею", - "Create Group": "Создать группу", + "Create group": "Создать группу", "Create post": "Создать публикацию", "Create video": "Создать видео", - "create_chat": "Создать чат", - "create_group": "Создать группу", "Crop image": "Кадрировать изображение", "Culture": "Культура", "Current password": "Текущий пароль", @@ -141,12 +132,12 @@ "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": "Дискурс — это интеллектуальная среда, веб-пространство и инструменты, которые позволяют авторам сотрудничать с читателями и объединяться для совместного создания публикаций и медиапроектов.
    Мы убеждены, один голос хорошо, а много — лучше. Самые потрясающиe истории мы создаём вместе.", - "Discours is created with our common effort": "Дискурс существует благодаря нашему общему вкладу", "Discours Manifest": "Манифест Дискурса", "Discours Partners": "Партнеры Дискурса", - "Discours – an open magazine about culture, science and society": "Дискурс – открытый журнал о культуре, науке и обществе", - "Discours_theme": "Тема дискурса", + "Discours theme": "Тема дискурса", "Discussing": "Обсуждаемое", "discussion": "дискурс", "Discussion rules": "Правила дискуссий", @@ -155,7 +146,6 @@ "Dogma": "Догма", "dogma keywords": "Discours.io, догма, редакционные принципы, этический кодекс, журналистика, сообщество", "Draft successfully deleted": "Черновик успешно удален", - "drafts": "черновики", "Drafts": "Черновики", "Drag the image to this area": "Перетащите изображение в эту область", "Each image must be no larger than 5 MB.": "Каждое изображение должно быть размером не больше 5 мб.", @@ -166,7 +156,6 @@ "Editor": "Редактор", "Email": "Почта", "email not confirmed": "email не подтвержден", - "enter": "войти", "Enter": "Войти", "Enter a new password": "Введите новый пароль", "Enter footnote text": "Введите текст сноски", @@ -182,7 +171,6 @@ "FAQ": "Советы и предложения", "Favorite": "Избранное", "Favorite topics": "Избранные темы", - "feed": "лента", "Feed": "Лента", "Feed settings": "Настроить ленту", "Feedback": "Обратная связь", @@ -206,11 +194,8 @@ "Group Chat": "Общий чат", "Groups": "Группы", "Header": "Заголовок", - "header 1": "заголовок 1", "Header 1": "Заголовок 1", - "header 2": "заголовок 2", "Header 2": "Заголовок 2", - "header 3": "заголовок 3", "Header 3": "Заголовок 3", "Headers": "Заголовки", "Help": "Помощь", @@ -235,8 +220,8 @@ "I have no account yet": "У меня еще нет аккаунта", "I know the password": "Я знаю пароль!", "Image format not supported": "Тип изображения не поддерживается", - "images": "изображения", - "In bookmarks, you can save favorite discussions and materials that you want to return to": "В закладках можно сохранять избранные дискуссии и материалы, к которым хочется вернуться", + "Image": "Изображение", + "In bookmarks, you can save favorite discussions and materials that you want to return to": "В закладках можно сохранять избранные дискуссии и материалы, к которым хочется вернуться", "Inbox": "Входящие", "Incorrect new password confirm": "Неверное подтверждение нового пароля", "Incorrect old password": "Старый пароль не верен", @@ -258,13 +243,12 @@ "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": "курсив", "Italic": "Курсив", "Join": "Присоединиться", "Join our maillist": "Чтобы получать рассылку лучших публикаций, просто укажите свою почту", "Join the community": "Присоединиться к сообществу", "Join the global community of authors!": "Присоединятесь к глобальному сообществу авторов со всего мира!", - "journal": "журнал", + "Journal": "Журнал", "jpg, .png, max. 10 mb.": "jpg, .png, макс. 10 мб.", "Just start typing...": "Просто начните печатать...", "Karma": "Карма", @@ -273,14 +257,15 @@ "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": "литература", "Literature": "Литература", "Load more": "Показать ещё", + "loaded": "загружено", "Loading": "Загрузка", "Login and security": "Вход и безопасность", "Logout": "Выход", @@ -292,28 +277,27 @@ "marker list": "маркир. список", "Material card": "Карточка материала", "Message": "Написать", + "Message text": "Текст сообщения", "min. 1400×1400 pix": "мин. 1400×1400 пикс.", "More": "Ещё", - "Most commented": "Комментируемое", "Most read": "Читаемое", "Move down": "Переместить вниз", "Move up": "Переместить вверх", - "music": "музыка", "Music": "Музыка", - "my feed": "моя лента", "My feed": "Моя лента", "My subscriptions": "Подписки", "Name": "Имя", + "New group": "Новая группа", "New literary work": "Новое произведение", + "New message": "Новое сообщение", "New only": "Только новые", "New password": "Новый пароль", - "New stories every day and even more!": "Каждый день вас ждут новые истории и ещё много всего интересного!", + "New stories and more are waiting for you every day!": "Каждый день вас ждут новые истории и ещё много всего интересного!", "Newsletter": "Рассылка", "Night mode": "Ночная тема", "No notifications yet": "Уведомлений пока нет", "No such account, please try to register": "Такой адрес не найден, попробуйте зарегистрироваться", "not verified": "ещё не подтверждён", - "Nothing here yet": "Здесь пока ничего нет", "Nothing is here": "Здесь ничего нет", "Notifications": "Уведомления", "number list": "нумер. список", @@ -321,6 +305,7 @@ "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", @@ -336,17 +321,15 @@ "Passwords are not equal": "Пароли не совпадают", "Paste Embed code": "Вставьте embed код", "Personal": "Личные", - "personal data usage and email notifications": "на обработку персональных данных и на получение почтовых уведомлений", "Pin": "Закрепить", "Platform Guide": "Гид по дискурсу", "Please check your email address": "Пожалуйста, проверьте введенный адрес почты", "Please check your inbox! We have sent a password reset link.": "Пожалуйста, проверьте свою почту, мы отправили вам письмо со ссылкой для сброса пароля", - "Please confirm your email to finish": "Подтвердите почту и действие совершится", + "Please confirm email": "Пожалуйста, подтвердите электронную почту", "Please enter a name to sign your comments and publication": "Пожалуйста, введите имя, которое будет отображаться на сайте", "Please enter email": "Пожалуйста, введите почту", "Please enter password": "Пожалуйста, введите пароль", "Please enter password again": "Пожалуйста, введите пароль ещё рез", - "Please, confirm email": "Пожалуйста, подтвердите электронную почту", "Please, set the article title": "Пожалуйста, задайте заголовок статьи", "Please, set the main topic first": "Пожалуйста, сначала выберите главную тему", "Podcasts": "Подкасты", @@ -355,7 +338,6 @@ "Popular authors": "Популярные авторы", "post": "пост", "Preview": "Предпросмотр", - "Principles": "Принципы сообщества", "principles keywords": "Discours.io, сообщества, ценности, правила редакции, многоголосие, созидание", "Professional principles that the open editorial team follows in its work": "Профессиональные принципы, которым открытая редакция следует в работе", "Profile": "Профиль", @@ -371,14 +353,14 @@ "Quit": "Выйти", "Quote": "Цитата", "Quotes": "Цитаты", - "Reason uknown": "Причина неизвестна", + "Reason unknown": "Причина неизвестна", "Recent": "Свежее", "register": "зарегистрируйтесь", "registered": "уже зарегистрирован", "Registered since {date}": "На сайте c {date}", "Release date...": "Дата выхода...", "Remove link": "Убрать ссылку", - "repeat": "повторить", + "Repeat": "Повторить", "Repeat new password": "Повторите новый пароль", "Reply": "Ответить", "Report": "Пожаловаться", @@ -415,6 +397,7 @@ "Show table of contents": "Показать главление", "sign in": "войти", "sign up": "зарегистрироваться", + "Sign up": "Создать аккаунт", "sign up or sign in": "зарегистрироваться или войти", "Site search": "Поиск по сайту", "Slug": "Постоянная ссылка", @@ -437,7 +420,6 @@ "Soon": "Скоро", "Sorry, this address is already taken, please choose another one.": "Увы, этот адрес уже занят, выберите другой", "Special projects": "Спецпроекты", - "Special Projects": "Спецпроекты", "Specify the source and the name of the author": "Укажите источник и имя автора", "squib": "Подверстка", "Start conversation": "Начать беседу", @@ -446,14 +428,12 @@ "Subscribe": "Подписаться", "Subscribe to comments": "Подписаться на комментарии", "Subscribe to the best publications newsletter": "Подпишитесь на рассылку лучших публикаций", - "Subscribe us": "Подпишитесь на нас", + "Subscribe us": "Подпишитесь на нас", "Subscribe what you like to tune your personal feed": "Подпишитесь на интересующие вас темы, чтобы настроить вашу персональную ленту и моментально узнавать о новых публикациях и обсуждениях", "Subscribe who you like to tune your personal feed": "Подпишитесь на интересующих вас авторов, чтобы настроить вашу персональную ленту и моментально узнавать о новых публикациях и обсуждениях", "subscriber": "подписчик", - "subscriber_rp": "подписчика", "subscribers": "подписчиков", "Subscribing...": "Подписываем...", - "subscribing...": "Подписка...", "Subscription": "Подписка", "Subscriptions": "Подписки", "Substrate": "Подложка", @@ -465,7 +445,6 @@ "Support us": "Помочь журналу", "terms of use": "правилами пользования сайтом", "Terms of use": "Правила сайта", - "terms of use keywords": "Discours.io, правила сайта, terms of use", "Text checking": "Проверка текста", "Thank you": "Благодарности", "Thank you!": "Спасибо Вам!", @@ -486,12 +465,13 @@ "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 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": "Для поиска публикаций, искусства, комментариев, интересных вам авторов и тем, просто начните вводить ваш запрос", + "To find publications, art, comments, authors and topics of interest to you, just start typing your query": "Для поиска публикаций, искусства, комментариев, интересных вам авторов и тем, просто начните вводить ваш запрос", "To leave a comment please": "Чтобы оставить комментарий, необходимо", + "to process personal data and receive email notifications": "на обработку персональных данных и на получение почтовых уведомлений", "To write a comment, you must": "Чтобы написать комментарий, необходимо", "today": "сегодня", "Top authors": "Рейтинг авторов", @@ -504,7 +484,6 @@ "Top viewed": "Самое читаемое", "Topic is supported by": "Тему поддерживают", "topicKeywords": "{topic}, Discours.io, статьи, журналистика, исследования", - "topics": "темы", "Topics": "Темы", "Topics which supported by author": "Автор поддерживает темы", "try": "попробуйте", @@ -512,7 +491,7 @@ "Unfollow": "Отписаться", "Unfollow the topic": "Отписаться от темы", "Unnamed draft": "Черновик без названия", - "Unsubscribing...": "Отписываем...", + "UnSubscribing...": "Отписываем...", "Upload": "Загрузить", "Upload error": "Ошибка загрузки", "Upload userpic": "Загрузить аватар", @@ -524,14 +503,13 @@ "Userpic": "Аватар", "Users": "Пользователи", "verified": "уже подтверждён", - "video": "видео", "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 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": "Добро пожаловать в Дискурс", @@ -545,14 +523,12 @@ "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": "вчера", @@ -560,11 +536,12 @@ "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 ll be able to participate in discussions, rate others' comments and learn about new responses": "Вы сможете участвовать в обсуждениях, оценивать комментарии других и узнавать о новых ответах", "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.": "Ваше имя появится на странице вашего профиля и как ваша подпись в публикациях, комментариях и откликах" } diff --git a/src/intl/prepositions.ts b/src/intl/prepositions.ts new file mode 100644 index 00000000..44823abe --- /dev/null +++ b/src/intl/prepositions.ts @@ -0,0 +1,227 @@ +export const dictionaryBefore = [ + 'а', + 'без', + 'в', + 'во', + 'до', + 'за', + 'из', + 'к', + 'ко', + 'на', + 'над', + 'о', + 'об', + 'от', + 'по', + 'под', + 'при', + 'про', + 'с', + 'со', + 'у', + 'через', + 'близ', + 'вне', + 'для', + 'меж', + 'ради', + 'среди', + 'около', + 'против', + 'между', + 'перед', + 'вокруг', + 'возле', + 'и', + 'но', + 'да', + 'или', + 'либо', + 'ни', + 'как', + 'то', + 'не', + 'что', + 'ведь', + 'вот', + 'вон', + 'именно', + 'лишь', + 'просто', + 'прямо', + 'только', + 'даже', + 'уже', + 'еще', + 'все', + 'однако', + 'хоть', + 'хотя', + 'будто', + 'словно', + 'точно', + 'ровно', + 'так', + 'если', + 'чтобы', + 'вдруг', + 'опять', + 'снова', + 'вновь', + 'тоже', + 'также', + 'почти', + 'едва', + 'чуть', + 'совсем', + 'совершенно', + 'абсолютно', + 'мол', + 'дескать', + 'якобы', + 'авось', + 'небось', + 'никак', + 'неужели', + 'разве', + 'ужели', + 'вряд', + 'пусть', + 'пускай', + 'давай', + 'давайте', + 'нет', + 'конечно', + 'несомненно', + 'безусловно', + 'итак', + 'следовательно', + 'значит', + 'поэтому', + 'потому', + 'вообще', + 'кстати', + 'кроме', + 'впрочем', + 'однако', + 'это', + 'эта', + 'этот', + 'эти', + 'та', + 'тот', + 'те', + 'ну', + 'аж', + 'вплоть', + 'ведь', + 'весьма', + 'крайне', + 'очень', + 'слишком', + 'всего', + 'всего-навсего', + 'лишь', + 'только', + 'исключительно', + 'вроде', + 'типа', + 'наподобие', + 'когда', + 'куда', + 'откуда', + 'зачем', + 'почему', + 'отчего', + 'где', + 'кто', + 'кого', + 'кому', + 'кем', + 'который', + 'которая', + 'которое', + 'которые', + 'чей', + 'чья', + 'чье', + 'чьи', + 'каков', + 'какова', + 'каково', + 'каковы', + 'сколько', + 'насколько', + 'настолько', + 'пока', + 'пока не', + 'едва', + 'едва не', + 'чем', + 'нежели', + 'словно', + 'будто', + 'хоть', + 'хотя', + 'пускай', + 'пусть', + 'раз', + 'раз уж', + 'коли', + 'коль', + 'чуть', + 'чуть ли не', + 'чуть не', + 'a', + 'to', + 'in', + 'into', + 'from', + 'get', + 'of', + 'out', + 'the', + 'is', + 'are', + 'be', + 'at' +] + +export const dictionaryAfter = [ + 'ли', + 'же', + 'бы', + 'б', + 'ж', + 'таки', + 'как', + 'так', + 'эдак', + 'эти', + 'это', + 'эта', + 'этот', + 'те', + 'то', + 'та', + 'тот', + 'а', + 'и', + 'но', + 'да', + 'нет' +] + +export const processPrepositions = (text: string): string => { + const prepositionRegexBefore = new RegExp(`(^|\\s)(${dictionaryBefore.join('|')})(\\s|$)`, 'gmi') + const prepositionRegexAfter = new RegExp(`(\\S+)\\s+(${dictionaryAfter.join('|')})(\\s|$)`, 'gmi') + + return text + .replace(prepositionRegexBefore, (_match, start, word, _end) => { + return `${start}${word} ` + }) + .replace(prepositionRegexAfter, (_match, word, particle, end) => { + return `${word} ${particle}${end}` + }) +} diff --git a/src/utils/translate.ts b/src/intl/translate.ts similarity index 90% rename from src/utils/translate.ts rename to src/intl/translate.ts index aa347612..5501af9d 100644 --- a/src/utils/translate.ts +++ b/src/intl/translate.ts @@ -1,6 +1,6 @@ -import { Author } from '../graphql/schema/core.gen' -import { capitalize } from './capitalize' -import { translit } from './ru2en' +import { Author } from '~/graphql/schema/core.gen' +import { capitalize } from '~/utils/capitalize' +import { translit } from './translit' export const isCyrillic = (s: string): boolean => { const cyrillicRegex = /[\u0400-\u04FF]/ // Range for Cyrillic characters diff --git a/src/utils/ru2en.ts b/src/intl/translit.ts similarity index 58% rename from src/utils/ru2en.ts rename to src/intl/translit.ts index 41b7afe4..ef34487b 100644 --- a/src/utils/ru2en.ts +++ b/src/intl/translit.ts @@ -1,4 +1,4 @@ -import translitConfig from '../config/translit.json' +import translitConfig from './abc-translit.json' const ru2en: { [key: string]: string } = translitConfig @@ -15,3 +15,9 @@ export const translit = (str: string) => { return [...str].map((c) => ru2en[c] || c).join('') } + +export const slugify = (text: string) => { + return translit(text.toLowerCase()) + .replaceAll(' ', '-') + .replaceAll(/[^\da-z]/g, '') +} diff --git a/src/utils/composeMediaItems.ts b/src/lib/composeMediaItems.ts similarity index 68% rename from src/utils/composeMediaItems.ts rename to src/lib/composeMediaItems.ts index 44865cd1..cf49c51e 100644 --- a/src/utils/composeMediaItems.ts +++ b/src/lib/composeMediaItems.ts @@ -1,4 +1,4 @@ -const removeFileExtension = (fileName: string) => { +const removeMediaFileExtension = (fileName: string) => { return fileName.replace(/\.(wav|flac|mp3|aac|jpg|jpeg|png|gif)$/i, '') } @@ -10,7 +10,7 @@ export const composeMediaItems = ( return { url: fileData.url, source: '', - title: fileData.originalFilename ? removeFileExtension(fileData.originalFilename) : '', + title: fileData.originalFilename ? removeMediaFileExtension(fileData.originalFilename) : '', body: '', ...optionalParams } diff --git a/src/utils/dummyFilter.ts b/src/lib/dummyFilter.ts similarity index 87% rename from src/utils/dummyFilter.ts rename to src/lib/dummyFilter.ts index f231c739..e54c5a43 100644 --- a/src/utils/dummyFilter.ts +++ b/src/lib/dummyFilter.ts @@ -1,7 +1,5 @@ -import type { Author, Topic } from '../graphql/schema/core.gen' - -import { isAuthor } from './isAuthor' -import { translit } from './ru2en' +import type { Author, Topic } from '~/graphql/schema/core.gen' +import { translit } from '../intl/translit' const prepareQuery = (searchQuery: string, lang: string) => { const q = searchQuery.toLowerCase() @@ -33,7 +31,7 @@ export const dummyFilter = ( return stringMatches(item?.title || '', q, lang) } - if (isAuthor(item)) { + if ('name' in item) { return stringMatches(item?.name || '', q, lang) || (item.bio && stringMatches(item.bio, q, lang)) } diff --git a/src/utils/getRandomTopicsFromArray.ts b/src/lib/getRandomTopicsFromArray.ts similarity index 86% rename from src/utils/getRandomTopicsFromArray.ts rename to src/lib/getRandomTopicsFromArray.ts index 27389537..8911f253 100644 --- a/src/utils/getRandomTopicsFromArray.ts +++ b/src/lib/getRandomTopicsFromArray.ts @@ -1,5 +1,5 @@ +import { Topic } from '~/graphql/schema/core.gen' import { RANDOM_TOPICS_COUNT } from '../components/Views/Home' -import { Topic } from '../graphql/schema/core.gen' export const getRandomTopicsFromArray = (topics: Topic[], count: number = RANDOM_TOPICS_COUNT): Topic[] => { if (!Array.isArray(topics)) return [] as Topic[] diff --git a/src/utils/getImageUrl.ts b/src/lib/getThumbUrl.ts similarity index 78% rename from src/utils/getImageUrl.ts rename to src/lib/getThumbUrl.ts index 252b9732..d9097722 100644 --- a/src/utils/getImageUrl.ts +++ b/src/lib/getThumbUrl.ts @@ -1,4 +1,4 @@ -import { cdnUrl, thumborUrl } from '../config/config' +import { cdnUrl, thumborUrl } from '../config' const URL_CONFIG = { cdnUrl: cdnUrl, @@ -8,12 +8,6 @@ const URL_CONFIG = { productionFolder: 'production/' } -const AUDIO_EXTENSIONS = new Set(['wav', 'mp3', 'ogg', 'aif', 'flac']) - -const isAudioFile = (filename: string): boolean => { - const extension = filename.split('.').pop()?.toLowerCase() - return AUDIO_EXTENSIONS.has(extension ?? '') -} const getLastSegment = (url: string): string => url.toLowerCase().split('/').pop() || '' const buildSizePart = (width?: number, height?: number, includeSize = true): string => { @@ -31,13 +25,21 @@ export const getImageUrl = ( return src } const filename = getLastSegment(src) - const base = isAudioFile(filename) ? URL_CONFIG.cdnUrl : URL_CONFIG.thumborUrl const suffix = options.noSizeUrlPart ? '' : buildSizePart(options.width, options.height) - const subfolder = isAudioFile(filename) ? URL_CONFIG.audioSubfolder : URL_CONFIG.imageSubfolder + const base = URL_CONFIG.thumborUrl + const subfolder = URL_CONFIG.imageSubfolder return `${base}${suffix}${URL_CONFIG.productionFolder}${subfolder}/${filename}` } +export const getAudioUrl = (src: string) => { + const filename = getLastSegment(src) + const base = URL_CONFIG.cdnUrl + const subfolder = URL_CONFIG.audioSubfolder + const prodfolder = URL_CONFIG.productionFolder + return `${base}${prodfolder}${subfolder}/${filename}` +} + export const getOpenGraphImageUrl = ( src: string, options: { diff --git a/src/utils/handleFileUpload.ts b/src/lib/handleFileUpload.ts similarity index 90% rename from src/utils/handleFileUpload.ts rename to src/lib/handleFileUpload.ts index b7297aef..81622fa7 100644 --- a/src/utils/handleFileUpload.ts +++ b/src/lib/handleFileUpload.ts @@ -1,5 +1,5 @@ import { UploadFile } from '@solid-primitives/upload' -import { coreApiUrl } from '../config/config' +import { coreApiUrl } from '../config' const apiUrl = `${coreApiUrl}/upload` diff --git a/src/utils/handleImageUpload.ts b/src/lib/handleImageUpload.ts similarity index 96% rename from src/utils/handleImageUpload.ts rename to src/lib/handleImageUpload.ts index 1791fc05..5eef3717 100644 --- a/src/utils/handleImageUpload.ts +++ b/src/lib/handleImageUpload.ts @@ -1,5 +1,5 @@ import { UploadFile } from '@solid-primitives/upload' -import { thumborUrl } from '../config/config' +import { thumborUrl } from '../config' export const handleImageUpload = async (uploadFile: UploadFile, token: string) => { const formData = new FormData() diff --git a/src/lib/locales/en/translation.json b/src/lib/locales/en/translation.json deleted file mode 100644 index 22eec2e4..00000000 --- a/src/lib/locales/en/translation.json +++ /dev/null @@ -1,543 +0,0 @@ -{ - "A guide to horizontal editorial: how an open journal works": "A guide to horizontal editorial: how an open journal works", - "About": "About", - "About the project": "About the project", - "actions": "actions", - "Add": "Add", - "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 link": "add link", - "Add rule": "Add rule", - "Add signature": "Add signature", - "Add subtitle": "Add subtitle", - "Add url": "Add url", - "Address on Discours": "Address on Discours", - "Album name": "Название aльбома", - "Alignment center": "Alignment center", - "Alignment left": "Alignment left", - "Alignment right": "Alignment right", - "All": "All", - "All articles": "All articles", - "All authors": "All authors", - "All posts": "All posts", - "All topics": "All topics", - "all topics": "all topics", - "Almost done! Check your email.": "Almost done! Just checking your email.", - "and some more authors": "{restUsersCount, plural, =0 {} one { and one more user} other { and more {restUsersCount} users}}", - "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", - "article": "article", - "Artist": "Artist", - "Artworks": "Artworks", - "Audio": "Audio", - "Author": "Author", - "author": "author", - "Authors": "Authors", - "authors": "authors", - "Autotypograph": "Autotypograph", - "Back": "Back", - "Back to editor": "Back to editor", - "Back to main page": "Back to main page", - "back to menu": "back to menu", - "Be the first to rate": "Be the first to rate", - "Become an author": "Become an author", - "Bold": "Bold", - "bold": "bold", - "Bookmarked": "Saved", - "Bookmarks": "Bookmarks", - "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", - "Can make any changes, accept or reject suggestions, and share access with others": "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 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": "Can write and edit text directly, and accept or reject suggestions from others", - "Cancel": "Cancel", - "cancel": "cancel", - "Cancel changes": "Cancel changes", - "Change password": "Change password", - "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", - "Close": "Close", - "Co-author": "Co-author", - "Collaborate": "Help Edit", - "Collaborators": "Collaborators", - "Collections": "Collections", - "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", - "Coming soon": "Coming soon", - "Comment successfully deleted": "Comment successfully deleted", - "Commentator": "Commentator", - "Commenting": "Commenting", - "Comments": "Comments", - "Common feed": "All", - "Communities": "Communities", - "community": "community", - "Community Discussion Rules": "Community Discussion Rules", - "Community Principles": "Community Principles", - "Community values and rules of engagement for the open editorial team": "Community values and rules of engagement for the open editorial team", - "Confirm": "Confirm", - "Confirm your new password": "Confirm your new password", - "Connect": "Connect", - "Contents": "Contents", - "Contribute to free samizdat. Support Discours - an independent non-profit publication that works only for you. Become a pillar of the open newsroom": "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", - "Copy": "Copy", - "Copy link": "Copy link", - "Corrections history": "Corrections history", - "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 Chat": "Create Chat", - "Create gallery": "Create gallery", - "Create Group": "Create a group", - "Create post": "Create post", - "Create video": "Create video", - "Crop image": "Crop image", - "Culture": "Culture", - "Current password": "Current password", - "Date of Birth": "Date of Birth", - "Decline": "Decline", - "Delete": "Delete", - "Delete cover": "Delete cover", - "Delete userpic": "Delete userpic", - "delimiter": "delimiter", - "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 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.
    We are convinced that one voice is good, but many is better. We create the most amazing stories together", - "Discours is created with our common effort": "Discours exists because of our common effort", - "Discours Manifest": "Discours Manifest", - "Discours Partners": "Discours Partners", - "Discours – an open magazine about culture, science and society": "Discours – an open magazine about culture, science and society", - "Discussing": "Discussing", - "discussion": "Discours", - "Discussion rules": "Discussion rules", - "Discussions": "Discussions", - "Do you really want to reset all changes?": "Do you really want to reset all changes?", - "Dogma": "Dogma", - "dogma keywords": "Discours.io, dogma, editorial principles, code of ethics, journalism, community", - "Draft successfully deleted": "Draft successfully deleted", - "Drafts": "Drafts", - "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.", - "earlier": "earlier", - "Edit": "Edit", - "Edit profile": "Edit profile", - "Editing": "Editing", - "Editor": "Editor", - "Email": "Mail", - "email not confirmed": "email not confirmed", - "Enter": "Enter", - "enter": "enter", - "Enter a new password": "Enter a new password", - "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 URL address": "Enter URL address", - "Enter your new password": "Enter your new password", - "Error": "Error", - "Experience": "Experience", - "Failed to delete comment": "Failed to delete comment", - "FAQ": "Tips and suggestions", - "Favorite": "Favorites", - "Favorite topics": "Favorite topics", - "Feed": "Feed", - "feed": "feed", - "Feed settings": "Feed settings", - "Feedback": "Feedback", - "Fill email": "Fill email", - "Fixed": "Fixed", - "Follow": "Follow", - "Follow the topic": "Follow the topic", - "follower": "follower", - "Followers": "Followers", - "Following": "Following", - "Forgot password?": "Forgot password?", - "Forward": "Forward", - "from": "from", - "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 1": "header 1", - "Header 2": "Header 2", - "header 2": "header 2", - "Header 3": "Header 3", - "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 Discours subscriptions": "Here you can manage all your Discours subscriptions", - "Here you can upload your photo": "Here you can upload your photo", - "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 Discours works": "How Discours works", - "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", - "Hundreds of people from different countries and cities share their knowledge and art on the Discours. Join us!": "Hundreds of people from different countries and cities share their knowledge and art on the Discours. Join us!", - "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", - "images": "images", - "In bookmarks, you can save favorite discussions and materials that you want to return to": "In bookmarks, you can save favorite discussions and materials that you want to return to", - "Inbox": "Inbox", - "Incorrect new password confirm": "Incorrect new password confirm", - "Incorrect old password": "Incorrect old password", - "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", - "Independent media project about culture, science, art and society with horizontal editing": "Independent media project about culture, science, art and society with horizontal editing", - "Insert footnote": "Insert footnote", - "Insert video link": "Insert video link", - "Interview": "Interview", - "Introduce": "Introduction", - "Invalid email": "Check if your email is correct", - "Invalid image URL": "Invalid image URL", - "invalid password": "invalid password", - "Invalid url format": "Invalid url format", - "Invite": "Invite", - "Invite co-authors": "Invite co-authors", - "Invite collaborators": "Invite collaborators", - "Invite to collab": "Invite to Collab", - "It does not look like url": "It doesn't look like a link", - "It's OK. Just enter your email to receive a link to change your password": "It's OK. Just enter your email to receive a link to change your password", - "Italic": "Italic", - "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!", - "journal": "journal", - "jpg, .png, max. 10 mb.": "jpg, .png, макс. 10 мб.", - "Just start typing...": "Just start typing...", - "keywords": "Discours.io, Discours magazine, Discours, culture, science, art, society, independent journalism, literature, music, cinema, video, photography", - "Knowledge base": "Knowledge base", - "Language": "Language", - "Last rev.": "Посл. изм.", - "Let's log in": "Let's log in", - "Link copied": "Link copied", - "Link copied to clipboard": "Link copied to clipboard", - "Link sent, check your email": "Link sent, check your email", - "List of authors of the open editorial community": "List of authors of the open editorial community", - "Lists": "Lists", - "Literature": "Literature", - "literature": "literature", - "Load more": "Show more", - "Loading": "Loading", - "Login and security": "Login and security", - "Logout": "Logout", - "Looks like you forgot to upload the video": "Looks like you forgot to upload the video", - "Manifest of samizdat: principles and mission of an open magazine with a horizontal editorial board": "Manifest of samizdat: principles and mission of an open magazine with a horizontal editorial board", - "Manifesto": "Manifesto", - "Many files, choose only one": "Many files, choose only one", - "Mark as read": "Mark as read", - "marker list": "marker list", - "Material card": "Material card", - "Message": "Message", - "min. 1400×1400 pix": "мин. 1400×1400 пикс.", - "More": "More", - "Most commented": "Commented", - "Most read": "Readable", - "Move down": "Move down", - "Move up": "Move up", - "Music": "Music", - "music": "music", - "My feed": "My feed", - "my feed": "my ribbon", - "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!", - "Newsletter": "Newsletter", - "Night mode": "Night mode", - "No notifications yet": "No notifications yet", - "not verified": "not verified", - "Nothing here yet": "There's nothing here yet", - "Nothing is here": "There is nothing here", - "Notifications": "Notifications", - "number list": "number list", - "Or paste a link to an image": "Or paste a link to an image", - "or sign in with social networks": "or sign in with social networks", - "Ordered list": "Ordered list", - "Our regular contributor": "Our regular contributor", - "Paragraphs": "Абзацев", - "Participate in the Discours: share information, join the editorial team": "Участвуйте в Дискурсе: делитесь информацией, присоединяйтесь к редакции", - "Participating": "Participating", - "Participation": "Participation", - "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: !@#$%^&*", - "Password updated!": "Password updated!", - "Passwords are not equal": "Passwords are not equal", - "Paste Embed code": "Paste Embed code", - "Personal": "Personal", - "personal data usage and email notifications": "to process personal data and receive email notifications", - "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", - "Please, set the article title": "Please, set the article title", - "Please, set the main topic first": "Please, set the main topic first", - "Podcasts": "Podcasts", - "Poetry": "Poetry", - "Popular": "Popular", - "Popular authors": "Popular authors", - "post": "post", - "Principles": "Community principles", - "principles keywords": "Discours.io, communities, values, editorial rules, polyphony, creation", - "Professional principles that the open editorial team follows in its work": "Professional principles that the open editorial team follows in its work", - "Profile": "Profile", - "Profile settings": "Profile settings", - "Publications": "Publications", - "Publish Album": "Publish Album", - "Publish Settings": "Publish Settings", - "Published": "Published", - "Punchline": "Punchline", - "Quit": "Quit", - "Quote": "Quote", - "Quotes": "Quotes", - "Reason uknown": "Reason unknown", - "Recent": "Fresh", - "register": "register", - "registered": "registered", - "Registered since {date}": "Registered since {date}", - "Remove link": "Remove link", - "repeat": "repeat", - "Repeat new password": "Repeat new password", - "Reply": "Reply", - "Report": "Complain", - "Reports": "Reports", - "Required": "Required", - "Resend code": "Send confirmation", - "resend confirmation link": "resend confirmation link", - "Restore password": "Restore password", - "Rules of the journal Discours": "Rules of the journal Discours", - "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", - "Self-publishing exists thanks to the help of wonderful people from all over the world. Thank you!": "Samizdat exists thanks to the help of wonderful people from all over the world. Thank you!", - "Send": "Send", - "Send link again": "Send link again", - "Settings": "Settings", - "Settings for account, email, password and login methods.": "Settings for account, email, password and login methods.", - "Share": "Share", - "Share publication": "Share publication", - "shout": "post", - "Show": "Show", - "Show lyrics": "Show lyrics", - "Show more": "Show more", - "Show table of contents": "Show table of contents", - "sign up or sign in": "sign up or sign in", - "Site search": "Site search", - "Slug": "Slug", - "slug is used by another user": "Slug is already taken by another user", - "Social networks": "Social networks", - "Society": "Society", - "some authors": "{count} {count, plural, one {author} other {authors}}", - "some comments": "{count, plural, =0 {{count} comments} one {{count} comment} few {{count} comments} other {{count} comments}}", - "some followers": "{count} {count, plural, one {follower} other {followers}}", - "some followings": "{count, plural, =0 {no subscriptions} one {{count} subscription} other {{count} subscriptions}}", - "Some new comments to your publication": "{commentsCount, plural, one {New comment} other {{commentsCount} comments}} to your publication", - "Some new replies to your comment": "{commentsCount, plural, one {New reply} other {{commentsCount} replays}} to your publication", - "some posts": "{count, plural, =0 {no publications} one {{count} publication} other {{count} publications}}", - "some shouts": "{count} {count, plural, one {post} other {posts}}", - "some views": "{count} {count, plural, one {view} other {views}}", - "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", - "Soon": "Скоро", - "Sorry, this address is already taken, please choose another one.": "Sorry, this address is already taken, please choose another one", - "Special Projects": "Special Projects", - "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", - "Start dialog": "Start dialog", - "Subsccriptions": "Subscriptions", - "Subscribe": "Subscribe", - "Subscribe to the best publications newsletter": "Subscribe to the best publications newsletter", - "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", - "subscriber": "subscriber", - "subscriber_rp": "subscriber", - "subscribers": "subscribers", - "subscribing...": "subscribing...", - "Subscribing...": "Subscribing...", - "Subscription": "Subscription", - "subscription": "subscription", - "subscription_rp": "subscription", - "Subscriptions": "Subscriptions", - "subscriptions": "subscriptions", - "Substrate": "Substrate", - "Success": "Success", - "Successfully authorized": "Authorization successful", - "Suggest an idea": "Suggest an idea", - "Support Discours": "Support Discours", - "Support the project": "Support the project", - "Support us": "Support us", - "Terms of use": "Site rules", - "terms of use": "terms of use", - "terms of use keywords": "Discours.io, site rules, terms of use", - "Text checking": "Text checking", - "Thank you": "Thank you", - "Thank you!": "Thank you!", - "The address is already taken": "The address is already taken", - "The most interesting publications on the topic": "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 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": "Thematic table of contents of the magazine. Here you can find all the topics that the community authors wrote about", - "Themes and plots": "Themes and plots", - "Theory": "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 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 content is not published yet": "This content is not published yet", - "This email is": "This email is", - "This email is not verified": "This email is not verified", - "This email is registered": "This email is registered", - "This email is verified": "This email is verified", - "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 month": "This month", - "This post has not been rated yet": "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 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 way you ll be able to subscribe to authors, interesting topics and customize your feed", - "This week": "This week", - "This year": "This year", - "To find publications, art, comments, authors and topics of interest to you, just start typing your query": "To find publications, art, comments, authors and topics of interest to you, just start typing your query", - "To leave a comment please": "To leave a comment please", - "To write a comment, you must": "To write a comment, you must", - "today": "today", - "Top authors": "Authors rating", - "Top commented": "Most commented", - "Top discussed": "Top discussed", - "Top month": "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", - "topicKeywords": "{topic}, Discours.io, articles, journalism, research", - "Topics": "Topics", - "topics": "topics", - "Topics which supported by author": "Topics which supported by author", - "try": "попробуйте", - "Try to find another way": "Try to find another way", - "Unfollow": "Unfollow", - "Unfollow the topic": "Unfollow the topic", - "Unnamed draft": "Unnamed draft", - "Unsubscribing...": "Unsubscribing...", - "Upload": "Upload", - "Upload error": "Upload error", - "Upload userpic": "Upload userpic", - "Upload video": "Upload video", - "Uploading image": "Uploading image", - "user already exist": "user already exists", - "User was not found": "User was not found", - "Username": "Username", - "Userpic": "Userpic", - "Users": "Users", - "verified": "verified", - "Video": "Video", - "video": "video", - "Video format not supported": "Video format not supported", - "view": "view", - "Views": "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 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 can't find you, check email or", - "We couldn't find anything for your request": "We couldn’t find anything for your request", - "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", - "Why you can earn a hole in your karma and how to receive rays of gratitude for your contribution to discussions in samizdat communities": "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": "Cooperate with Discours", - "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 good articles, comment\nand it won't be so empty here": "Write good articles, comment\nand it won't be so empty here", - "Write message": "Write a message", - "Write to us": "Write to us", - "Write your colleagues name or email": "Write your colleague's name or email", - "yesterday": "yesterday", - "You can": "You can", - "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 can now login using your new password": "Теперь вы можете входить с помощью нового пароля", - "You can't edit this post": "You can't edit this post", - "You were successfully authorized": "You were successfully authorized", - "You ll be able to participate in discussions, rate others' comments and learn about new responses": "You ll be able to participate in discussions, rate others' comments and learn about 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" -} diff --git a/src/utils/media-query.ts b/src/lib/mediaQuery.ts similarity index 100% rename from src/utils/media-query.ts rename to src/lib/mediaQuery.ts diff --git a/src/utils/profileSocialLinks.ts b/src/lib/profileSocialLinks.ts similarity index 100% rename from src/utils/profileSocialLinks.ts rename to src/lib/profileSocialLinks.ts diff --git a/src/utils/sortby.ts b/src/lib/sort.ts similarity index 64% rename from src/utils/sortby.ts rename to src/lib/sort.ts index 61ef9676..8c34e79d 100644 --- a/src/utils/sortby.ts +++ b/src/lib/sort.ts @@ -1,11 +1,12 @@ -import type { Author, Maybe, Reaction, Shout, Topic, TopicStat } from '../graphql/schema/core.gen' +import type { Author, Maybe, Reaction, Shout, Topic, TopicStat } from '~/graphql/schema/core.gen' -// biome-ignore lint/suspicious/noExplicitAny: sort by first char -export const byFirstChar = (a: { name?: any; title?: any }, b: { name?: any; title?: any }) => - (a.name || a.title || '').localeCompare(b.name || b.title || '') +export const byFirstChar = (a: Author | Topic, b: Author | Topic) => + ((a as Author).name || (a as Topic).title || '').localeCompare( + (b as Author).name || (b as Topic).title || '' + ) -export const byCreated = (a: Shout | Reaction, b: Shout | Reaction) => { - return a?.created_at - b?.created_at +export const byCreated = (a: { created_at?: number }, b: { created_at?: number }) => { + return (a?.created_at || 0) - (b?.created_at || 0) } export const byPublished = (a: Shout, b: Shout) => { @@ -29,6 +30,7 @@ export const byLength = ( export type SomeStat = { [x: string]: Maybe } | undefined | null export const byStat = (metric: string) => { + if (metric === 'name' || metric === 'title') return byFirstChar return (a: { stat?: SomeStat }, b: { stat?: SomeStat }) => { const aStat = a.stat?.[metric] ?? 0 const bStat = b.stat?.[metric] ?? 0 @@ -55,10 +57,3 @@ export const byScore = () => { return 0 } } -// biome-ignore lint/suspicious/noExplicitAny: sort -export const sortBy = (data: any, metric: string | ((a: any, b: any) => number) | undefined) => { - const x = [...data] - // @ts-ignore - x.sort(typeof metric === 'function' ? metric : byStat(metric)) - return x -} diff --git a/src/utils/useEscKeyDownHandler.ts b/src/lib/useEscKeyDownHandler.ts similarity index 100% rename from src/utils/useEscKeyDownHandler.ts rename to src/lib/useEscKeyDownHandler.ts diff --git a/src/utils/useOutsideClickHandler.ts b/src/lib/useOutsideClickHandler.ts similarity index 100% rename from src/utils/useOutsideClickHandler.ts rename to src/lib/useOutsideClickHandler.ts diff --git a/src/utils/validateFile.ts b/src/lib/validateUploads.ts similarity index 63% rename from src/utils/validateFile.ts rename to src/lib/validateUploads.ts index 8ba40887..1da62af5 100644 --- a/src/utils/validateFile.ts +++ b/src/lib/validateUploads.ts @@ -1,9 +1,15 @@ import { UploadFile } from '@solid-primitives/upload' -export const validateFiles = (fileType: string, files: UploadFile[]): boolean => { - const imageExtensions = new Set(['jpg', 'jpeg', 'png', 'gif', 'bmp']) - const docExtensions = new Set(['doc', 'docx', 'pdf', 'txt']) +export const imageExtensions = new Set(['jpg', 'jpeg', 'png', 'gif', 'bmp']) +export const docExtensions = new Set(['doc', 'docx', 'pdf', 'txt']) +export const audioExtensions = new Set(['wav', 'mp3', 'ogg', 'aif', 'flac']) +export const isAudioFilename = (filename: string): boolean => { + const extension = filename.split('.').pop()?.toLowerCase() + return audioExtensions.has(extension || '') +} + +export const validateUploads = (fileType: string, files: UploadFile[]): boolean => { for (const file of files) { let isValid: boolean diff --git a/src/pages/about/discussionRules.page.route.ts b/src/pages/about/discussionRules.page.route.ts deleted file mode 100644 index f8907219..00000000 --- a/src/pages/about/discussionRules.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../../stores/router' -import { getServerRoute } from '../../utils/getServerRoute' - -export default getServerRoute(ROUTES.discussionRules) diff --git a/src/pages/about/discussionRules.page.tsx b/src/pages/about/discussionRules.page.tsx deleted file mode 100644 index 25f799f1..00000000 --- a/src/pages/about/discussionRules.page.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { Meta } from '../../context/meta' - -import { StaticPage } from '../../components/Views/StaticPage' -import { useLocalize } from '../../context/localize' -import { getImageUrl } from '../../utils/getImageUrl' - -export const DiscussionRulesPage = () => { - const { t } = useLocalize() - - const ogImage = getImageUrl('production/image/logo_image.png') - const ogTitle = t('Community Discussion Rules') - const description = t( - 'Why you can earn a hole in your karma and how to receive rays of gratitude for your contribution to discussions in samizdat communities' - ) - - return ( - - - - - - - - - - - - -

    {ogTitle}

    - -

    - Открытая редакция существует благодаря дружному сообществу авторов и читателей — - вдумчивых и сознательных людей, приверженных ценностям гуманизма, демократии и прав - человека. Мы очень ценим атмосферу осмысленного общения, которая здесь сложилась. Чтобы - сохранить ее такой же уютной и творческой, мы составили правила общения - в сообществе, руководствуясь которыми каждый мог бы соучаствовать в плодотворных - дискуссиях, не задевая других. Ключевой принцип этих правил предельно прост — - уважайте ближних, постарайтесь не нарушать законы Российской Федерации без крайней - на то необходимости и помните, что в дискуссиях чутких и здравомыслящих - людей рождается истина. -

    - -

    За что можно получить дырку в карме и выиграть бан в сообществе

    -
      -
    1. -

      - Оскорбления, личные нападки, травля и угрозы. В любом виде. Конкретного человека или - социальной группы — не суть. Агрессия, переход на личности - и токсичность едва ли способствуют плодотворному общению. -

      -
    2. - -
    3. -

      - Шовинизм, расизм, сексизм, гомофобия, пропаганда ненависти, педофилии, суицида, распространение - детской порнографии и другого человеконенавистнического контента. -

      -
    4. - -
    5. -

      - Спам, реклама, фейкньюз, ссылки на пропагандистские СМИ, вбросы дезинформации, специально - уводящий от темы флуд, провокации, разжигание конфликтов, намеренный срыв дискуссий. -

      -
    6. - -
    7. -

      - Неаргументированная критика и комментарии вроде «отстой», «зачем - я это увидел/а», «не читал, но осуждаю», «либераху - порвало», «лол», «скатились», «первый нах» и тому - подобные. Односложные реплики не подразумевают возможность обогащающего диалога, - не продуктивны и никак не помогают авторам делать материалы лучше, - а читателям — разобраться. -

      -
    8. -
    - -

    За что можно получить лучи добра и благодарности в сообществе

    -
      -
    1. -

      - Вежливость и конструктивность. Мы выступаем за конструктивный - диалог, аргументированные комментарии и доброжелательное отношение друг к другу. - Задавайте содержательные вопросы, пишите развернутые комментарии, подкрепляйте - их аргументами, чтобы диалог был полезен всем участникам, помогая глубже понять тему - и разобраться в вопросе. И, пожалуйста, уважайте собеседника, даже если он вам - лично не импонирует: только так получаются продуктивные дискуссии. -

      -
    2. - -
    3. -

      - Обмен знаниями и историями. Осмысленные высказывания по теме поста, - оригинальные рассуждения, рассказы о личном опыте и проектах, обмен профессиональной - экспертизой, наблюдения и реальные истории из жизни — чем больше - мы делимся друг с другом знаниями, тем интереснее и плодотворнее становится наше - общение. Помните, что каждый вдумчивый ответ повышает качество дискуссий в сообществе - и делает чтение самиздата ещё интереснее. -

      -
    4. - -
    5. -

      - Чувство юмора и добродушие. Остроумие и дружелюбие не только - направляют дискуссии в продуктивное русло, но и улучшают настроение. - Не вредите негативом, которого в интернете и без нас хватает, - и не травите на корню классные инициативы — всё великое начинается - с малого. Мы за поддерживающую и вдохновляющую атмосферу в сообществе. - Надеемся, вы тоже. -

      -
    6. - -
    7. -

      - Благодарность и поддержка. Если публикация вам зашла, не стесняйтесь - ставить лайки, делиться понравившимися материалами, благодарить авторов, читателей, художников - и редакторов в комментариях. Цените и поддерживайте классные проекты, сильные - тексты, новое искусство, осмысленные комментарии и вклад других - в самиздат — сотрудничество делает нас сильнее и усиливает звучание идей - и смыслов, которые помогают лучше понимать мир. -

      -
    8. -
    -
    - ) -} - -export const Page = DiscussionRulesPage diff --git a/src/pages/about/dogma.page.route.ts b/src/pages/about/dogma.page.route.ts deleted file mode 100644 index 2c63adf0..00000000 --- a/src/pages/about/dogma.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../../stores/router' -import { getServerRoute } from '../../utils/getServerRoute' - -export default getServerRoute(ROUTES.dogma) diff --git a/src/pages/about/dogma.page.tsx b/src/pages/about/dogma.page.tsx deleted file mode 100644 index ada689a5..00000000 --- a/src/pages/about/dogma.page.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Meta } from '../../context/meta' - -import { StaticPage } from '../../components/Views/StaticPage' -import { useLocalize } from '../../context/localize' -import { getImageUrl } from '../../utils/getImageUrl' - -export const DogmaPage = () => { - const { t } = useLocalize() - - const ogImage = getImageUrl('production/image/logo_image.png') - const ogTitle = t('Dogma') - const description = t('Professional principles that the open editorial team follows in its work') - - return ( - - - - - - - - - - - - -

    - Редакционные принципы -

    - -

    - Дискурс — журнал с открытой горизонтальной редакцией. Содержание журнала определяется прямым - голосованием его авторов. Мы нередко занимаем различные позиции по разным проблемам, но - придерживаемся общих профессиональных принципов: -

    -
      -
    1. - На первое место ставим факты. Наша задача — не судить, а наблюдать и непредвзято - фиксировать происходящее. Все утверждения и выводы, которые мы делаем, подтверждаются фактами, - цифрами, мнениями экспертов или ссылками на авторитетные источники. -
    2. -
    3. - Ответственно относимся к источникам. - Мы выбираем только надежные источники, проверяем информацию и рассказываем, как и откуда мы её - получили, кроме случаев, когда это может нанести вред источникам. Тогда мы не раскроем их, даже в - суде. -
    4. -
    5. - Выбираем компетентных и независимых экспертов, понимая всю степень ответственности перед - аудиторией. -
    6. -
    7. - - Даем возможность высказаться всем заинтересованным сторонам, но не присоединяемся ни к чьему - лагерю. - - Ко всем событиям, компаниям и людям мы относимся с одинаковым скептицизмом. -
    8. -
    9. - Всегда исправляем ошибки, если мы их допустили. - Никто не безгрешен, иногда и мы ошибаемся. Заметили ошибку — отправьте{' '} - ремарку автору или напишите нам на{' '} - - welcome@discours.io - - . -
    10. -
    -
    - ) -} - -export const Page = DogmaPage diff --git a/src/pages/about/guide.page.tsx b/src/pages/about/guide.page.tsx deleted file mode 100644 index c2cba232..00000000 --- a/src/pages/about/guide.page.tsx +++ /dev/null @@ -1,247 +0,0 @@ -import { Meta } from '../../context/meta' - -import { StaticPage } from '../../components/Views/StaticPage' -import { useLocalize } from '../../context/localize' -import { getImageUrl } from '../../utils/getImageUrl' - -export const GuidePage = () => { - const { t } = useLocalize() - - const ogImage = getImageUrl('production/image/logo_image.png') - const ogTitle = t('How Discours works') - const description = t('A guide to horizontal editorial: how an open journal works') - - return ( - - <> - - - - - - - - - - - -

    - {ogTitle} -

    - -

    - Дискурс — независимый журнал о культуре, науке, искусстве и обществе с  - открытой редакцией. У нас нет главного редактора, инвестора - и вообще никого, кто бы принимал единоличные решения. Вместо традиционных иерархий - Дискурс основан на принципах прямой демократии: в нашем горизонтальном сообществе все - редакционные вопросы решаются открытым голосованием авторов журнала. Вот как это работает. -

    -

    Как устроен сайт Дискурса

    -

    Дискурс состоит из четырех основных разделов:

    -
      -
    • -

      - Темы -  — у нас публикуются исследования, обзоры, эссе, интервью, репортажи, - аналитика и другие материалы о культуре, науке, искусстве и обществе. -

      -
    • -
    • -

      - Искусство -  — здесь, например, представлены художественные произведения: литература, живопись, - музыка, фотографии, видео. Этот раздел помогает прозвучать новому искусству, которое создают - российские художники, писатели, режиссёры и музыканты. -

      -
    • - {/* -
    • -

      - События — в этом разделе - публикуются самые важные, по мнению редакции, культурные - события России — выставки, лекции, концерты, кинопоказы, фестивали, - художественные и политические акции. Напишите нам - на почту, если вы - хотите разместить объявление. Мы делаем это - на безвозмездной основе. -

      -
    • -
    • -

      - Редакция — - это внутренний раздел, где появляются новые материалы, которые присылают - в редакцию. Здесь авторы обсуждают, редактируют и оценивают - публикации, определяя таким образом содержание журнала. -

      -
    • -*/} -
    -

    - Материалы в Дискурсе объединяются по темам - — ключевым словам, которые располагаются в конце материалов и связывают - материалы по жанрам (например, интервью,{' '} - репортажи, эссе,{' '} - ликбезы - ), по тематике (кино, философия,{' '} - история, абсурдизм,{' '} - секс и т.д.) или в серии (как « - Законы мира» или « - За линией Маннергейма - »). Темы объединяют сотни публикаций, помогают ориентироваться в журнале и следить - за интересными материалами. -

    - -
    -

    Как стать автором журнала

    -

    - Дискурс объединяет журналистов, активистов, музыкантов, художников, фотографов, режиссеров, - философов, ученых и других замечательных людей. Каждый может прислать{' '} - свой материал в журнал. Формат и тематика не имеют значения, единственное, что - важно — хороший ли материал. Если - сообщество поддержит вашу публикацию, она выйдет в журнале и станет доступна тысячам - наших читателей. -

    -
    - -

    Как проходит голосование

    -

    - Все присылаемые в Дискурс материалы попадают в  - «Редакцию». Это внутренний раздел сайта, где участники сообщества - решают, что будет опубликовано в Дискурсе. Как только работа получает одобрение как минимум - пятерых авторов открытой редакции, она немедленно публикуется в журнале. Если же - материал набирает более 20% голосов «против», он не выходит - и может быть отправлен на доработку. Жестких сроков рассмотрения материалов у нас - нет, иногда это занимает час, иногда месяц, обычно — несколько дней. -

    -
    -

    - Как только сообщество поддержит публикацию, вы получите приглашение - в интернет-редакцию и сможете голосовать за новые материалы. -

    -
    - -

    Как мы делаем тексты друг друга лучше

    -

    - Дискурс — журнал с совместным редактированием. Совершенствовать тексты нам - помогает система ремарок. Вы можете выделить часть текста в любой статье - и оставить к ней замечание, вопрос или предложение — автор текста получит - совет на почту и сможет его учесть. Так мы устраняем опечатки, неточности - и советуем друг другу, как сделать тексты качественнее и интереснее. -

    -

    - Среди участников сообщества есть профессиональные редакторы, которые помогают авторам делать - тексты лучше. Если вашему материалу потребуется доработка, они помогут отредактировать текст, - подобрать иллюстрации, придумать заголовок и красиво сверстать публикацию. Если - вы хотите обсудить текст, прежде чем загрузить материал в интернет-редакцию — - разместите его в google-документе, откройте доступ к редактированию по ссылке - и напишите нам на  - - welcome@discours.io - - . -

    -

    - Если у вас возникают трудности с тем, чтобы подобрать к своему материалу - иллюстрации, тоже пишите на  - - почту - - — наши коллеги-художники могут вам помочь{' '} - - в режиме совместного редактирования - - . -

    - -

    Что сообщество дает авторам

    -
      -
    • -

      - Право определять, каким будет журнал. Дискурс — это общественная - институция, созданная людьми и ради людей, функционирующая на условиях прямой - демократии. Авторы публикуют статьи и художественные проекты, участвуют - в обсуждениях, голосуют за работы коллег и таким образом вносят свой вклад - в развитие проекта, определяя содержание и направление журнала. -

      -
    • -
    • -

      - Возможность обратиться к широкой аудитории. Дискурс читают десятки тысяч - людей, и с каждым днем их становится больше. -

      -
    • -
    • -

      - Поддержка редакции. Дискурс предоставляет авторам аккредитацию - на мероприятия, базу контактов, юридическую поддержку, ознакомление с книжными, - кино- и музыкальными новинками до их выхода в свет. Если что-то - из этого вам понадобится, пишите на почту{' '} - - welcome@discours.io - -  — поможем. -

      -
    • -
    • -

      - Пресс-карты для корреспондентов. Три опубликованные статьи позволяют авторам - Дискурса получить официальные удостоверения журналистов (пресс-карты) на следующий год. - Пресс-карты удостоверяют, что вы журналист и можете пользоваться всеми теми правами, - которые гарантирует Закон о СМИ. Кроме того, многие культурные институции (музеи, галереи - и др.) предоставляют журналистам право свободного входа. -

      -
    • -
    • -

      - Помощь сотен специалистов в разных областях. В основе Дискурса - лежит идея совместного редактирования. Участники редакционного сообщества — - несколько сотен журналистов, исследователей, художников, литераторов из разных стран - — изучают материалы друг друга до публикации и помогают сделать - их качественнее и интереснее. Так, в редакции нередко складываются творческие - союзы: например, авторов текстов и художников, создающих для них иллюстрации. -

      -
    • -
    • -

      - Пространство общения полное выдающихся людей. Дискурс — большое - живое сообщество интеллектуалов, разбросанных по всему земному шару. Вступив - в редакцию, вы сможете познакомиться со множеством интересных людей, которые - определяют повестку завтрашнего дня, вдохновляют окружающих, создают новое и изучают - старое, ищут знания и готовы ими делиться, чтобы менять мир в соответствии - со своими идеалами. -

      -
    • -
    - -

    Как быть в курсе

    -

    - За свежими публикациями Дискурса можно следить не только на сайте, - но и на страницах в  - - Фейсбуке - - ,{' '} - - ВКонтакте - {' '} - и  - - Телеграме - - . А ещё раз в месяц мы отправляем почтовую рассылку{' '} - с дайджестом лучших материалов. -

    -

    - Если вы хотите сотрудничать, что-то обсудить или предложить — пожалуйста, пишите - на  - - welcome@discours.io - - . Мы обязательно ответим. -

    - -
    - ) -} - -export const Page = GuidePage diff --git a/src/pages/about/guilde.page.route.ts b/src/pages/about/guilde.page.route.ts deleted file mode 100644 index 2d86c1b5..00000000 --- a/src/pages/about/guilde.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../../stores/router' -import { getServerRoute } from '../../utils/getServerRoute' - -export default getServerRoute(ROUTES.guide) diff --git a/src/pages/about/help.page.route.ts b/src/pages/about/help.page.route.ts deleted file mode 100644 index 48288617..00000000 --- a/src/pages/about/help.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../../stores/router' -import { getServerRoute } from '../../utils/getServerRoute' - -export default getServerRoute(ROUTES.help) diff --git a/src/pages/about/help.page.tsx b/src/pages/about/help.page.tsx deleted file mode 100644 index 8da9bd10..00000000 --- a/src/pages/about/help.page.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { Meta } from '../../context/meta' - -import { Donate } from '../../components/Discours/Donate' -import { StaticPage } from '../../components/Views/StaticPage' -import { useLocalize } from '../../context/localize' -import { getImageUrl } from '../../utils/getImageUrl' - -export const HelpPage = () => { - const { t } = useLocalize() - - const ogImage = getImageUrl('production/image/logo_image.png') - const ogTitle = t('Support Discours') - const description = t( - 'Contribute to free samizdat. Support Discours - an independent non-profit publication that works only for you. Become a pillar of the open newsroom' - ) - - return ( - - <> - - - - - - - - - - - -

    - Как вы можете поддержать Дискурс? -

    - -

    - Дискурс — уникальное независимое издание с горизонтальной редакцией, существующее - в интересах своих читателей. Ваша поддержка действительно много значит — - не только для редакции Дискурса, но и для сохранения свободной мысли - и некоммерческого искусства в нашем обществе. -

    -

    - Дискурс существует на добровольных началах. Никакой медиахолдинг, фонд или государственная - структура не финансирует нас — благодаря этому мы можем писать о том, - что важно, а не о том, что выгодно. Сообщество наших волонтеров ежедневно трудится, - чтобы рассказывать вам интересные, не освещенные другими изданиями истории — - но мы не сможем делать это без вашей помощи. Пожертвования читателей составляют - основу нашего бюджета и позволяют нам существовать. -

    -

    - Если вам нравится то, что мы делаем и вы хотите, чтобы Дискурс продолжался, - пожалуйста, поддержите проект. -

    -
    -
    - -
    -
    -

    На что пойдут деньги?

    -

    - Ваши пожертвования пойдут на оплату серверов, содержание офиса, зарплату редакции - и налоги, оплату юридического сопровождения и труда бухгалтера, совершенствование сайта, - аренду помещения для открытой редакции, на печать альманаха Дискурс с лучшими текстами - авторов за полгода, а также на другие редакционные и технические расходы. -

    -

    Ваша помощь позволит нам

    -
      -
    • -

      Оставаться бесплатным изданием.

      -

      - Мы делаем открытый журнал для всех желающих, а также собираем искусство лучших - авторов по всему миру. Ваша поддержка позволяет нам становиться лучше. -

      -
    • -
    • -

      Создавать еще больше контента.

      -

      - Каждый день к нам присоединяются новые люди, и чем больше нас становится, тем больше - мы творим и строже оцениваем результаты творчества друг друга. В результате - повышается и количество, и качество контента. Каждый день мы трудимся, чтобы - открывать нашим читателям новые грани окружающего мира. -

      -
    • -
    • -

      Развивать форматы и расширять деятельность Дискурса.

      -

      - Мы создаем различные спецпроекты и регулярно проводим необычные мероприятия. - Мы хотим приносить пользу человечеству всеми возможными способами. -

      -
    • -
    • -

      Модернизировать сайт.

      -

      - Мы совершенствуем платформу и стараемся сделать проект максимально удобным для вас. - Мы работаем над мобильной версией, новым дизайном, фукционалом, системой регистрации, - навигации и рекомендаций, которые сделают наше общение еще увлекательней. -

      -
    • -
    • -

      Выпускать альманах.

      -

      - Выпускать раз в полугодие печатный альманах Дискурс с 33 лучшими текстами - сайта. -

      -
    • -
    • -

      Захватить весь мир

      -

      и принести «Дискурс» в каждый дом.

      -
    • -
    -

    Войдите в попечительский совет Дискурса

    -

    - Вы хотите сделать крупное пожертвование? Станьте попечителем Дискурса —{' '} - - напишите нам - - , мы будем рады единомышленникам. -

    -

    Как ещё можно поддержать Дискурс?

    -

    - Есть много других способов поддержать Дискурс и труд наших авторов. Например, вы можете - периодически рассказывать о проекте своим друзьям в соцсетях, делиться хорошими - материалами или — что еще лучше — публиковать свои статьи - в «Дискурсе». Но главное, что вы можете сделать для Дискурса, — - читать нас. Мы вкладываем в журнал душу, и внимание каждого читателя убеждает нас - в правильности выбранного пути. Не переключайтесь. -

    -

    - Если вы хотите помочь проекту, но у вас возникли вопросы, напишите нам письмо - по адресу{' '} - - welcome@discours.io - - . -

    - -
    - ) -} - -export const Page = HelpPage diff --git a/src/pages/about/manifest.page.route.ts b/src/pages/about/manifest.page.route.ts deleted file mode 100644 index 70a30fba..00000000 --- a/src/pages/about/manifest.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../../stores/router' -import { getServerRoute } from '../../utils/getServerRoute' - -export default getServerRoute(ROUTES.manifest) diff --git a/src/pages/about/manifest.page.tsx b/src/pages/about/manifest.page.tsx deleted file mode 100644 index 932d0056..00000000 --- a/src/pages/about/manifest.page.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { Meta } from '../../context/meta' - -import { Feedback } from '../../components/Discours/Feedback' -import { Modal } from '../../components/Nav/Modal' -import Opener from '../../components/Nav/Modal/Opener' -import { StaticPage } from '../../components/Views/StaticPage' -import { Newsletter } from '../../components/_shared/Newsletter' -import { useLocalize } from '../../context/localize' -import { getImageUrl } from '../../utils/getImageUrl' - -export const ManifestPage = () => { - const { t } = useLocalize() - - const ogImage = getImageUrl('production/image/logo_image.png') - const ogTitle = t('Discours Manifest') - const description = t( - 'Manifest of samizdat: principles and mission of an open magazine with a horizontal editorial board' - ) - - return ( - - <> - - - - - - - - - - - - - - - - - -

    - Манифест -

    - -

    - Дискурс — независимый художественно-аналитический журнал с горизонтальной - редакцией, основанный на принципах свободы слова, прямой демократии и совместного - редактирования. Дискурс создаётся открытым медиасообществом ученых, журналистов, музыкантов, - писателей, предпринимателей, философов, инженеров, художников и специалистов со всего - мира, объединившихся, чтобы вместе делать общий журнал и объяснять с разных точек зрения - мозаичную картину современности. -

    -

    - Мы пишем о культуре, науке и обществе, рассказываем о новых идеях - и современном искусстве, публикуем статьи, исследования, репортажи, интервью людей, чью - прямую речь стоит услышать, и работы художников из разных стран — - от фильмов и музыки до живописи и фотографии. Помогая друг другу делать - публикации качественнее и общим голосованием выбирая лучшие материалы для журнала, - мы создаём новую горизонтальную журналистику, чтобы честно рассказывать о важном - и интересном. -

    -

    - Редакция Дискурса открыта для всех: у нас нет цензуры, запретных тем и идеологических - рамок. Каждый может прислать материал в журнал и  - присоединиться к редакции. Предоставляя трибуну для независимой - журналистики и художественных проектов, мы помогаем людям рассказывать свои истории так, - чтобы они были услышаны. Мы убеждены: чем больше голосов будет звучать на Дискурсе, тем - громче в полифонии мнений будет слышна истина. -

    - -

    - Как участвовать в самиздате -

    - -

    - Дискурс создается открытым сообществом энтузиастов новой независимой - журналистики. Участвовать в открытой редакции и помогать журналу можно следующими - способами: -

    -
    - -

    Предлагать материалы

    -
    -

    - Создавайте свои статьи и художественные работы — лучшие из - них будут опубликованы в журнале. Дискурс — некоммерческое издание, авторы - публикуются в журнале на общественных началах, получая при этом{' '} - поддержку редакции, право голоса, множество других - возможностей и читателей по всему миру. -

    -
    - -
    - - - -

    - Дискурс существует на пожертвования читателей. Если вам нравится журнал, пожалуйста,{' '} - поддержите нашу работу. Ваши пожертвования пойдут на выпуск новых - материалов, оплату серверов, труда программистов, дизайнеров и редакторов. -

    -
    - -
    - -

    Сотрудничать с журналом

    -
    -

    - Мы всегда открыты для сотрудничества и рады единомышленникам. Если вы хотите помогать - журналу с редактурой, корректурой, иллюстрациями, переводами, версткой, подкастами, - мероприятиями, фандрайзингом или как-то ещё — скорее пишите нам на  - welcome@discours.io. -

    -

    - Если вы представляете некоммерческую организацию и хотите сделать с нами совместный - проект, получить информационную поддержку или предложить другую форму - сотрудничества — пишите. -

    -

    - Если вы разработчик и хотите помогать с развитием сайта Дискурса,{' '} - присоединяйтесь к IT-команде самиздата. Открытый - код платформы для независимой журналистики, а также всех наших спецпроектов - и медиаинструментов находится{' '} - в свободном доступе на GitHub. -

    -
    - -
    - -

    Как еще можно помочь

    -
    -

    - Советуйте Дискурс друзьям и знакомым. Обсуждайте и распространяйте наши - публикации — все материалы открытой редакции можно читать и перепечатывать - бесплатно. Подпишитесь на самиздат ВКонтакте, - в  - Фейсбуке и в  - Телеграме, а также на  - рассылку лучших материалов, чтобы не пропустить ничего - интересного. -

    -

    - Рассказывайте о впечатлениях{' '} - от материалов открытой редакции, делитесь идеями, - интересными темами, о которых хотели бы узнать больше, и историями, которые нужно - рассказать. -

    -
    - -

    - Будем на связи -

    - -

    - Если вы хотите предложить материал, сотрудничать, рассказать о проблеме, которую нужно - осветить, сообщить об ошибке или баге, что-то обсудить, уточнить или посоветовать, - пожалуйста, напишите нам здесь или на почту{' '} - welcome@discours.io. Мы обязательно ответим - и постараемся реализовать все хорошие задумки. -

    - -
    - ) -} - -export const Page = ManifestPage diff --git a/src/pages/about/partners.page.route.ts b/src/pages/about/partners.page.route.ts deleted file mode 100644 index 5e588193..00000000 --- a/src/pages/about/partners.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../../stores/router' -import { getServerRoute } from '../../utils/getServerRoute' - -export default getServerRoute(ROUTES.partners) diff --git a/src/pages/about/partners.page.tsx b/src/pages/about/partners.page.tsx deleted file mode 100644 index 7277cbb3..00000000 --- a/src/pages/about/partners.page.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Meta } from '../../context/meta' - -import { StaticPage } from '../../components/Views/StaticPage' -import { useLocalize } from '../../context/localize' -import { getImageUrl } from '../../utils/getImageUrl' - -export const PartnersPage = () => { - const { t } = useLocalize() - - const ogTitle = t('Partners') - const ogImage = getImageUrl('production/image/logo_image.png') - const description = t('Discours Partners') - - return ( - - - - - - - - - - - - -

    {t('Partners')}

    -
    - ) -} - -export const Page = PartnersPage diff --git a/src/pages/about/principles.page.route.ts b/src/pages/about/principles.page.route.ts deleted file mode 100644 index 4995eb09..00000000 --- a/src/pages/about/principles.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../../stores/router' -import { getServerRoute } from '../../utils/getServerRoute' - -export default getServerRoute(ROUTES.principles) diff --git a/src/pages/about/principles.page.tsx b/src/pages/about/principles.page.tsx deleted file mode 100644 index d8b10b63..00000000 --- a/src/pages/about/principles.page.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { Meta } from '../../context/meta' - -import { StaticPage } from '../../components/Views/StaticPage' -import { useLocalize } from '../../context/localize' -import { getImageUrl } from '../../utils/getImageUrl' - -export const PrinciplesPage = () => { - const { t } = useLocalize() - - const ogImage = getImageUrl('production/image/logo_image.png') - const ogTitle = t('Community Principles') - const description = t('Community values and rules of engagement for the open editorial team') - - return ( - - - - - - - - - - - - -

    - {ogTitle} -

    - -
      -
    1. -

      - Горизонтальность. Мы все разные, и это классно. Вертикалей - в мире достаточно, мы — горизонтальное сообщество и ценим наши различия, - потому что знаем — в них наша сила. Благодаря разнообразию сотен голосов, - усиливающих друг друга, в сообществе складывается неповторимая синергия, которая помогает - вместе достигать большего. -

      -
    2. -
    3. -

      - Многоголосие. Мы ценим свободу слова и аргументированные мнения. - Предоставляя трибуну каждому, кому есть что сказать, самиздат отражает полифонию позиций, знаний - и опыта, которые открывают более полную картину реальности. -

      -
    4. -
    5. -

      - Взаимопомощь. Мы помогаем друг другу, потому что хотим, чтобы в мире - было еще больше хорошего. Обсуждая что-то, мы всегда интересуемся, чем можем помочь. - В самиздате можно найти специалистов практически в любых сферах и получить - поддержку от сотен людей. Благодаря коллективной экспертизе глобального сообщества - в самиздате выходят крутейшие публикации, которыми можно вечно гордиться. -

      -
    6. -
    7. -

      - Взаимоуважение. Мы ценим, искренне уважаем друг друга и вместо - борщевиков враждебности культивируем цветы добра, мира, знания и юмора. Нам некогда - доказывать друг другу, кто круче. Гораздо приятнее сотрудничать, помогать и создавать - что-то важное, интересное и полезное. -

      -
    8. -
    9. -

      - Созидание. Мы создаем, потому что любим создавать. Мы открыто делимся - опытом, дарим идеи, обмениваемся мнениями и благодарим за критику, используя - ее для совершенствования мастерства и саморазвития. Мы знаем, что мир - не идеальное место, и делаем всё возможное, чтобы он стал лучше. -

      -
    10. -
    - -

    - Как у нас принято себя вести -

    - -

    - Открытая редакция объединяет сотни потрясающих людей со всего мира, которые делают крутейшие - вещи. Это пространство, где доверяют, вдохновляют, исследуют и создают новое вместе. Поскольку - все в сообществе очень разные, как-то мы собрались и решили зафиксировать базовые - ценности открытой редакции, а заодно придумали универсальные правила взаимодействия, чтобы - общение было не только плодотворным, но и приятным для всех участников сообщества. -

    -
      -
    1. -

      - Действуем, помогаем и делимся. В редакции мы создаем свои - проекты и помогаем другим создавать свои — советами, делом, участием, - вовлеченностью. Мы открыто делимся опытом, мнениями и идеями, потому что ценим силу - сотрудничества и знаем, что идеи реализуются скорее, лучше и веселее, если над ними - трудиться сообща. -

      -
    2. - -
    3. -

      - Общаемся дружелюбно. Помните, по ту сторону монитора находятся - реальные люди. Неуважение ранит других так же, как ранило бы вас самих. Поэтому - не стоит кричать (даже капслоком), заполнять эфир желчью и бросаться - грубостями — так вы рискуете не только растерять доверие окружающих, - но и остаться непонятым. -

      -
    4. - -
    5. -

      - Критикуем и реагируем конструктивно. Самиздат про то, чтобы - разбираться в сложных вещах всем сообществом, поэтому мы тактично и без агрессии - делимся мнениями, стараясь убедительно аргументировать позиции. И с благодарностью - принимаем критику, используя ее для улучшения наших проектов. Мы верим, что каждый - участник сообщества имеет добрые намерения, и придерживаемся принципов доброжелательной - критики, стараемся делиться советами — лучшим средством для самосовершенствования. - Обоснованная критика помогает и адресату, и всем участникам сообщества досконально - изучить тему и глубже разобраться в проблеме. -

      -
    6. - -
    7. -

      - Решаем трудности не агрессией, а диалогом. Обесценивать мнения - и оскорблять других людей только потому, что вы с ними - не согласны, — не лучший способ донести свою точку зрения. Конечно, важно - высказаться, если вас что-то не устраивает и откровенно бесит. Но прежде чем - сжигать оппонента гневом, попробуйте понять, почему этот «нехороший человек» так - поступает. Возможно, аргументы собеседника окажутся убедительными или вам удастся изменить его - мнение. В любом случае конфликты решаются в диалогах и проходят, - а налаженное взаимопонимание останется надолго. -

      -
    8. - -
    9. -

      - Не переходим на личности — это признак токсичности. Всегда - мудрее обсуждать точку зрения человека, а не его самого, даже если он вам - не импонирует. Предвзятое отношение ограничивает кругозор, добавляет преждевременные - морщины и не помогает окружающим стать лучше. Вежливость - и взаимоуважение — краеугольная основа вдумчивых и осмысленных дискуссий. -

      -
    10. - -
    11. -

      - Благодарим за помощь. Благодарите коллег даже за самые, - казалось бы, простые вещи. «Спасибо» не зря называют волшебным - словом — на искренней благодарности держится любое подлинное сотрудничество. - Поддержка воодушевляет на новые подвиги и напоминает, что мир делают прекрасным - не машины, а живые люди. -

      -
    12. - -
    13. -

      - Даем еще один шанс. Все совершают ошибки, и за один проступок - не стоит вычеркивать людей из жизни. Ошибки нужны, чтобы на них учиться - и делать выводы. Однако если многократно и систематически нарушать правила сообщества, - наверняка можно заслужить минусы в карму от других участников и потерять доступ - к сообществу. -

      -
    14. - -
    15. -

      - Вместе создаем идеальную среду общения. Открытая редакция — это - утопическое пространство обогащающей и осмысленной коммуникации. Атмосфера горизонтального - сообщества складывается из действий каждого, поэтому мы действуем так, чтобы - способствовать сотворчеству, коллективному познанию и развитию самиздата и нашей - альтернативной интеллектуальной медиасреды. -

      -
    16. - -
    17. -

      - Помним, что всё в сообществе зависит от нас. Если нам чего-то - не хватает, мы начинаем действовать — рассказываем об идее, находим - единомышленников, готовим и запускаем проект. Так в сообществе становится на одну - крутую активность больше. Так появилось наше сообщество. Так появился самиздат и все - проекты открытой редакции. Чтобы в сообществе случилось что-то прекрасное, достаточно - просто положить этому начало. -

      -
    18. -
    -
    - ) -} - -export const Page = PrinciplesPage diff --git a/src/pages/about/projects.page.route.ts b/src/pages/about/projects.page.route.ts deleted file mode 100644 index b8ab171a..00000000 --- a/src/pages/about/projects.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../../stores/router' -import { getServerRoute } from '../../utils/getServerRoute' - -export default getServerRoute(ROUTES.projects) diff --git a/src/pages/about/projects.page.tsx b/src/pages/about/projects.page.tsx deleted file mode 100644 index f8d541b0..00000000 --- a/src/pages/about/projects.page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { PageLayout } from '../../components/_shared/PageLayout' -import { useLocalize } from '../../context/localize' - -export const ProjectsPage = () => { - const { t } = useLocalize() - return ( - -
    -
    -
    -

    {t('Projects')}

    -
    -
    -
    -
    - ) -} -export const Page = ProjectsPage diff --git a/src/pages/about/termsOfUse.page.route.ts b/src/pages/about/termsOfUse.page.route.ts deleted file mode 100644 index 301475fd..00000000 --- a/src/pages/about/termsOfUse.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../../stores/router' -import { getServerRoute } from '../../utils/getServerRoute' - -export default getServerRoute(ROUTES.termsOfUse) diff --git a/src/pages/about/termsOfUse.page.tsx b/src/pages/about/termsOfUse.page.tsx deleted file mode 100644 index a5082158..00000000 --- a/src/pages/about/termsOfUse.page.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import { Meta } from '../../context/meta' - -import { StaticPage } from '../../components/Views/StaticPage' -import { useLocalize } from '../../context/localize' -import { getImageUrl } from '../../utils/getImageUrl' - -export const TermsOfUsePage = () => { - const { t } = useLocalize() - - const ogTitle = t('Terms of use') - const ogImage = getImageUrl('production/image/logo_image.png') - const description = t('Rules of the journal Discours') - - return ( - - - - - - - - - - - - -

    - Пользовательское соглашение -

    - -

    - Дискурс — это сообщество творческих людей, объединенных идеей делать интересный журнал - для всех желающих. Авторы Дискурса сообща посредством прямого голосования определяют содержание - журнала. -

    -

    Для того, чтобы Дискурс работал без помех, разработаны настоящие Правила.

    -

    Определения

    -

    - Сайт — портал discours.io -

    -

    - Пользователь — лицо, пользующееся Сайтом, либо юридическое лицо, - обладающее правами на интеллектуальную собственность. -

    -

    - Публикация контента — размещение Пользователем посредством Сайта - объектов авторских прав и другой информации для других пользователей. -

    -

    - Издательство — администрация сайта, которая занимается технической - и издательской деятельностью для обеспечения функционирования Сайта и Альманаха. - Издательство не вмешивается в принятие редакционных решений авторским сообществом. -

    -

    - Альманах «Дискурс» (свидетельство о регистрации СМИ: ПИ № - ФС77-63947 от 18.12.15) — печатное периодическое издание, которое выходит раз - в год и состоит из лучших публикаций на Сайте за это время. -

    - -
      -
    1. -

      - Вся информация на сайте (включая тексты, изображения, видеоматериалы, аудиозаписи, - программный код, дизайн сайта и т.д.) является объектом интеллектуальной собственности - ее правообладателей и охраняется законодательством РФ. -

      -
    2. -
    3. -

      - Публикуя контент на сайте, Пользователь на безвозмездной основе предоставляет - Издательству право на воспроизведение, распространение, перевод, редактирование контента. - Данное право предоставляется Издательству на весь срок действия авторских прав - Пользователя. -

      -
    4. -
    5. -

      - Пользователь предоставляет Издательству право редактировать контент, в том числе вносить - в него изменения, сокращения и дополнения, снабжать его иллюстрациями - и пояснениями, исправлять ошибки и уточнять фактические сведения, при условии, что - этим не искажается авторский замысел. -

      -
    6. -
    7. -

      - Обнародование контента осуществляется Издательством в соответствии с условиями - лицензии{' '} - - Creative Commons BY-NC-ND 4.0 - - . Все материалы сайта предназначены исключительно для личного некоммерческого использования. - Права на дизайн и программный код сайта принадлежат Издательству. -

      -
    8. -
    9. -

      - Все аудиовизуальные произведения являются собственностью своих авторов и правообладателей - и используются только в образовательных и информационных целях. Если - вы являетесь собственником того или иного произведения и не согласны с его - размещением на сайте, пожалуйста, напишите на  - - welcome@discours.io - - . -

      -
    10. -
    11. -

      - Цитирование, распространение, доведение до всеобщего сведения материалов Cайта - приветствуется. При использовании материалов сайта необходимо указать имя автора и активную - ссылку на материал на Сайте. -

      -
    12. -
    -

    Правила поведения

    -
      -
    1. -

      - Находясь на Сайте, Пользователь подтверждает свое совершеннолетие, правоспособность, - а также согласие с настоящими Правилами и политикой конфиденциальности - и готовность нести полную ответственность за их соблюдение. -

      -
    2. -
    3. -

      На сайте запрещено:

      -
        -
      • - Публиковать контент, авторские права на который принадлежат третьим лицам, без согласия - этих лиц. Если авторские права на контент принадлежат нескольким лицам, то его публикация - предполагает согласие их всех. -
      • -
      • Размещать коммерческую и политическую рекламу.
      • -
      • - Целенаправленно препятствовать нормальному функционированию сообщества и сайта - discours.io -
      • -
      • Выдавать себя за другого человека и представляться его именем.
      • -
      • - Размещать информацию, которая не соответствует целям создания Сайта, ущемляет интересы - других пользователей или третьих лиц, нарушает законы Российской Федерации. -
      • -
      -
    4. -
    5. -

      - Пользователь несет всю ответственность за содержание публикуемого контента и свое - взаимодействие с другими пользователями, и обязуется возместить все расходы - в случае предъявления каких-либо претензий третьими лицами. Издательство не несет - ответственности за содержание публикуемой пользователями информации, в том числе - за размещенные на сайте комментарии. Переписка между Пользователем - и Издательством считается юридически значимой. Настоящие Правила могут быть изменены - Издательством, изменения вступают в силу с момента публикации на Сайте. -

      -
    6. -
    7. -

      - Если Пользователь очевидно и целенаправленно нарушает правила, Издательство может - и принять в отношении автора следующие меры: вынести предупреждение и обязать - автора устранить допущенное нарушение, удалить контент, нарушающий правила, заблокировать или - удалить аккаунт нарушителя. -

      -
    8. -
    -

    Политика конфиденциальности

    -
      -
    1. -

      Сайт может собирать у пользователей следующие данные:

      -
        -
      • -

        - Данные, которые пользователи сообщают о себе сами при подаче заявки, регистрации, - авторизации или заполнения профиля, в том числе ФИО и контактную информацию. - Конфиденциальные данные, такие как идентификатор и электронный адрес, используются для - идентификации пользователя. Данные профиля, размещённые публично по желанию - пользователя, которое выражается фактом их предоставления, используется для - демонстрации другим пользователям той информации о себе, которую пользователь готов - предоставить. -

        -
      • -
      • -

        - Данные, собранные автоматическим путем, такие, как cookie-файлы. Эти неперсонализированные - данные могут использоваться для сбора статистики и улучшения работы сайта. -

        -
      • -
      -
    2. -
    3. -

      - Издательство обеспечивает конфиденциальность персональных данных и применяет все - необходимые организационные и технические меры по их защите. -

      -
    4. -
    5. -

      - По желанию пользователя Издательство готово удалить любую информацию о нем, собранную - автоматическим путем. Для этого следует написать на адрес электронной почты{' '} - - welcome@discours.io - - . -

      -
    6. -
    7. -

      - Если в информации, предоставляемой Издательству Пользователем, содержатся персональные - данные последнего, то фактом их предоставления он соглашается - на их обработку любым способом, не запрещенным законодательством РФ. -

      -

      - Общедоступные видео на сайте могут транслироваться с YouTube и регулируются{' '} - - политикой конфиденциальности Google - - . Загрузка видео на сайт также означает согласие с  - - Условиями использования YouTube - - . -

      -
    8. -
    9. -

      - Данные, которые мы получаем от вас, мы используем только в соответствии - с принципами обработки данных, указанными в этом документе. -

      -
    10. -
    -

    Обратная связь

    -

    - Любые вопросы и предложения по поводу функционирования сайта можно направить - по электронной почте{' '} - - welcome@discours.io - {' '} - или через форму «предложить идею». -

    -
    - ) -} - -export const Page = TermsOfUsePage diff --git a/src/pages/about/thanks.page.route.ts b/src/pages/about/thanks.page.route.ts deleted file mode 100644 index 2db639c8..00000000 --- a/src/pages/about/thanks.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../../stores/router' -import { getServerRoute } from '../../utils/getServerRoute' - -export default getServerRoute(ROUTES.thanks) diff --git a/src/pages/about/thanks.page.tsx b/src/pages/about/thanks.page.tsx deleted file mode 100644 index 24b740d3..00000000 --- a/src/pages/about/thanks.page.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { Meta } from '../../context/meta' - -import { StaticPage } from '../../components/Views/StaticPage' -import { useLocalize } from '../../context/localize' -import { getImageUrl } from '../../utils/getImageUrl' - -export const ThanksPage = () => { - const { t } = useLocalize() - const ogImage = getImageUrl('production/image/logo_image.png') - const ogTitle = t('Thank you') - const description = t( - 'Self-publishing exists thanks to the help of wonderful people from all over the world. Thank you!' - ) - - return ( - - - - - - - - - - - - -

    - {ogTitle} -

    - {/* -

    Команда

    -

    - Константин Ворович — исполнительный директор, - welcome@discours.io
    - Александр Гусев — технический архитектор, - services@discours.io
    - Екатерина Ильина — шеф-редактор проекта, - letter@discours.io
    - Яна Климова — редактор сайта и соцсетей, - letter@discours.io
    - Николай Носачевский — голос и душа подкаста, - podcast@discours.io -

    - */} -

    Неоценимый вклад в Дискурс внесли и вносят

    -

    - Мария Бессмертная, Дамир Бикчурин, Константин Ворович, Ян Выговский, Эльдар Гариффулин, Павел - Гафаров, Виктория Гендлина, Александр Гусев, Данила Давыдов, Константин Дубовик, Вячеслав Еременко, - Кристина Ибрагим, Екатерина Ильина, Анна Капаева, Яна Климова, Александр Коренков, Ирэна Лесневская, - Игорь Лобанов, Анастасия Лозовая, Григорий Ломизе, Евгений Медведев, Павел Никулин, Николай - Носачевский, Андрей Орловский, Михаил Панин, Антон Панов, Павел Пепперштейн, Любовь Покровская, Илья - Розовский, Денис Светличный, Павел Соколов, Сергей Стрельников, Глеб Струнников, Николай Тарковский, - Кирилл Филимонов, Алексей Хапов, Екатерина Харитонова -

    -

    Авторы

    -

    - Мы безмерно благодарны{' '} - - каждому автору - {' '} - за участие и поддержку проекта. Сегодня, когда для большинства деньги стали целью - и основным источником мотивации, бескорыстная помощь и основанный на энтузиазме труд - бесценны. Именно вы своим трудом каждый день делаете Дискурс таким, какой он есть. -

    -

    Иллюстраторы

    -

    - Ольга Аверинова, Регина Акчурина, Айгуль Берхеева, Екатерина Вакуленко, Анастасия Викулова, Мария - Власенко, Ванесса Гаврилова, Ольга Горше, Ксения Горшкова, Ангелина Гребенюкова, Илья Diliago, Антон - Жаголкин, Саша Керова, Ольга Машинец, Злата Мечетина, Тала Никитина, Никита Поздняков, Матвей - Сапегин, Татьяна Сафонова, Виктория Шибаева -

    -

    Меценаты

    -

    - Дискурс существует исключительно на пожертвования читателей. Мы бесконечно признательны - всем, кто нас поддерживает. Ваши пожертвования — финансовый фундамент журнала. Благодаря - вам мы развиваем платформу качественной журналистики, которая помогает самым разным авторам - быть услышанными. Стать нашим меценатом и подписаться на ежемесячную поддержку проекта - можно здесь. -

    -
    - ) -} - -export const Page = ThanksPage diff --git a/src/pages/allAuthors.page.route.ts b/src/pages/allAuthors.page.route.ts deleted file mode 100644 index 7edd2f2f..00000000 --- a/src/pages/allAuthors.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../stores/router' -import { getServerRoute } from '../utils/getServerRoute' - -export default getServerRoute(ROUTES.authors) diff --git a/src/pages/allAuthors.page.server.ts b/src/pages/allAuthors.page.server.ts deleted file mode 100644 index 1450723c..00000000 --- a/src/pages/allAuthors.page.server.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { PageContext } from '../renderer/types' -import type { PageProps } from './types' - -import { PAGE_SIZE } from '../components/Views/AllTopics/AllTopics' -import { apiClient } from '../graphql/client/core' - -export const onBeforeRender = async (_pageContext: PageContext) => { - const allAuthors = await apiClient.getAllAuthors() - const topWritingAuthors = await apiClient.loadAuthorsBy({ - by: { order: 'shouts' }, - limit: PAGE_SIZE, - offset: 0 - }) - const topFollowedAuthors = await apiClient.loadAuthorsBy({ - by: { order: 'followers' }, - limit: PAGE_SIZE, - offset: 0 - }) - const pageProps: PageProps = { allAuthors, seo: { title: '' }, topWritingAuthors, topFollowedAuthors } - - return { - pageContext: { - pageProps - } - } -} diff --git a/src/pages/allAuthors.page.tsx b/src/pages/allAuthors.page.tsx deleted file mode 100644 index 3086cef1..00000000 --- a/src/pages/allAuthors.page.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import type { PageProps } from './types' - -import { createSignal, onMount } from 'solid-js' - -import { AllAuthors } from '../components/Views/AllAuthors/' -import { PAGE_SIZE } from '../components/Views/AllTopics/AllTopics' -import { PageLayout } from '../components/_shared/PageLayout' -import { useLocalize } from '../context/localize' -import { loadAllAuthors, loadAuthors } from '../stores/zine/authors' - -export const AllAuthorsPage = (props: PageProps) => { - const [isLoaded, setIsLoaded] = createSignal( - Boolean(props.allAuthors && props.topFollowedAuthors && props.topWritingAuthors) - ) - - const { t } = useLocalize() - - onMount(async () => { - if (isLoaded()) { - return - } - - await loadAllAuthors() - await loadAuthors({ by: { order: 'shouts' }, limit: PAGE_SIZE, offset: 0 }) - await loadAuthors({ by: { order: 'followers' }, limit: PAGE_SIZE, offset: 0 }) - setIsLoaded(true) - }) - - return ( - - - - ) -} - -export const Page = AllAuthorsPage diff --git a/src/pages/allTopics.page.route.ts b/src/pages/allTopics.page.route.ts deleted file mode 100644 index e7019ef7..00000000 --- a/src/pages/allTopics.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../stores/router' -import { getServerRoute } from '../utils/getServerRoute' - -export default getServerRoute(ROUTES.topics) diff --git a/src/pages/allTopics.page.server.ts b/src/pages/allTopics.page.server.ts deleted file mode 100644 index d9cfd2a5..00000000 --- a/src/pages/allTopics.page.server.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { PageContext } from '../renderer/types' -import type { PageProps } from './types' - -import { apiClient } from '../graphql/client/core' - -export const onBeforeRender = async (_pageContext: PageContext) => { - const allTopics = await apiClient.getAllTopics() - - const pageProps: PageProps = { allTopics, seo: { title: '' } } - - return { - pageContext: { - pageProps - } - } -} diff --git a/src/pages/allTopics.page.tsx b/src/pages/allTopics.page.tsx deleted file mode 100644 index 5b440afe..00000000 --- a/src/pages/allTopics.page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { AllTopics } from '../components/Views/AllTopics' -import { PageLayout } from '../components/_shared/PageLayout' -import { useLocalize } from '../context/localize' -import { useTopics } from '../context/topics' - -export const AllTopicsPage = () => { - const { t } = useLocalize() - const { sortedTopics } = useTopics() - - return ( - - - - ) -} - -export const Page = AllTopicsPage diff --git a/src/pages/article.page.route.ts b/src/pages/article.page.route.ts deleted file mode 100644 index 9b9f5ecb..00000000 --- a/src/pages/article.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../stores/router' -import { getServerRoute } from '../utils/getServerRoute' - -export default getServerRoute(ROUTES.article) diff --git a/src/pages/article.page.server.ts b/src/pages/article.page.server.ts deleted file mode 100644 index 3c41e14a..00000000 --- a/src/pages/article.page.server.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { PageContext } from '../renderer/types' -import type { PageProps } from './types' - -import { render } from 'vike/abort' - -import { apiClient } from '../graphql/client/core' - -export const onBeforeRender = async (pageContext: PageContext) => { - const { slug } = pageContext.routeParams - const article = await apiClient.getShoutBySlug(slug) - - if (!article) { - throw render(404) - } - - const pageProps: PageProps = { article, seo: { title: article.title } } - - return { - pageContext: { - pageProps - } - } -} diff --git a/src/pages/article.page.tsx b/src/pages/article.page.tsx deleted file mode 100644 index c78d7b10..00000000 --- a/src/pages/article.page.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import type { Shout } from '../graphql/schema/core.gen' -import type { PageProps } from './types' - -import { redirectPage } from '@nanostores/router' -import { Show, createMemo, createSignal, onMount } from 'solid-js' - -import { FullArticle } from '../components/Article/FullArticle' -import { Loading } from '../components/_shared/Loading' -import { PageLayout } from '../components/_shared/PageLayout' -import { ReactionsProvider } from '../context/reactions' -import { router, useRouter } from '../stores/router' -import { loadShout, useArticlesStore } from '../stores/zine/articles' -import { setPageLoadManagerPromise } from '../utils/pageLoadManager' - -export const ArticlePage = (props: PageProps) => { - const shouts = props.article ? [props.article] : [] - const { page } = useRouter() - - const slug = createMemo(() => page().params['slug'] as string) - - const { articleEntities } = useArticlesStore({ - shouts - }) - - const article = createMemo(() => articleEntities()[slug()]) - - onMount(async () => { - if (!article()?.body) { - const loadShoutPromise = loadShout(slug()) - setPageLoadManagerPromise(loadShoutPromise) - await loadShoutPromise - - if (!article()) { - redirectPage(router, 'fourOuFour') - } - } - }) - - const [scrollToComments, setScrollToComments] = createSignal(false) - - return ( - { - setScrollToComments(value) - }} - > - - }> - - - - - ) -} - -export const Page = ArticlePage diff --git a/src/pages/author.page.route.ts b/src/pages/author.page.route.ts deleted file mode 100644 index a6b7653a..00000000 --- a/src/pages/author.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../stores/router' -import { getServerRoute } from '../utils/getServerRoute' - -export default getServerRoute(ROUTES.author) diff --git a/src/pages/author.page.server.ts b/src/pages/author.page.server.ts deleted file mode 100644 index dca92742..00000000 --- a/src/pages/author.page.server.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { PageContext } from '../renderer/types' -import type { PageProps } from './types' - -import { render } from 'vike/abort' - -import { PRERENDERED_ARTICLES_COUNT } from '../components/Views/Author' -import { apiClient } from '../graphql/client/core' - -export const onBeforeRender = async (pageContext: PageContext) => { - const { slug } = pageContext.routeParams - console.debug(`[author.page] detected author in route: @${slug}`) - const author = await apiClient.getAuthor({ slug }) - - if (!author) { - throw render(404) - } - - const authorShouts = await apiClient.getShouts({ - filters: { author: slug, featured: false }, - limit: PRERENDERED_ARTICLES_COUNT - }) - const pageProps: PageProps = { author, authorShouts, seo: { title: author.name } } - - return { - pageContext: { - pageProps - } - } -} diff --git a/src/pages/author.page.tsx b/src/pages/author.page.tsx deleted file mode 100644 index a2e9bd70..00000000 --- a/src/pages/author.page.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import type { PageProps } from './types' - -import { Show, createEffect, createMemo, createSignal, on, onCleanup } from 'solid-js' - -import { AuthorView, PRERENDERED_ARTICLES_COUNT } from '../components/Views/Author' -import { Loading } from '../components/_shared/Loading' -import { PageLayout } from '../components/_shared/PageLayout' -import { useLocalize } from '../context/localize' -import { ReactionsProvider } from '../context/reactions' -import { useRouter } from '../stores/router' -import { loadShouts, resetSortedArticles } from '../stores/zine/articles' -import { loadAuthor } from '../stores/zine/authors' - -export const AuthorPage = (props: PageProps) => { - const { t } = useLocalize() - const { page } = useRouter() - const slug = createMemo(() => page().params['slug'] as string) - - const [isLoaded, setIsLoaded] = createSignal( - Boolean(props.authorShouts) && Boolean(props.author) && props.author.slug === slug() - ) - - createEffect( - on(slug, async (s) => { - if (s) { - setIsLoaded(false) - resetSortedArticles() - await loadShouts({ - filters: { author: s, featured: false }, - limit: PRERENDERED_ARTICLES_COUNT - }) - await loadAuthor({ slug: s }) - setIsLoaded(true) - } - }) - ) - - onCleanup(() => resetSortedArticles()) - - return ( - - - }> - - - - - ) -} - -export const Page = AuthorPage diff --git a/src/pages/authorAbout.page.route.ts b/src/pages/authorAbout.page.route.ts deleted file mode 100644 index 4bcf9ef3..00000000 --- a/src/pages/authorAbout.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../stores/router' -import { getServerRoute } from '../utils/getServerRoute' - -export default getServerRoute(ROUTES.authorAbout) diff --git a/src/pages/authorComment.page.route.ts b/src/pages/authorComment.page.route.ts deleted file mode 100644 index 10ff3029..00000000 --- a/src/pages/authorComment.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../stores/router' -import { getServerRoute } from '../utils/getServerRoute' - -export default getServerRoute(ROUTES.authorComments) diff --git a/src/pages/connect.page.route.ts b/src/pages/connect.page.route.ts deleted file mode 100644 index 264948b8..00000000 --- a/src/pages/connect.page.route.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ROUTES } from '../stores/router' -import { getServerRoute } from '../utils/getServerRoute' - -export default getServerRoute(ROUTES.connect) diff --git a/src/pages/connect.page.tsx b/src/pages/connect.page.tsx deleted file mode 100644 index 23ff0d75..00000000 --- a/src/pages/connect.page.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { Show, createSignal } from 'solid-js' - -import { PageLayout } from '../components/_shared/PageLayout' - -export const ConnectPage = () => { - const [state, setState] = createSignal<'initial' | 'loading' | 'success' | 'error'>('initial') - - const formRef: { current: HTMLFormElement } = { current: null } - const handleFormSubmit = async (e) => { - e.preventDefault() - setState('loading') - - // eslint-disable-next-line unicorn/prefer-spread - const postData = Array.from(formRef.current.elements).reduce( - (acc, element) => { - const formField = element as unknown as { name: string; value: string } - if (formField.name) { - acc[formField.name] = formField.value - } - - return acc - }, - {} as Record - ) - - const requestOptions = { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(postData) - } - - const result = await fetch('/api/feedback', requestOptions) - - if (!result.ok) { - console.error('[handleFormSubmit]', result) - setState('error') - return - } - - setState('success') - window.scrollTo({ - top: 0 - }) - } - - // TODO: l10n - return ( - -
    -
    -
    - -

    - Предложить идею -

    - -

    - Хотите что-то предложить, обсудить или посоветовать? Поделиться темой или идеей? Напишите - нам скорее! Если укажете свою почту, мы обязательно ответим. -

    - - (formRef.current = el)}> -
    - -
    -
    - - -
    -
    -