Merge branch 'main' into feature/search-modal

This commit is contained in:
Arkadzi Rakouski 2024-01-21 16:01:41 +03:00 committed by GitHub
commit 2d396dc175
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
378 changed files with 13034 additions and 21672 deletions

106
.eslintrc.cjs Normal file
View File

@ -0,0 +1,106 @@
module.exports = {
plugins: ["@typescript-eslint", "import", "sonarjs", "unicorn", "promise", "solid", "jest"],
extends: [
"eslint:recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"prettier",
"plugin:sonarjs/recommended",
"plugin:unicorn/recommended",
"plugin:promise/recommended",
"plugin:solid/recommended",
"plugin:jest/recommended"
],
overrides: [
{
files: ["**/*.ts", "**/*.tsx"],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2021,
ecmaFeatures: { jsx: true },
sourceType: "module",
project: "./tsconfig.json"
},
extends: [
"plugin:@typescript-eslint/recommended"
// Maybe one day...
// 'plugin:@typescript-eslint/recommended-requiring-type-checking'
],
rules: {
"@typescript-eslint/no-unused-vars": [
"warn",
{
argsIgnorePattern: "^_"
}
],
"@typescript-eslint/no-non-null-assertion": "error",
// TODO: Remove any usage and enable
"@typescript-eslint/no-explicit-any": "off"
}
}
],
env: {
browser: true,
node: true,
mocha: true
},
globals: {},
rules: {
// Solid
"solid/reactivity": "off", // FIXME
"solid/no-innerhtml": "off",
/** Unicorn **/
"unicorn/no-null": "off",
"unicorn/filename-case": "off",
"unicorn/no-array-for-each": "off",
"unicorn/no-array-reduce": "off",
"unicorn/prefer-string-replace-all": "warn",
"unicorn/prevent-abbreviations": "off",
"unicorn/prefer-module": "off",
"unicorn/import-style": "off",
"unicorn/numeric-separators-style": "off",
"unicorn/prefer-node-protocol": "off",
"unicorn/prefer-dom-node-append": "off", // FIXME
"unicorn/prefer-top-level-await": "warn",
"unicorn/consistent-function-scoping": "warn",
"unicorn/no-array-callback-reference": "warn",
"unicorn/no-array-method-this-argument": "warn",
"unicorn/no-for-loop": "off",
"sonarjs/no-duplicate-string": ["warn", { threshold: 5 }],
// Promise
// 'promise/catch-or-return': 'off', // Should be enabled
"promise/always-return": "off",
eqeqeq: "error",
"no-param-reassign": "error",
"no-nested-ternary": "error",
"no-shadow": "error",
"import/order": ["warn", {
groups: ["type", "builtin", "external", "internal", "parent", "sibling", "index"],
distinctGroup: false,
pathGroups: [
{
pattern: "*.scss",
patternOptions: { matchBase: true },
group: "index",
position: "after"
}
],
"newlines-between": "always",
alphabetize: {
order: "asc",
caseInsensitive: true
}
}]
},
settings: {
"import/resolver": {
typescript: true,
node: true
}
}
};

View File

@ -1,87 +0,0 @@
module.exports = {
plugins: ['@typescript-eslint', 'import', 'sonarjs', 'unicorn', 'promise', 'solid', 'jest'],
extends: [
'eslint:recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'prettier',
'plugin:sonarjs/recommended',
'plugin:unicorn/recommended',
'plugin:promise/recommended',
'plugin:solid/recommended',
'plugin:jest/recommended'
],
overrides: [
{
files: ['**/*.ts', '**/*.tsx'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2021,
ecmaFeatures: { jsx: true },
sourceType: 'module',
project: './tsconfig.json'
},
extends: [
'plugin:@typescript-eslint/recommended'
// Maybe one day...
// 'plugin:@typescript-eslint/recommended-requiring-type-checking'
],
rules: {
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_'
}
],
'@typescript-eslint/no-non-null-assertion': 'error',
// TODO: Remove any usage and enable
'@typescript-eslint/no-explicit-any': 'off'
}
}
],
env: {
browser: true,
node: true,
mocha: true
},
globals: {},
rules: {
// Solid
'solid/reactivity': 'off', // FIXME
'solid/no-innerhtml': 'off',
/** Unicorn **/
'unicorn/no-null': 'off',
'unicorn/filename-case': 'off',
'unicorn/no-array-for-each': 'off',
'unicorn/no-array-reduce': 'off',
'unicorn/prefer-string-replace-all': 'warn',
'unicorn/prevent-abbreviations': 'off',
'unicorn/prefer-module': 'off',
'unicorn/import-style': 'off',
'unicorn/numeric-separators-style': 'off',
'unicorn/prefer-node-protocol': 'off',
'unicorn/prefer-dom-node-append': 'off', // FIXME
'unicorn/prefer-top-level-await': 'warn',
'unicorn/consistent-function-scoping': 'warn',
'unicorn/no-array-callback-reference': 'warn',
'unicorn/no-array-method-this-argument': 'warn',
'sonarjs/no-duplicate-string': ['warn', { threshold: 5 }],
// Promise
// 'promise/catch-or-return': 'off', // Should be enabled
'promise/always-return': 'off',
eqeqeq: 'error',
'no-param-reassign': 'error',
'no-nested-ternary': 'error',
'no-shadow': 'error'
},
settings: {
'import/resolver': {
typescript: true,
node: true
}
}
}

View File

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

View File

@ -4,7 +4,6 @@
"singleQuote": true, "singleQuote": true,
"proseWrap": "always", "proseWrap": "always",
"printWidth": 108, "printWidth": 108,
"trailingComma": "none",
"plugins": [], "plugins": [],
"overrides": [ "overrides": [
{ {

32
api/edge-ssr.js Normal file
View File

@ -0,0 +1,32 @@
import { renderPage } from 'vike/server'
export const config = {
runtime: 'edge',
}
export default async function handler(request) {
const { url, cookies } = request
const pageContext = await renderPage({ urlOriginal: url, cookies })
const { httpResponse, errorWhileRendering, is404 } = pageContext
if (errorWhileRendering && !is404) {
console.error(errorWhileRendering)
return new Response('', { status: 500 })
}
if (!httpResponse) {
return new Response()
}
const { body, statusCode, headers: headersArray } = httpResponse
const headers = headersArray.reduce((acc, [name, value]) => {
acc[name] = value
return acc
}, {})
headers['Cache-Control'] = 's-maxage=1, stale-while-revalidate'
return new Response(body, { status: statusCode, headers })
}

View File

@ -15,7 +15,7 @@ export default async function handler(req, res) {
from: 'Discours Feedback Robot <robot@discours.io>', from: 'Discours Feedback Robot <robot@discours.io>',
to: 'welcome@discours.io', to: 'welcome@discours.io',
subject, subject,
text text,
} }
try { try {

View File

@ -13,18 +13,18 @@ export default async (req, res) => {
const response = await mg.lists.members.createMember('newsletter@discours.io', { const response = await mg.lists.members.createMember('newsletter@discours.io', {
address: email, address: email,
subscribed: true, subscribed: true,
upsert: 'yes' upsert: 'yes',
}) })
return res.status(200).json({ return res.status(200).json({
success: true, success: true,
message: 'Email was added to newsletter list', message: 'Email was added to newsletter list',
response: JSON.stringify(response) response: JSON.stringify(response),
}) })
} catch (error) { } catch (error) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: error.message message: error.message,
}) })
} }
} }

View File

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

18041
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@
"version": "0.8.0", "version": "0.8.0",
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"type": "module",
"scripts": { "scripts": {
"build": "vite build", "build": "vite build",
"check": "npm run lint && npm run typecheck", "check": "npm run lint && npm run typecheck",
@ -35,20 +36,21 @@
"intl-messageformat": "10.5.3", "intl-messageformat": "10.5.3",
"just-throttle": "4.2.0", "just-throttle": "4.2.0",
"mailgun.js": "8.2.1", "mailgun.js": "8.2.1",
"sanitize-html": "2.11.0" "sanitize-html": "2.11.0",
"mailgun.js": "8.2.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.21.8", "@babel/core": "7.23.3",
"@graphql-codegen/cli": "3.2.2", "@graphql-codegen/cli": "5.0.0",
"@graphql-codegen/typescript": "3.0.4", "@graphql-codegen/typescript": "4.0.1",
"@graphql-codegen/typescript-operations": "3.0.4", "@graphql-codegen/typescript-operations": "4.0.1",
"@graphql-codegen/typescript-urql": "3.7.3", "@graphql-codegen/typescript-urql": "4.0.0",
"@graphql-codegen/urql-introspection": "2.2.1", "@graphql-codegen/urql-introspection": "3.0.0",
"@graphql-tools/url-loader": "7.17.18", "@graphql-tools/url-loader": "7.17.18",
"@graphql-typed-document-node/core": "3.2.0", "@graphql-typed-document-node/core": "3.2.0",
"@hocuspocus/provider": "2.0.6", "@hocuspocus/provider": "2.0.6",
"@nanostores/router": "0.8.3", "@nanostores/router": "0.11.0",
"@nanostores/solid": "0.3.2", "@nanostores/solid": "0.4.2",
"@popperjs/core": "2.11.8", "@popperjs/core": "2.11.8",
"@sentry/browser": "5.30.0", "@sentry/browser": "5.30.0",
"@solid-primitives/media": "2.2.3", "@solid-primitives/media": "2.2.3",
@ -56,7 +58,7 @@
"@solid-primitives/share": "2.0.4", "@solid-primitives/share": "2.0.4",
"@solid-primitives/storage": "1.3.9", "@solid-primitives/storage": "1.3.9",
"@solid-primitives/upload": "0.0.110", "@solid-primitives/upload": "0.0.110",
"@solidjs/meta": "0.28.2", "@solidjs/meta": "0.29.1",
"@thisbeyond/solid-select": "0.14.0", "@thisbeyond/solid-select": "0.14.0",
"@tiptap/core": "2.0.3", "@tiptap/core": "2.0.3",
"@tiptap/extension-blockquote": "2.0.3", "@tiptap/extension-blockquote": "2.0.3",
@ -87,71 +89,66 @@
"@tiptap/extension-text": "2.0.3", "@tiptap/extension-text": "2.0.3",
"@tiptap/extension-underline": "2.0.3", "@tiptap/extension-underline": "2.0.3",
"@tiptap/extension-youtube": "2.0.3", "@tiptap/extension-youtube": "2.0.3",
"@types/js-cookie": "3.0.4", "@types/js-cookie": "3.0.6",
"@types/node": "20.1.1", "@types/node": "20.9.0",
"@typescript-eslint/eslint-plugin": "6.7.3", "@typescript-eslint/eslint-plugin": "6.10.0",
"@typescript-eslint/parser": "6.7.3", "@typescript-eslint/parser": "6.10.0",
"@urql/core": "3.2.2", "@urql/core": "3.2.2",
"@urql/devtools": "2.0.3", "@urql/devtools": "2.0.3",
"babel-preset-solid": "1.7.4", "babel-preset-solid": "1.8.4",
"bootstrap": "5.3.2", "bootstrap": "5.3.2",
"clsx": "2.0.0", "clsx": "2.0.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"debounce": "1.2.1", "eslint": "8.53.0",
"eslint": "8.50.0",
"eslint-config-stylelint": "20.0.0", "eslint-config-stylelint": "20.0.0",
"eslint-import-resolver-typescript": "3.6.1", "eslint-import-resolver-typescript": "3.6.1",
"eslint-plugin-import": "2.28.1", "eslint-plugin-import": "2.29.0",
"eslint-plugin-jest": "27.4.0", "eslint-plugin-jest": "27.6.0",
"eslint-plugin-jsx-a11y": "6.7.1", "eslint-plugin-jsx-a11y": "6.8.0",
"eslint-plugin-promise": "6.1.1", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-solid": "0.13.0", "eslint-plugin-solid": "0.13.0",
"eslint-plugin-sonarjs": "0.21.0", "eslint-plugin-sonarjs": "0.23.0",
"eslint-plugin-unicorn": "48.0.1", "eslint-plugin-unicorn": "49.0.0",
"fast-deep-equal": "3.1.3", "fast-deep-equal": "3.1.3",
"graphql": "16.6.0", "graphql": "16.8.1",
"graphql-tag": "2.12.6", "graphql-tag": "2.12.6",
"html-to-json-parser": "1.1.0",
"husky": "8.0.3", "husky": "8.0.3",
"hygen": "6.2.11", "hygen": "6.2.11",
"i18next-http-backend": "2.2.0", "i18next-http-backend": "2.2.0",
"javascript-time-ago": "2.5.9", "javascript-time-ago": "2.5.9",
"jest": "29.7.0", "jest": "29.7.0",
"js-cookie": "3.0.5", "js-cookie": "3.0.5",
"lint-staged": "14.0.1", "lint-staged": "15.1.0",
"loglevel": "1.8.1", "loglevel": "1.8.1",
"loglevel-plugin-prefix": "0.8.4", "loglevel-plugin-prefix": "0.8.4",
"markdown-it": "13.0.1", "nanostores": "0.9.5",
"markdown-it-container": "3.0.0", "prettier": "3.1.0",
"markdown-it-implicit-figures": "0.11.0", "prettier-eslint": "16.1.2",
"markdown-it-mark": "3.0.1",
"markdown-it-replace-link": "1.2.0",
"nanostores": "0.7.4",
"prettier": "3.0.3",
"prettier-eslint": "15.0.1",
"prosemirror-history": "1.3.0", "prosemirror-history": "1.3.0",
"prosemirror-trailing-node": "2.0.3", "prosemirror-trailing-node": "2.0.3",
"prosemirror-view": "1.30.2", "prosemirror-view": "1.30.2",
"rollup": "3.21.6", "rollup": "3.21.6",
"sass": "1.68.0", "sass": "1.69.5",
"solid-js": "1.7.5", "solid-js": "1.8.7",
"solid-popper": "0.3.0", "solid-popper": "0.3.0",
"solid-tiptap": "0.6.0", "solid-tiptap": "0.6.0",
"solid-transition-group": "0.2.2", "solid-transition-group": "0.2.3",
"sort-json": "2.0.1",
"sort-package-json": "2.6.0", "sort-package-json": "2.6.0",
"stylelint": "15.10.3", "stylelint": "15.11.0",
"stylelint-config-standard-scss": "11.0.0", "stylelint-config-standard-scss": "11.1.0",
"stylelint-order": "6.0.3", "stylelint-order": "6.0.3",
"stylelint-scss": "5.2.1", "stylelint-scss": "5.3.1",
"swiper": "9.4.1", "swiper": "11.0.5",
"throttle-debounce": "5.0.0",
"typescript": "5.2.2", "typescript": "5.2.2",
"typograf": "7.1.0", "typograf": "7.1.0",
"uniqolor": "1.1.0", "uniqolor": "1.1.0",
"vite": "4.3.9", "vike": "0.4.148",
"vite": "4.5.0",
"vite-plugin-mkcert": "1.16.0", "vite-plugin-mkcert": "1.16.0",
"vite-plugin-sass-dts": "1.3.11", "vite-plugin-sass-dts": "1.3.11",
"vite-plugin-solid": "2.7.0", "vite-plugin-solid": "2.7.2",
"vite-plugin-ssr": "0.4.123",
"y-prosemirror": "1.2.1", "y-prosemirror": "1.2.1",
"yjs": "13.6.0" "yjs": "13.6.0"
}, },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 744 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Binary file not shown.

5
public/icons/copy.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 866 B

View File

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

After

Width:  |  Height:  |  Size: 797 B

View File

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

After

Width:  |  Height:  |  Size: 901 B

View File

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

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

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

After

Width:  |  Height:  |  Size: 859 B

View File

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

After

Width:  |  Height:  |  Size: 784 B

View File

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

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

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

Before

Width:  |  Height:  |  Size: 401 B

After

Width:  |  Height:  |  Size: 380 B

View File

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

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 264 B

View File

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

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,7 +1,8 @@
{ {
"...subscribing": "...subscribing", "A guide to horizontal editorial: how an open journal works": "A guide to horizontal editorial: how an open journal works",
"About": "About", "About": "About",
"About the project": "About the project", "About the project": "About the project",
"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 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 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 an embed widget": "Add an embed widget",
@ -18,12 +19,13 @@
"Add signature": "Add signature", "Add signature": "Add signature",
"Add subtitle": "Add subtitle", "Add subtitle": "Add subtitle",
"Add url": "Add url", "Add url": "Add url",
"Address on Discourse": "Address on Discourse", "Address on Discours": "Address on Discours",
"Album name": "Название aльбома", "Album name": "Название aльбома",
"Alignment center": "Alignment center", "Alignment center": "Alignment center",
"Alignment left": "Alignment left", "Alignment left": "Alignment left",
"Alignment right": "Alignment right", "Alignment right": "Alignment right",
"All": "All", "All": "All",
"All articles": "All articles",
"All authors": "All authors", "All authors": "All authors",
"All posts": "All posts", "All posts": "All posts",
"All topics": "All topics", "All topics": "All topics",
@ -41,6 +43,7 @@
"Back": "Back", "Back": "Back",
"Back to editor": "Back to editor", "Back to editor": "Back to editor",
"Back to main page": "Back to main page", "Back to main page": "Back to main page",
"Be the first to rate": "Be the first to rate",
"Become an author": "Become an author", "Become an author": "Become an author",
"Bold": "Bold", "Bold": "Bold",
"Bookmarked": "Saved", "Bookmarked": "Saved",
@ -58,22 +61,34 @@
"By title": "By title", "By title": "By title",
"By updates": "By updates", "By updates": "By updates",
"By views": "By views", "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", "Cancel changes": "Cancel changes",
"Change password": "Change password",
"Characters": "Знаков", "Characters": "Знаков",
"Chat Title": "Chat Title", "Chat Title": "Chat Title",
"Choose a post type": "Choose a post type", "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 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", "Choose who you want to write to": "Choose who you want to write to",
"Close": "Close",
"Co-author": "Co-author",
"Collaborate": "Help Edit", "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 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", "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", "Comment successfully deleted": "Comment successfully deleted",
"Commentator": "Commentator",
"Comments": "Comments", "Comments": "Comments",
"Communities": "Communities", "Communities": "Communities",
"Community Discussion Rules": "Community Discussion Rules",
"Community Principles": "Community Principles", "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": "Confirm",
"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", "Cooperate": "Cooperate",
"Copy": "Copy", "Copy": "Copy",
"Copy link": "Copy link", "Copy link": "Copy link",
@ -95,14 +110,18 @@
"Decline": "Decline", "Decline": "Decline",
"Delete": "Delete", "Delete": "Delete",
"Delete cover": "Delete cover", "Delete cover": "Delete cover",
"Delete userpic": "Delete userpic",
"Description": "Description", "Description": "Description",
"Discours": "Discours", "Discours": "Discours",
"Discours Manifest": "Discours Manifest",
"Discours Partners": "Discours Partners",
"Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects": "Discours is&#160;an&#160;intellectual environment, a&#160;web space and tools that allows authors to&#160;collaborate with readers and come together to&#160;co-create publications and media projects.<br/><em>We&#160;are convinced that one voice is&#160;good, but many is&#160;better. We&#160;create the most amazing stories together</em>", "Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects": "Discours is&#160;an&#160;intellectual environment, a&#160;web space and tools that allows authors to&#160;collaborate with readers and come together to&#160;co-create publications and media projects.<br/><em>We&#160;are convinced that one voice is&#160;good, but many is&#160;better. We&#160;create the most amazing stories together</em>",
"Discours is created with our common effort": "Discours exists because of our common effort", "Discours is created with our common effort": "Discours exists because of our common effort",
"Discours an open magazine about culture, science and society": "Discours an open magazine about culture, science and society",
"Discussing": "Discussing", "Discussing": "Discussing",
"Discussion rules": "Discussion rules", "Discussion rules": "Discussion rules",
"Discussion rules in social networks": "Discussion rules",
"Discussions": "Discussions", "Discussions": "Discussions",
"Do you really want to reset all changes?": "Do you really want to reset all changes?",
"Dogma": "Dogma", "Dogma": "Dogma",
"Draft successfully deleted": "Draft successfully deleted", "Draft successfully deleted": "Draft successfully deleted",
"Drafts": "Drafts", "Drafts": "Drafts",
@ -111,9 +130,11 @@
"Edit": "Edit", "Edit": "Edit",
"Edit profile": "Edit profile", "Edit profile": "Edit profile",
"Editing": "Editing", "Editing": "Editing",
"Editor": "Editor",
"Email": "Mail", "Email": "Mail",
"Enter": "Enter", "Enter": "Enter",
"Enter URL address": "Enter URL address", "Enter URL address": "Enter URL address",
"Enter a new password": "Enter a new password",
"Enter footnote text": "Enter footnote text", "Enter footnote text": "Enter footnote text",
"Enter image description": "Enter image description", "Enter image description": "Enter image description",
"Enter image title": "Enter image title", "Enter image title": "Enter image title",
@ -126,6 +147,7 @@
"FAQ": "Tips and suggestions", "FAQ": "Tips and suggestions",
"Favorite": "Favorites", "Favorite": "Favorites",
"Favorite topics": "Favorite topics", "Favorite topics": "Favorite topics",
"Feed": "Feed",
"Feed settings": "Feed settings", "Feed settings": "Feed settings",
"Feedback": "Feedback", "Feedback": "Feedback",
"Fill email": "Fill email", "Fill email": "Fill email",
@ -150,25 +172,30 @@
"Help": "Помощь", "Help": "Помощь",
"Help to edit": "Help to edit", "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 customize your profile the way you want.": "Here you can customize your profile the way you want.",
"Here you can manage all your Discourse subscriptions": "Here you can manage all your Discourse subscriptions", "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", "Hide table of contents": "Hide table of contents",
"Highlight": "Highlight", "Highlight": "Highlight",
"Hooray! Welcome!": "Hooray! Welcome!", "Hooray! Welcome!": "Hooray! Welcome!",
"Horizontal collaborative journalistic platform": "Horizontal collaborative journalism platform", "Horizontal collaborative journalistic platform": "Horizontal collaborative journalism platform",
"Hot topics": "Hot topics", "Hot topics": "Hot topics",
"Hotkeys": "Горячие клавиши", "Hotkeys": "Горячие клавиши",
"How Discours works": "How Discours works",
"How can I help/skills": "How can I help/skills", "How can I help/skills": "How can I help/skills",
"How it works": "How it works", "How it works": "How it works",
"How to help": "How to help?", "How to help": "How to help?",
"How to write a good article": "Как написать хорошую статью", "How to write a good article": "Как написать хорошую статью",
"How to write an article": "How to write an 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 an account": "I have an account!",
"I have no account yet": "I don't have an account yet", "I have no account yet": "I don't have an account yet",
"I know the password": "I know the password", "I know the password": "I know the password",
"Image format not supported": "Image format not supported", "Image format not supported": "Image format not supported",
"In&nbsp;bookmarks, you can save favorite discussions and&nbsp;materials that you want to return to": "In&nbsp;bookmarks, you can save favorite discussions and&nbsp;materials that you want to return to", "In&nbsp;bookmarks, you can save favorite discussions and&nbsp;materials that you want to return to": "In&nbsp;bookmarks, you can save favorite discussions and&nbsp;materials that you want to return to",
"Inbox": "Inbox",
"Incut": "Incut", "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", "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 footnote": "Insert footnote",
"Insert video link": "Insert video link", "Insert video link": "Insert video link",
"Interview": "Interview", "Interview": "Interview",
@ -177,6 +204,7 @@
"Invalid image URL": "Invalid image URL", "Invalid image URL": "Invalid image URL",
"Invalid url format": "Invalid url format", "Invalid url format": "Invalid url format",
"Invite co-authors": "Invite co-authors", "Invite co-authors": "Invite co-authors",
"Invite collaborators": "Invite collaborators",
"Invite to collab": "Invite to Collab", "Invite to collab": "Invite to Collab",
"It does not look like url": "It doesn't look like a link", "It does not look like url": "It doesn't look like a link",
"Italic": "Italic", "Italic": "Italic",
@ -190,16 +218,19 @@
"Last rev.": "Посл. изм.", "Last rev.": "Посл. изм.",
"Let's log in": "Let's log in", "Let's log in": "Let's log in",
"Link copied": "Link copied", "Link copied": "Link copied",
"Link copied to clipboard": "Link copied to clipboard",
"Link sent, check your email": "Link sent, check your email", "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", "Lists": "Lists",
"Literature": "Literature", "Literature": "Literature",
"Load more": "Show more", "Load more": "Show more",
"Loading": "Loading", "Loading": "Loading",
"Logout": "Logout", "Logout": "Logout",
"Looks like you forgot to upload the video": "Looks like you forgot to upload the video", "Looks like you forgot to upload the video": "Looks like you forgot to upload the video",
"Manifest": "Manifest", "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", "Manifesto": "Manifesto",
"Many files, choose only one": "Many files, choose only one", "Many files, choose only one": "Many files, choose only one",
"Mark as read": "Mark as read",
"Material card": "Material card", "Material card": "Material card",
"Message": "Message", "Message": "Message",
"More": "More", "More": "More",
@ -227,10 +258,12 @@
"NotificationNewReplyText2": "from", "NotificationNewReplyText2": "from",
"NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { and one more user} other { and more {restUsersCount} users}}", "NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { and one more user} other { and more {restUsersCount} users}}",
"Notifications": "Notifications", "Notifications": "Notifications",
"Now you can enter a new password, it must contain at least 8 characters and not be the same as the previous password": "Now you can enter a new password, it must contain at least 8 characters and not be the same as the previous password",
"Or paste a link to an image": "Or paste a link to an image", "Or paste a link to an image": "Or paste a link to an image",
"Ordered list": "Ordered list", "Ordered list": "Ordered list",
"Our regular contributor": "Our regular contributor", "Our regular contributor": "Our regular contributor",
"Paragraphs": "Абзацев", "Paragraphs": "Абзацев",
"Participate in the Discours: share information, join the editorial team": "Участвуйте в Дискурсе: делитесь информацией, присоединяйтесь к редакции",
"Participating": "Participating", "Participating": "Participating",
"Participation": "Participation", "Participation": "Participation",
"Partners": "Partners", "Partners": "Partners",
@ -239,6 +272,7 @@
"Password should be at least 8 characters": "Password should be at least 8 characters", "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 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 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", "Passwords are not equal": "Passwords are not equal",
"Paste Embed code": "Paste Embed code", "Paste Embed code": "Paste Embed code",
"Personal": "Personal", "Personal": "Personal",
@ -256,12 +290,14 @@
"Popular": "Popular", "Popular": "Popular",
"Popular authors": "Popular authors", "Popular authors": "Popular authors",
"Principles": "Community principles", "Principles": "Community principles",
"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": "Profile",
"Profile settings": "Profile settings", "Profile settings": "Profile settings",
"Publications": "Publications", "Publications": "Publications",
"PublicationsWithCount": "{count, plural, =0 {no publications} one {{count} publication} other {{count} publications}}", "PublicationsWithCount": "{count, plural, =0 {no publications} one {{count} publication} other {{count} publications}}",
"Publish Album": "Publish Album", "Publish Album": "Publish Album",
"Publish Settings": "Publish Settings", "Publish Settings": "Publish Settings",
"Published": "Published",
"Punchline": "Punchline", "Punchline": "Punchline",
"Quit": "Quit", "Quit": "Quit",
"Quote": "Quote", "Quote": "Quote",
@ -276,6 +312,7 @@
"Required": "Required", "Required": "Required",
"Resend code": "Send confirmation", "Resend code": "Send confirmation",
"Restore password": "Restore password", "Restore password": "Restore password",
"Rules of the journal Discours": "Rules of the journal Discours",
"Save draft": "Save draft", "Save draft": "Save draft",
"Save settings": "Save settings", "Save settings": "Save settings",
"Saving...": "Saving...", "Saving...": "Saving...",
@ -286,11 +323,13 @@
"Sections": "Sections", "Sections": "Sections",
"Security": "Security", "Security": "Security",
"Select": "Select", "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": "Send",
"Send link again": "Send link again", "Send link again": "Send link again",
"Settings": "Settings", "Settings": "Settings",
"Site search": "Site search", "Site search": "Site search",
"Share": "Share", "Share": "Share",
"Share publication": "Share publication",
"Show": "Show", "Show": "Show",
"Show lyrics": "Show lyrics", "Show lyrics": "Show lyrics",
"Show more": "Show more", "Show more": "Show more",
@ -302,6 +341,7 @@
"Something went wrong, please try again": "Something went wrong, please try again", "Something went wrong, please try again": "Something went wrong, please try again",
"Song lyrics": "Song lyrics...", "Song lyrics": "Song lyrics...",
"Song title": "Song title", "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", "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",
"Special projects": "Special projects", "Special projects": "Special projects",
@ -321,21 +361,31 @@
"Success": "Success", "Success": "Success",
"Successfully authorized": "Authorization successful", "Successfully authorized": "Authorization successful",
"Suggest an idea": "Suggest an idea", "Suggest an idea": "Suggest an idea",
"Support Discours": "Support Discours",
"Support the project": "Support the project", "Support the project": "Support the project",
"Support us": "Help the magazine", "Support us": "Support us",
"Terms of use": "Site rules", "Terms of use": "Site rules",
"Text checking": "Text checking", "Text checking": "Text checking",
"Thank you": "Thank you", "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", "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 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?", "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 comment has not yet been rated": "This comment has not yet been rated",
"This email is already taken. If it's you": "This email is already taken. If it's you", "This email is already taken. If it's you": "This email is already taken. If it's you",
"This functionality is currently not available, we would like to work on this issue. Use the download link.": "This functionality is currently not available, we would like to work on this issue. Use the download link.", "This 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 post has not been rated yet": "This post has not been rated yet",
"This way we&nbsp;ll realize that you&nbsp;re a real person and&nbsp;ll take your vote into account. And&nbsp;you&nbsp;ll see how others voted": "This way we&nbsp;ll realize that you&nbsp;re a real person and&nbsp;ll take your vote into account. And&nbsp;you&nbsp;ll see how others voted", "This way we&nbsp;ll realize that you&nbsp;re a real person and&nbsp;ll take your vote into account. And&nbsp;you&nbsp;ll see how others voted": "This way we&nbsp;ll realize that you&nbsp;re a real person and&nbsp;ll take your vote into account. And&nbsp;you&nbsp;ll see how others voted",
"This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed": "This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed", "This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed": "This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed",
"To find publications, art, comments, authors and topics of interest to you, just start typing your query": "To&nbsp;find publications, art, comments, authors and topics of&nbsp;interest to&nbsp;you, just start typing your query", "To find publications, art, comments, authors and topics of interest to you, just start typing your query": "To&nbsp;find publications, art, comments, authors and topics of&nbsp;interest to&nbsp;you, just start typing your query",
"This week": "This week",
"This year": "This year",
"To leave a comment please": "To leave a comment please", "To leave a comment please": "To leave a comment please",
"To write a comment, you must": "To write a comment, you must", "To write a comment, you must": "To write a comment, you must",
"Top authors": "Authors rating", "Top authors": "Authors rating",
@ -355,6 +405,7 @@
"Unnamed draft": "Unnamed draft", "Unnamed draft": "Unnamed draft",
"Upload": "Upload", "Upload": "Upload",
"Upload error": "Upload error", "Upload error": "Upload error",
"Upload userpic": "Upload userpic",
"Upload video": "Upload video", "Upload video": "Upload video",
"Uploading image": "Uploading image", "Uploading image": "Uploading image",
"Username": "Username", "Username": "Username",
@ -364,6 +415,7 @@
"Video format not supported": "Video format not supported", "Video format not supported": "Video format not supported",
"Views": "Views", "Views": "Views",
"We couldn't find anything for your request": "We&nbsp;couldn&rsquo;t find anything for your request", "We couldn't find anything for your request": "We&nbsp;couldn&rsquo;t find anything for your request",
"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 can't find you, check email or": "We can't find you, check email or",
"We know you, please try to login": "This email address is already registered, please try to login", "We 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.", "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.",
@ -375,8 +427,9 @@
"Welcome to Discours to subscribe to new publications": "Welcome to Discours to subscribe to new publications", "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", "Welcome to Discours to vote": "Welcome to Discours to vote",
"Where": "From", "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": "Слов", "Words": "Слов",
"Work with us": "Cooperate with Discourse", "Work with us": "Cooperate with Discours",
"Write a comment...": "Write a comment...", "Write a comment...": "Write a comment...",
"Write a short introduction": "Write a short introduction", "Write a short introduction": "Write a short introduction",
"Write about the topic": "Write about the topic", "Write about the topic": "Write about the topic",
@ -385,7 +438,9 @@
"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 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 message": "Write a message",
"Write to us": "Write to us", "Write to us": "Write to us",
"Write your colleagues name or email": "Write your colleague's name or email",
"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 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 were successfully authorized": "You were successfully authorized", "You were successfully authorized": "You were successfully authorized",
"You&nbsp;ll be able to participate in&nbsp;discussions, rate others' comments and&nbsp;learn about&nbsp;new responses": "You&nbsp;ll be able to participate in&nbsp;discussions, rate others' comments and&nbsp;learn about&nbsp;new responses", "You&nbsp;ll be able to participate in&nbsp;discussions, rate others' comments and&nbsp;learn about&nbsp;new responses": "You&nbsp;ll be able to participate in&nbsp;discussions, rate others' comments and&nbsp;learn about&nbsp;new responses",
"You've confirmed email": "You've confirmed email", "You've confirmed email": "You've confirmed email",
@ -407,7 +462,8 @@
"community": "community", "community": "community",
"contents": "contents", "contents": "contents",
"delimiter": "delimiter", "delimiter": "delimiter",
"discussion": "discourse", "discussion": "Discours",
"dogma keywords": "Discours.io, dogma, editorial principles, code of ethics, journalism, community",
"drafts": "drafts", "drafts": "drafts",
"earlier": "earlier", "earlier": "earlier",
"email not confirmed": "email not confirmed", "email not confirmed": "email not confirmed",
@ -423,6 +479,7 @@
"italic": "italic", "italic": "italic",
"journal": "journal", "journal": "journal",
"jpg, .png, max. 10 mb.": "jpg, .png, макс. 10 мб.", "jpg, .png, max. 10 mb.": "jpg, .png, макс. 10 мб.",
"keywords": "Discours.io, Discours magazine, Discours, culture, science, art, society, independent journalism, literature, music, cinema, video, photography",
"literature": "literature", "literature": "literature",
"marker list": "marker list", "marker list": "marker list",
"min. 1400×1400 pix": "мин. 1400×1400 пикс.", "min. 1400×1400 pix": "мин. 1400×1400 пикс.",
@ -432,6 +489,7 @@
"or sign in with social networks": "or sign in with social networks", "or sign in with social networks": "or sign in with social networks",
"personal data usage and email notifications": "to process personal data and receive email notifications", "personal data usage and email notifications": "to process personal data and receive email notifications",
"post": "post", "post": "post",
"principles keywords": "Discours.io, communities, values, editorial rules, polyphony, creation",
"register": "register", "register": "register",
"repeat": "repeat", "repeat": "repeat",
"shout": "post", "shout": "post",
@ -441,11 +499,14 @@
"subscriber": "subscriber", "subscriber": "subscriber",
"subscriber_rp": "subscriber", "subscriber_rp": "subscriber",
"subscribers": "subscribers", "subscribers": "subscribers",
"subscribing...": "subscribing...",
"subscription": "subscription", "subscription": "subscription",
"subscription_rp": "subscription", "subscription_rp": "subscription",
"subscriptions": "subscriptions", "subscriptions": "subscriptions",
"terms of use": "terms of use", "terms of use": "terms of use",
"terms of use keywords": "Discours.io, site rules, terms of use",
"today": "today", "today": "today",
"topicKeywords": "{topic}, Discours.io, articles, journalism, research",
"topics": "topics", "topics": "topics",
"user already exist": "user already exists", "user already exist": "user already exists",
"video": "video", "video": "video",

View File

@ -1,8 +1,9 @@
{ {
"...subscribing": "...подписываем", "A guide to horizontal editorial: how an open journal works": "Гид по горизонтальной редакции: как работает открытый журнал",
"A short introduction to keep the reader interested": "Добавьте вступление, чтобы заинтересовать читателя", "A short introduction to keep the reader interested": "Добавьте вступление, чтобы заинтересовать читателя",
"About": "О себе", "About": "О себе",
"About the project": "О проекте", "About the project": "О проекте",
"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": "Добавить embed-виджет", "Add an embed widget": "Добавить embed-виджет",
@ -20,12 +21,13 @@
"Add subtitle": "Добавить подзаголовок", "Add subtitle": "Добавить подзаголовок",
"Add to bookmarks": "Добавить в закладки", "Add to bookmarks": "Добавить в закладки",
"Add url": "Добавить ссылку", "Add url": "Добавить ссылку",
"Address on Discourse": "Адрес на Дискурсе", "Address on Discours": "Адрес на Дискурсе",
"Album name": "Название альбома", "Album name": "Название альбома",
"Alignment center": "По центру", "Alignment center": "По центру",
"Alignment left": "По левому краю", "Alignment left": "По левому краю",
"Alignment right": "По правому краю", "Alignment right": "По правому краю",
"All": "Все", "All": "Все",
"All articles": "Все материалы",
"All authors": "Все авторы", "All authors": "Все авторы",
"All posts": "Все публикации", "All posts": "Все публикации",
"All topics": "Все темы", "All topics": "Все темы",
@ -44,6 +46,7 @@
"Back": "Назад", "Back": "Назад",
"Back to editor": "Вернуться в редактор", "Back to editor": "Вернуться в редактор",
"Back to main page": "Вернуться на главную", "Back to main page": "Вернуться на главную",
"Be the first to rate": "Оцените первым",
"Become an author": "Стать автором", "Become an author": "Стать автором",
"Bold": "Жирный", "Bold": "Жирный",
"Bookmarked": "Сохранено", "Bookmarked": "Сохранено",
@ -61,22 +64,35 @@
"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 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": "Отменить изменения", "Cancel changes": "Отменить изменения",
"Change password": "Сменить пароль",
"Characters": "Знаков", "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": "Закрыть",
"Co-author": "Соавтор",
"Collaborate": "Помочь редактировать", "Collaborate": "Помочь редактировать",
"Collaborators": "Соавторы",
"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": "Уже скоро",
"Comment": "Комментировать",
"Comment successfully deleted": "Комментарий успешно удален", "Comment successfully deleted": "Комментарий успешно удален",
"Commentator": "Комментатор",
"Comments": "Комментарии", "Comments": "Комментарии",
"Communities": "Сообщества", "Communities": "Сообщества",
"Community Discussion Rules": "Правила дискуссий в сообществе",
"Community Principles": "Принципы сообщества", "Community Principles": "Принципы сообщества",
"Community values and rules of engagement for the open editorial team": "Ценности сообщества и правила взаимодействия открытой редакции",
"Confirm": "Подтвердить", "Confirm": "Подтвердить",
"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": "Скопировать ссылку",
@ -98,14 +114,19 @@
"Decline": "Отмена", "Decline": "Отмена",
"Delete": "Удалить", "Delete": "Удалить",
"Delete cover": "Удалить обложку", "Delete cover": "Удалить обложку",
"Delete userpic": "Удалить аватар",
"Description": "Описание", "Description": "Описание",
"Discours": "Дискурс", "Discours": "Дискурс",
"Discours Manifest": "Манифест Дискурса",
"Discours Partners": "Партнеры Дискурса",
"Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects": "Дискурс&#160;&#8212; это интеллектуальная среда, веб-пространство и&#160;инструменты, которые позволяют авторам сотрудничать&#160;с&#160;читателями и&#160;объединяться для совместного создания публикаций и&#160;медиапроектов.<br/>Мы&#160;убеждены, один голос хорошо, а&#160;много&#160;&#8212; лучше. Самые потрясающиe истории мы создаём вместе.", "Discours is an intellectual environment, a web space and tools that allows authors to collaborate with readers and come together to co-create publications and media projects": "Дискурс&#160;&#8212; это интеллектуальная среда, веб-пространство и&#160;инструменты, которые позволяют авторам сотрудничать&#160;с&#160;читателями и&#160;объединяться для совместного создания публикаций и&#160;медиапроектов.<br/>Мы&#160;убеждены, один голос хорошо, а&#160;много&#160;&#8212; лучше. Самые потрясающиe истории мы создаём вместе.",
"Discours is created with our common effort": "Дискурс существует благодаря нашему общему вкладу", "Discours is created with our common effort": "Дискурс существует благодаря нашему общему вкладу",
"Discours an open magazine about culture, science and society": "Дискурс открытый журнал о культуре, науке и обществе",
"Discours_theme": "Тема дискурса",
"Discussing": "Обсуждаемое", "Discussing": "Обсуждаемое",
"Discussion rules": "Правила дискуссий", "Discussion rules": "Правила дискуссий",
"Discussion rules in social networks": "Правила сообществ самиздата в&nbsp;соцсетях",
"Discussions": "Дискуссии", "Discussions": "Дискуссии",
"Do you really want to reset all changes?": "Вы действительно хотите сбросить все изменения?",
"Dogma": "Догма", "Dogma": "Догма",
"Draft successfully deleted": "Черновик успешно удален", "Draft successfully deleted": "Черновик успешно удален",
"Drafts": "Черновики", "Drafts": "Черновики",
@ -115,9 +136,11 @@
"Edit profile": "Редактировать профиль", "Edit profile": "Редактировать профиль",
"Edited": "Отредактирован", "Edited": "Отредактирован",
"Editing": "Редактирование", "Editing": "Редактирование",
"Editor": "Редактор",
"Email": "Почта", "Email": "Почта",
"Enter": "Войти", "Enter": "Войти",
"Enter URL address": "Введите адрес ссылки", "Enter URL address": "Введите адрес ссылки",
"Enter a new password": "Введите новый пароль",
"Enter footnote text": "Введите текст сноски", "Enter footnote text": "Введите текст сноски",
"Enter image description": "Введите описание изображения", "Enter image description": "Введите описание изображения",
"Enter image title": "Введите название изображения", "Enter image title": "Введите название изображения",
@ -130,6 +153,7 @@
"FAQ": "Советы и предложения", "FAQ": "Советы и предложения",
"Favorite": "Избранное", "Favorite": "Избранное",
"Favorite topics": "Избранные темы", "Favorite topics": "Избранные темы",
"Feed": "Лента",
"Feed settings": "Настройки ленты", "Feed settings": "Настройки ленты",
"Feedback": "Обратная связь", "Feedback": "Обратная связь",
"Fill email": "Введите почту", "Fill email": "Введите почту",
@ -157,25 +181,30 @@
"Help": "Помощь", "Help": "Помощь",
"Help to edit": "Помочь редактировать", "Help to edit": "Помочь редактировать",
"Here you can customize your profile the way you want.": "Здесь можно настроить свой профиль так, как вы хотите.", "Here you can customize your profile the way you want.": "Здесь можно настроить свой профиль так, как вы хотите.",
"Here you can manage all your Discourse subscriptions": "Здесь можно управлять всеми своими подписками на Дискурсе", "Here you can manage all your Discours subscriptions": "Здесь можно управлять всеми своими подписками на Дискурсе",
"Here you can upload your photo": "Здесь вы можете загрузить свою фотографию",
"Hide table of contents": "Скрыть главление", "Hide table of contents": "Скрыть главление",
"Highlight": "Подсветка", "Highlight": "Подсветка",
"Hooray! Welcome!": "Ура! Добро пожаловать!", "Hooray! Welcome!": "Ура! Добро пожаловать!",
"Horizontal collaborative journalistic platform": "Открытая платформа<br/>для независимой журналистики", "Horizontal collaborative journalistic platform": "Открытая платформа<br/>для независимой журналистики",
"Hot topics": "Горячие темы", "Hot topics": "Горячие темы",
"Hotkeys": "Горячие клавиши", "Hotkeys": "Горячие клавиши",
"How Discours works": "Как устроен Дискурс",
"How can I help/skills": "Чем могу помочь/навыки", "How can I help/skills": "Чем могу помочь/навыки",
"How it works": "Как это работает", "How it works": "Как это работает",
"How to help": "Как помочь?", "How to help": "Как помочь?",
"How to write a good article": "Как написать хорошую статью", "How to write 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!": "Сотни людей из разных стран и городов делятся своими знаниями и искусством на Дискурсе. Присоединяйтесь!",
"I have an account": "У меня есть аккаунт!", "I have an account": "У меня есть аккаунт!",
"I have no account yet": "У меня еще нет аккаунта", "I have no account yet": "У меня еще нет аккаунта",
"I know the password": "Я знаю пароль!", "I know the password": "Я знаю пароль!",
"Image format not supported": "Тип изображения не поддерживается", "Image format not supported": "Тип изображения не поддерживается",
"In&nbsp;bookmarks, you can save favorite discussions and&nbsp;materials that you want to return to": "В&nbsp;закладках можно сохранять избранные дискуссии и&nbsp;материалы, к&nbsp;которым хочется вернуться", "In&nbsp;bookmarks, you can save favorite discussions and&nbsp;materials that you want to return to": "В&nbsp;закладках можно сохранять избранные дискуссии и&nbsp;материалы, к&nbsp;которым хочется вернуться",
"Inbox": "Входящие",
"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": "Независимый медиапроект о культуре, науке, искусстве и обществе с горизонтальной редакцией",
"Insert footnote": "Вставить сноску", "Insert footnote": "Вставить сноску",
"Insert video link": "Вставить ссылку на видео", "Insert video link": "Вставить ссылку на видео",
"Interview": "Интервью", "Interview": "Интервью",
@ -184,6 +213,7 @@
"Invalid image URL": "Некорректная ссылка на изображение", "Invalid image URL": "Некорректная ссылка на изображение",
"Invalid url format": "Неверный формат ссылки", "Invalid url format": "Неверный формат ссылки",
"Invite co-authors": "Пригласить соавторов", "Invite co-authors": "Пригласить соавторов",
"Invite collaborators": "Пригласить соавторов",
"Invite experts": "Пригласить экспертов", "Invite experts": "Пригласить экспертов",
"Invite to collab": "Пригласить к участию", "Invite to collab": "Пригласить к участию",
"It does not look like url": "Это не похоже на ссылку", "It does not look like url": "Это не похоже на ссылку",
@ -199,16 +229,19 @@
"Last rev.": "Посл. изм.", "Last rev.": "Посл. изм.",
"Let's log in": "Давайте авторизуемся", "Let's log in": "Давайте авторизуемся",
"Link copied": "Ссылка скопирована", "Link copied": "Ссылка скопирована",
"Link copied to clipboard": "Ссылка скопирована в буфер обмена",
"Link sent, check your email": "Ссылка отправлена, проверьте почту", "Link sent, check your email": "Ссылка отправлена, проверьте почту",
"List of authors of the open editorial community": "Список авторов сообщества открытой редакции",
"Lists": "Списки", "Lists": "Списки",
"Literature": "Литература", "Literature": "Литература",
"Load more": "Показать ещё", "Load more": "Показать ещё",
"Loading": "Загрузка", "Loading": "Загрузка",
"Logout": "Выход", "Logout": "Выход",
"Looks like you forgot to upload the video": "Похоже, что вы забыли загрузить видео", "Looks like you forgot to upload the video": "Похоже, что вы забыли загрузить видео",
"Manifest": "Манифест", "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": "Отметить прочитанным",
"Material card": "Карточка материала", "Material card": "Карточка материала",
"Message": "Написать", "Message": "Написать",
"More": "Ещё", "More": "Ещё",
@ -237,10 +270,12 @@
"NotificationNewReplyText2": "от", "NotificationNewReplyText2": "от",
"NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}", "NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}",
"Notifications": "Уведомления", "Notifications": "Уведомления",
"Now you can enter a new password, it must contain at least 8 characters and not be the same as the previous password": "Теперь можете ввести новый пароль, он должен содержать минимум 8 символов и не совпадать с предыдущим паролем",
"Or paste a link to an image": "Или вставьте ссылку на изображение", "Or paste a link to an image": "Или вставьте ссылку на изображение",
"Ordered list": "Нумерованный список", "Ordered list": "Нумерованный список",
"Our regular contributor": "Наш постоянный автор", "Our regular contributor": "Наш постоянный автор",
"Paragraphs": "Абзацев", "Paragraphs": "Абзацев",
"Participate in the Discours: share information, join the editorial team": "Participate in the Discours: share information, join the editorial team",
"Participating": "Участвовать", "Participating": "Участвовать",
"Participation": "Соучастие", "Participation": "Соучастие",
"Partners": "Партнёры", "Partners": "Партнёры",
@ -249,6 +284,7 @@
"Password should be at least 8 characters": "Пароль должен быть не менее 8 символов", "Password should be at least 8 characters": "Пароль должен быть не менее 8 символов",
"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!": "Пароль обновлен!",
"Passwords are not equal": "Пароли не совпадают", "Passwords are not equal": "Пароли не совпадают",
"Paste Embed code": "Вставьте embed код", "Paste Embed code": "Вставьте embed код",
"Personal": "Личные", "Personal": "Личные",
@ -267,6 +303,7 @@
"Popular authors": "Популярные авторы", "Popular authors": "Популярные авторы",
"Preview": "Предпросмотр", "Preview": "Предпросмотр",
"Principles": "Принципы сообщества", "Principles": "Принципы сообщества",
"Professional principles that the open editorial team follows in its work": "Профессиональные принципы, которым открытая редакция следует в работе",
"Profile": "Профиль", "Profile": "Профиль",
"Profile settings": "Настройки профиля", "Profile settings": "Настройки профиля",
"Profile successfully saved": "Профиль успешно сохранён", "Profile successfully saved": "Профиль успешно сохранён",
@ -276,6 +313,7 @@
"Publish": "Опубликовать", "Publish": "Опубликовать",
"Publish Album": "Опубликовать альбом", "Publish Album": "Опубликовать альбом",
"Publish Settings": "Настройки публикации", "Publish Settings": "Настройки публикации",
"Published": "Опубликованные",
"Punchline": "Панчлайн", "Punchline": "Панчлайн",
"Quit": "Выйти", "Quit": "Выйти",
"Quote": "Цитата", "Quote": "Цитата",
@ -291,6 +329,7 @@
"Required": "Поле обязательно для заполнения", "Required": "Поле обязательно для заполнения",
"Resend code": "Выслать подтверждение", "Resend code": "Выслать подтверждение",
"Restore password": "Восстановить пароль", "Restore password": "Восстановить пароль",
"Rules of the journal Discours": "Правила журнала Дискурс",
"Save": "Сохранить", "Save": "Сохранить",
"Save draft": "Сохранить черновик", "Save draft": "Сохранить черновик",
"Save settings": "Сохранить настройки", "Save settings": "Сохранить настройки",
@ -302,11 +341,13 @@
"Sections": "Разделы", "Sections": "Разделы",
"Security": "Безопасность", "Security": "Безопасность",
"Select": "Выбрать", "Select": "Выбрать",
"Self-publishing 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": "Настройки",
"Site search": "Поиск по сайту", "Site search": "Поиск по сайту",
"Share": "Поделиться", "Share": "Поделиться",
"Share publication": "Поделиться публикацией",
"Short opening": "Расскажите вашу историю...", "Short opening": "Расскажите вашу историю...",
"Show": "Показать", "Show": "Показать",
"Show lyrics": "Текст песни", "Show lyrics": "Текст песни",
@ -319,6 +360,7 @@
"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": "Спецпроекты",
@ -339,21 +381,31 @@
"Success": "Успешно", "Success": "Успешно",
"Successfully authorized": "Авторизация успешна", "Successfully authorized": "Авторизация успешна",
"Suggest an idea": "Предложить идею", "Suggest an idea": "Предложить идею",
"Support Discours": "Поддержите Дискурс",
"Support the project": "Поддержать проект", "Support the project": "Поддержать проект",
"Support us": "Помочь журналу", "Support us": "Помочь журналу",
"Terms of use": "Правила сайта", "Terms of use": "Правила сайта",
"Text checking": "Проверка текста", "Text checking": "Проверка текста",
"Thank you": "Благодарности", "Thank you": "Благодарности",
"Thank you!": "Спасибо Вам!",
"The address is already taken": "Адрес уже занят",
"The most interesting publications on the topic": "Самые интересные публикации по теме {topicName}",
"Thematic table of contents of the magazine. Here you can find all the topics that community authors have written about.": "Тематическое оглавление журнала. Здесь можно найти все темы, о которых писали авторы сообщества.",
"Thematic table of contents of the magazine. Here you can find all the topics that the community authors wrote about": "Тематическое оглавление журнала. Здесь можно найти все темы, о которых писали авторы сообщества",
"Themes and plots": "Темы и сюжеты",
"Theory": "Теории", "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 email is already taken. If it's you": "Такой email уже зарегистрирован. Если это вы", "This email is already taken. If it's you": "Такой email уже зарегистрирован. Если это вы",
"This functionality is currently not available, we would like to work on this issue. Use the download link.": "В данный момент этот функционал не доступен, бы работаем над этой проблемой. Воспользуйтесь загрузкой по ссылке.", "This 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 post has not been rated yet": "Эту публикацию еще пока никто не оценил",
"This way we&nbsp;ll realize that you&nbsp;re a real person and&nbsp;ll take your vote into account. And&nbsp;you&nbsp;ll see how others voted": "Так мы&nbsp;поймем, что вы&nbsp;реальный человек, и&nbsp;учтем ваш голос. А&nbsp;вы&nbsp;увидите, как проголосовали другие", "This way we&nbsp;ll realize that you&nbsp;re a real person and&nbsp;ll take your vote into account. And&nbsp;you&nbsp;ll see how others voted": "Так мы&nbsp;поймем, что вы&nbsp;реальный человек, и&nbsp;учтем ваш голос. А&nbsp;вы&nbsp;увидите, как проголосовали другие",
"This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed": "Так вы&nbsp;сможете подписаться на&nbsp;авторов, интересные темы и&nbsp;настроить свою ленту", "This way you&nbsp;ll be able to subscribe to&nbsp;authors, interesting topics and&nbsp;customize your feed": "Так вы&nbsp;сможете подписаться на&nbsp;авторов, интересные темы и&nbsp;настроить свою ленту",
"To find publications, art, comments, authors and topics of interest to you, just start typing your query": "Для поиска публикаций, искусства, комментариев, интересных вам авторов и&nbsp;тем, просто начните вводить ваш запрос", "To find publications, art, comments, authors and topics of interest to you, just start typing your query": "Для поиска публикаций, искусства, комментариев, интересных вам авторов и&nbsp;тем, просто начните вводить ваш запрос",
"This week": "За неделю",
"This year": "За год",
"To leave a comment please": "Чтобы оставить комментарий, необходимо", "To leave a comment please": "Чтобы оставить комментарий, необходимо",
"To write a comment, you must": "Чтобы написать комментарий, необходимо", "To write a comment, you must": "Чтобы написать комментарий, необходимо",
"Top authors": "Рейтинг авторов", "Top authors": "Рейтинг авторов",
@ -373,6 +425,7 @@
"Unnamed draft": "Черновик без названия", "Unnamed draft": "Черновик без названия",
"Upload": "Загрузить", "Upload": "Загрузить",
"Upload error": "Ошибка загрузки", "Upload error": "Ошибка загрузки",
"Upload userpic": "Загрузить аватар",
"Upload video": "Загрузить видео", "Upload video": "Загрузить видео",
"Uploading image": "Загружаем изображение", "Uploading image": "Загружаем изображение",
"Username": "Имя пользователя", "Username": "Имя пользователя",
@ -382,6 +435,7 @@
"Video format not supported": "Тип видео не поддерживается", "Video format not supported": "Тип видео не поддерживается",
"Views": "Просмотры", "Views": "Просмотры",
"We couldn't find anything for your request": "Мы&nbsp;не&nbsp;смогли ничего найти по&nbsp;вашему запросу", "We couldn't find anything for your request": "Мы&nbsp;не&nbsp;смогли ничего найти по&nbsp;вашему запросу",
"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 know you, please try to login": "Такой адрес почты уже зарегистрирован, попробуйте залогиниться", "We know you, please try to login": "Такой адрес почты уже зарегистрирован, попробуйте залогиниться",
"We've sent you a message with a link to enter our website.": "Мы выслали вам письмо с ссылкой на почту. Перейдите по ссылке в письме, чтобы войти на сайт.", "We've sent you a message with a link to enter our website.": "Мы выслали вам письмо с ссылкой на почту. Перейдите по ссылке в письме, чтобы войти на сайт.",
@ -394,6 +448,7 @@
"Welcome to Discours to vote": "Войдите в Дискурс, чтобы голосовать", "Welcome to Discours to vote": "Войдите в Дискурс, чтобы голосовать",
"Welcome!": "Добро пожаловать!", "Welcome!": "Добро пожаловать!",
"Where": "Откуда", "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": "Слов", "Words": "Слов",
"Work with us": "Сотрудничать с Дискурсом", "Work with us": "Сотрудничать с Дискурсом",
"Write a comment...": "Написать комментарий...", "Write a comment...": "Написать комментарий...",
@ -404,7 +459,9 @@
"Write good articles, comment\nand it won't be so empty here": "Пишите хорошие статьи, комментируйте,\nи здесь станет не так пусто", "Write good articles, comment\nand it won't be so empty here": "Пишите хорошие статьи, комментируйте,\nи здесь станет не так пусто",
"Write message": "Написать сообщение", "Write message": "Написать сообщение",
"Write to us": "Напишите нам", "Write to us": "Напишите нам",
"Write your colleagues name or email": "Напишите имя или e-mail коллеги",
"You can download multiple tracks at once in .mp3, .wav or .flac formats": "Можно загрузить сразу несколько треков в форматах .mp3, .wav или .flac", "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 was successfully authorized": "Вы были успешно авторизованы", "You was successfully authorized": "Вы были успешно авторизованы",
"You&nbsp;ll be able to participate in&nbsp;discussions, rate others' comments and&nbsp;learn about&nbsp;new responses": "Вы&nbsp;сможете участвовать в&nbsp;обсуждениях, оценивать комментарии других и&nbsp;узнавать о&nbsp;новых ответах", "You&nbsp;ll be able to participate in&nbsp;discussions, rate others' comments and&nbsp;learn about&nbsp;new responses": "Вы&nbsp;сможете участвовать в&nbsp;обсуждениях, оценивать комментарии других и&nbsp;узнавать о&nbsp;новых ответах",
"You've confirmed email": "Вы подтвердили почту", "You've confirmed email": "Вы подтвердили почту",
@ -429,8 +486,8 @@
"create_chat": "Создать чат", "create_chat": "Создать чат",
"create_group": "Создать группу", "create_group": "Создать группу",
"delimiter": "разделитель", "delimiter": "разделитель",
"discourse_theme": "Тема дискурса",
"discussion": "дискурс", "discussion": "дискурс",
"dogma keywords": "Discours.io, догма, редакционные принципы, этический кодекс, журналистика, сообщество",
"drafts": "черновики", "drafts": "черновики",
"earlier": "ранее", "earlier": "ранее",
"email not confirmed": "email не подтвержден", "email not confirmed": "email не подтвержден",
@ -446,6 +503,7 @@
"italic": "курсив", "italic": "курсив",
"journal": "журнал", "journal": "журнал",
"jpg, .png, max. 10 mb.": "jpg, .png, макс. 10 мб.", "jpg, .png, max. 10 mb.": "jpg, .png, макс. 10 мб.",
"keywords": "Discours.io, журнал Дискурс, Дискурс, культура, наука, искусство, общество, независимая журналистика, литература, музыка, кино, видео, фотографии",
"literature": "литература", "literature": "литература",
"marker list": "маркир. список", "marker list": "маркир. список",
"min. 1400×1400 pix": "мин. 1400×1400 пикс.", "min. 1400×1400 pix": "мин. 1400×1400 пикс.",
@ -456,6 +514,7 @@
"or sign in with social networks": "или войдите через соцсеть", "or sign in with social networks": "или войдите через соцсеть",
"personal data usage and email notifications": "на обработку персональных данных и на получение почтовых уведомлений", "personal data usage and email notifications": "на обработку персональных данных и на получение почтовых уведомлений",
"post": "пост", "post": "пост",
"principles keywords": "Discours.io, сообщества, ценности, правила редакции, многоголосие, созидание",
"register": "зарегистрируйтесь", "register": "зарегистрируйтесь",
"repeat": "повторить", "repeat": "повторить",
"shout": "пост", "shout": "пост",
@ -468,8 +527,11 @@
"subscriber": "подписчик", "subscriber": "подписчик",
"subscriber_rp": "подписчика", "subscriber_rp": "подписчика",
"subscribers": "подписчиков", "subscribers": "подписчиков",
"subscribing...": "Подписка...",
"terms of use": "правилами пользования сайтом", "terms of use": "правилами пользования сайтом",
"terms of use keywords": "Discours.io, правила сайта, terms of use",
"today": "сегодня", "today": "сегодня",
"topicKeywords": "{topic}, Discours.io, статьи, журналистика, исследования",
"topics": "темы", "topics": "темы",
"user already exist": "пользователь уже существует", "user already exist": "пользователь уже существует",
"video": "видео", "video": "видео",

View File

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

View File

@ -1,2 +1,2 @@
User-agent: * User-agent: *
Disallow: / Allow: /

View File

@ -1,21 +1,16 @@
// FIXME: breaks on vercel, research import type { PageProps, RootSearchParams } from '../pages/types'
// import 'solid-devtools'
import { hideModal, MODALS, showModal } from '../stores/ui' import { Meta, MetaProvider } from '@solidjs/meta'
import { Component, createEffect, createMemo } from 'solid-js' import { Component, createEffect, createMemo } from 'solid-js'
import { ROUTES, useRouter } from '../stores/router'
import { Dynamic } from 'solid-js/web' import { Dynamic } from 'solid-js/web'
import type { PageProps, RootSearchParams } from '../pages/types' import { ConfirmProvider } from '../context/confirm'
import { HomePage } from '../pages/index.page' import { EditorProvider } from '../context/editor'
import { AllTopicsPage } from '../pages/allTopics.page' import { LocalizeProvider } from '../context/localize'
import { TopicPage } from '../pages/topic.page' import { MediaQueryProvider } from '../context/mediaQuery'
import { AllAuthorsPage } from '../pages/allAuthors.page' import { NotificationsProvider } from '../context/notifications'
import { AuthorPage } from '../pages/author.page' import { SessionProvider } from '../context/session'
import { FeedPage } from '../pages/feed.page' import { SnackbarProvider } from '../context/snackbar'
import { ArticlePage } from '../pages/article.page'
import { SearchPage } from '../pages/search.page'
import { FourOuFourPage } from '../pages/fourOuFour.page'
import { DiscussionRulesPage } from '../pages/about/discussionRules.page' import { DiscussionRulesPage } from '../pages/about/discussionRules.page'
import { DogmaPage } from '../pages/about/dogma.page' import { DogmaPage } from '../pages/about/dogma.page'
import { GuidePage } from '../pages/about/guide.page' import { GuidePage } from '../pages/about/guide.page'
@ -26,21 +21,26 @@ import { PrinciplesPage } from '../pages/about/principles.page'
import { ProjectsPage } from '../pages/about/projects.page' import { ProjectsPage } from '../pages/about/projects.page'
import { TermsOfUsePage } from '../pages/about/termsOfUse.page' import { TermsOfUsePage } from '../pages/about/termsOfUse.page'
import { ThanksPage } from '../pages/about/thanks.page' import { ThanksPage } from '../pages/about/thanks.page'
import { CreatePage } from '../pages/create.page' import { AllAuthorsPage } from '../pages/allAuthors.page'
import { EditPage } from '../pages/edit.page' import { AllTopicsPage } from '../pages/allTopics.page'
import { ArticlePage } from '../pages/article.page'
import { AuthorPage } from '../pages/author.page'
import { ConnectPage } from '../pages/connect.page' import { ConnectPage } from '../pages/connect.page'
import { InboxPage } from '../pages/inbox.page' import { CreatePage } from '../pages/create.page'
import { ExpoPage } from '../pages/expo/expo.page'
import { SessionProvider } from '../context/session'
import { ProfileSettingsPage } from '../pages/profile/profileSettings.page'
import { ProfileSecurityPage } from '../pages/profile/profileSecurity.page'
import { ProfileSubscriptionsPage } from '../pages/profile/profileSubscriptions.page'
import { DraftsPage } from '../pages/drafts.page' import { DraftsPage } from '../pages/drafts.page'
import { SnackbarProvider } from '../context/snackbar' import { EditPage } from '../pages/edit.page'
import { LocalizeProvider } from '../context/localize' import { ExpoPage } from '../pages/expo/expo.page'
import { ConfirmProvider } from '../context/confirm' import { FeedPage } from '../pages/feed.page'
import { EditorProvider } from '../context/editor' import { FourOuFourPage } from '../pages/fourOuFour.page'
import { NotificationsProvider } from '../context/notifications' import { InboxPage } from '../pages/inbox.page'
import { HomePage } from '../pages/index.page'
import { ProfileSecurityPage } from '../pages/profile/profileSecurity.page'
import { ProfileSettingsPage } from '../pages/profile/profileSettings.page'
import { ProfileSubscriptionsPage } from '../pages/profile/profileSubscriptions.page'
import { SearchPage } from '../pages/search.page'
import { TopicPage } from '../pages/topic.page'
import { ROUTES, useRouter } from '../stores/router'
import { hideModal, MODALS, showModal } from '../stores/ui'
// TODO: lazy load // TODO: lazy load
// const SomePage = lazy(() => import('./Pages/SomePage')) // const SomePage = lazy(() => import('./Pages/SomePage'))
@ -51,7 +51,6 @@ const pagesMap: Record<keyof typeof ROUTES, Component<PageProps>> = {
authorAbout: AuthorPage, authorAbout: AuthorPage,
inbox: InboxPage, inbox: InboxPage,
expo: ExpoPage, expo: ExpoPage,
expoLayout: ExpoPage,
connect: ConnectPage, connect: ConnectPage,
create: CreatePage, create: CreatePage,
edit: EditPage, edit: EditPage,
@ -82,11 +81,14 @@ const pagesMap: Record<keyof typeof ROUTES, Component<PageProps>> = {
profileSettings: ProfileSettingsPage, profileSettings: ProfileSettingsPage,
profileSecurity: ProfileSecurityPage, profileSecurity: ProfileSecurityPage,
profileSubscriptions: ProfileSubscriptionsPage, profileSubscriptions: ProfileSubscriptionsPage,
fourOuFour: FourOuFourPage fourOuFour: FourOuFourPage,
} }
export const App = (props: PageProps) => { type Props = PageProps & { is404: boolean }
export const App = (props: Props) => {
const { page, searchParams } = useRouter<RootSearchParams>() const { page, searchParams } = useRouter<RootSearchParams>()
let is404 = props.is404
createEffect(() => { createEffect(() => {
if (!searchParams().modal) { if (!searchParams().modal) {
@ -102,7 +104,8 @@ export const App = (props: PageProps) => {
const pageComponent = createMemo(() => { const pageComponent = createMemo(() => {
const result = pagesMap[page()?.route || 'home'] const result = pagesMap[page()?.route || 'home']
if (!result || page()?.path === '/404') { if (is404 || !result || page()?.path === '/404') {
is404 = false
return FourOuFourPage return FourOuFourPage
} }
@ -110,18 +113,23 @@ export const App = (props: PageProps) => {
}) })
return ( return (
<LocalizeProvider> <MetaProvider>
<SnackbarProvider> <Meta name="viewport" content="width=device-width, initial-scale=1" />
<ConfirmProvider> <LocalizeProvider>
<SessionProvider> <MediaQueryProvider>
<NotificationsProvider> <SnackbarProvider>
<EditorProvider> <ConfirmProvider>
<Dynamic component={pageComponent()} {...props} /> <SessionProvider>
</EditorProvider> <NotificationsProvider>
</NotificationsProvider> <EditorProvider>
</SessionProvider> <Dynamic component={pageComponent()} {...props} />
</ConfirmProvider> </EditorProvider>
</SnackbarProvider> </NotificationsProvider>
</LocalizeProvider> </SessionProvider>
</ConfirmProvider>
</SnackbarProvider>
</MediaQueryProvider>
</LocalizeProvider>
</MetaProvider>
) )
} }

View File

@ -1,11 +1,13 @@
h1 { h1 {
@include font-size(4rem); @include font-size(4rem);
line-height: 1.1; line-height: 1.1;
margin-top: 0.5em; margin-top: 0.5em;
} }
h2 { h2 {
@include font-size(4rem); @include font-size(4rem);
line-height: 1.1; line-height: 1.1;
} }
@ -14,30 +16,22 @@ img {
} }
.shoutHeader { .shoutHeader {
margin-bottom: 2em; margin-bottom: 1.5em;
@include media-breakpoint-up(md) {
margin: 0 0 2em;
}
} }
.shoutCover { .articleContent {
background-size: cover; img:not([data-disable-lightbox='true']) {
height: 0; cursor: zoom-in;
padding-bottom: 56.2%; }
} }
.shoutBody { .shoutBody {
font-size: 1.6rem; font-size: 1.6rem;
line-height: 1.6; line-height: 1.6;
img {
display: block;
margin-bottom: 0.5em;
}
blockquote, blockquote,
blockquote[data-type='punchline'] { blockquote[data-type='punchline'] {
clear: both;
font-size: 2.6rem; font-size: 2.6rem;
font-weight: bold; font-weight: bold;
line-height: 1.4; line-height: 1.4;
@ -61,6 +55,7 @@ img {
ta-quotation { ta-quotation {
border: solid #000; border: solid #000;
border-width: 0 0 0 2px; border-width: 0 0 0 2px;
clear: both;
display: block; display: block;
font-weight: 500; font-weight: 500;
line-height: 1.6; line-height: 1.6;
@ -70,7 +65,12 @@ img {
&[data-float='left'], &[data-float='left'],
&[data-float='right'] { &[data-float='right'] {
@include font-size(2.2rem); @include font-size(2.2rem);
line-height: 1.4; line-height: 1.4;
@include media-breakpoint-up(sm) {
clear: none;
}
} }
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
@ -92,9 +92,11 @@ img {
ta-sub, ta-sub,
ta-selection-frame, ta-selection-frame,
ta-border-sub { ta-border-sub {
background: #f1f2f3;
display: block;
@include font-size(1.4rem); @include font-size(1.4rem);
background: #f1f2f3;
clear: both;
display: block;
margin: 3.2rem 0; margin: 3.2rem 0;
padding: 3.2rem; padding: 3.2rem;
@ -108,6 +110,10 @@ img {
} }
} }
ta-sub {
font-size: inherit;
}
*[data-bg='black'] { *[data-bg='black'] {
background: #000; background: #000;
color: #fff; color: #fff;
@ -173,15 +179,17 @@ img {
:global(.img-align-left) { :global(.img-align-left) {
float: left; float: left;
margin: 1em 8.3333% 1.5em 0; margin: 0 8.3333% 1.5em 0;
} }
:global(.width-30) { @include media-breakpoint-up(sm) {
width: 30%; :global(.width-30) {
} width: 30%;
}
:global(.width-50) { :global(.width-50) {
width: 50%; width: 50%;
}
} }
:global(.img-align-left.width-50) { :global(.img-align-left.width-50) {
@ -191,13 +199,15 @@ img {
} }
:global(.img-align-right) { :global(.img-align-right) {
float: right; @include media-breakpoint-up(sm) {
margin: 1em 0 1.5em 8.3333%; float: right;
margin: 1em 0 1.5em 8.3333%;
}
} }
:global(.img-align-right.width-50) { :global(.img-align-right.width-50) {
@include media-breakpoint-up(xl) { @include media-breakpoint-up(xl) {
margin-right: -16.6666%; margin-right: -8.3333%;
} }
} }
@ -295,22 +305,24 @@ img {
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
padding: 3rem 0 0; padding: 3rem 0 0;
position: relative;
@include media-breakpoint-down(sm) { @include media-breakpoint-down(lg) {
flex-wrap: wrap; flex-wrap: wrap;
} }
} }
.shoutStatsItem { .shoutStatsItem {
@include font-size(1.5rem); @include font-size(1.5rem);
align-items: center; align-items: center;
font-weight: 500; font-weight: 500;
display: flex; display: flex;
margin: 0 6% 1em 0; margin: 0 2rem 1em 0;
vertical-align: baseline; vertical-align: baseline;
cursor: pointer; cursor: pointer;
@include media-breakpoint-up(sm) { @include media-breakpoint-up(xl) {
margin-right: 3.2rem; margin-right: 3.2rem;
} }
@ -354,6 +366,14 @@ img {
} }
} }
.shoutStatsItemBookmarks {
margin-left: auto;
@include media-breakpoint-up(lg) {
margin-left: 0;
}
}
.shoutStatsItemInner { .shoutStatsItemInner {
cursor: pointer; cursor: pointer;
@ -377,32 +397,41 @@ img {
.shoutStatsItemAdditionalData { .shoutStatsItemAdditionalData {
color: rgb(0 0 0 / 40%); color: rgb(0 0 0 / 40%);
cursor: default;
font-weight: normal; font-weight: normal;
justify-self: flex-end; justify-self: flex-end;
margin-right: 0;
margin-left: auto;
white-space: nowrap; white-space: nowrap;
cursor: default;
.icon { .icon {
opacity: 0.4; opacity: 0.4;
height: 2rem; height: 2rem;
} }
@include media-breakpoint-down(sm) { @include media-breakpoint-down(lg) {
flex: 1 40%; flex: 1 100%;
order: 9;
.shoutStatsItemAdditionalDataItem {
margin-left: 0;
}
} }
} }
.shoutStatsItemViews { .shoutStatsItemViews {
color: rgb(0 0 0 / 40%);
cursor: default; cursor: default;
font-weight: normal;
margin-left: auto;
white-space: nowrap;
@include media-breakpoint-down(sm) { @include media-breakpoint-down(lg) {
color: rgb(0 0 0 / 0.4); bottom: 0;
flex: 1 40%; flex: 1 40%;
justify-content: end; justify-content: end;
margin-right: 0; margin-right: 0;
order: 10; order: 10;
position: absolute;
right: 0;
.icon { .icon {
display: none !important; display: none !important;
@ -411,15 +440,20 @@ img {
} }
.shoutStatsItemLabel { .shoutStatsItemLabel {
font-weight: normal;
margin-left: 0.3em; margin-left: 0.3em;
}
.commentsTextLabel {
display: none;
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
display: none; display: block;
} }
} }
.shoutStatsItemCount { .shoutStatsItemCount {
@include media-breakpoint-down(sm) { @include media-breakpoint-down(lg) {
display: none; display: none;
} }
} }
@ -427,7 +461,8 @@ img {
.shoutStatsItemAdditionalDataItem { .shoutStatsItemAdditionalDataItem {
font-weight: normal; font-weight: normal;
display: inline-block; display: inline-block;
margin-left: 2rem;
// margin-left: 2rem;
margin-right: 0; margin-right: 0;
margin-bottom: 0; margin-bottom: 0;
cursor: default; cursor: default;
@ -441,6 +476,7 @@ img {
.topicsList { .topicsList {
@include font-size(1.2rem); @include font-size(1.2rem);
border-bottom: 1px solid #e8e8e8; border-bottom: 1px solid #e8e8e8;
letter-spacing: 0.08em; letter-spacing: 0.08em;
margin-top: 1.6rem; margin-top: 1.6rem;
@ -480,12 +516,15 @@ img {
} }
.commentsHeaderWrapper { .commentsHeaderWrapper {
display: flex; @include media-breakpoint-up(sm) {
justify-content: space-between; display: flex;
justify-content: space-between;
}
} }
.commentsHeader { .commentsHeader {
@include font-size(2.4rem); @include font-size(2.4rem);
margin-bottom: 1em; margin-bottom: 1em;
.newReactions { .newReactions {
@ -519,6 +558,7 @@ img {
button { button {
@include font-size(1.5rem); @include font-size(1.5rem);
border-radius: 0.8rem; border-radius: 0.8rem;
margin-right: 1.2rem; margin-right: 1.2rem;
padding: 0.9rem 1.2rem; padding: 0.9rem 1.2rem;
@ -600,13 +640,14 @@ a[data-toggle='tooltip'] {
width: 0; width: 0;
height: 0; height: 0;
border-style: solid; border-style: solid;
border-width: 4px 4px 0 4px; border-width: 4px 4px 0;
border-color: var(--black-500) transparent transparent transparent; border-color: var(--black-500) transparent transparent transparent;
} }
} }
.lead { .lead {
@include font-size(1.8rem); @include font-size(1.8rem);
font-weight: 600; font-weight: 600;
b, b,
@ -614,3 +655,19 @@ a[data-toggle='tooltip'] {
font-weight: 700; font-weight: 700;
} }
} }
.articlePopupOpener {
.iconHover {
display: none;
}
&:hover {
.icon {
display: none;
}
.iconHover {
display: inline-block;
}
}
}

View File

@ -1,11 +1,13 @@
import { clsx } from 'clsx' import { clsx } from 'clsx'
import styles from './AudioHeader.module.scss'
import { MediaItem } from '../../../pages/types'
import { createSignal, Show } from 'solid-js' import { createSignal, Show } from 'solid-js'
import { Icon } from '../../_shared/Icon'
import { Topic } from '../../../graphql/types.gen' import { Topic } from '../../../graphql/types.gen'
import { CardTopic } from '../../Feed/CardTopic' import { MediaItem } from '../../../pages/types'
import { Icon } from '../../_shared/Icon'
import { Image } from '../../_shared/Image' import { Image } from '../../_shared/Image'
import { CardTopic } from '../../Feed/CardTopic'
import styles from './AudioHeader.module.scss'
type Props = { type Props = {
title: string title: string
@ -19,7 +21,7 @@ export const AudioHeader = (props: Props) => {
return ( return (
<div class={clsx(styles.AudioHeader, { [styles.expandedImage]: expandedImage() })}> <div class={clsx(styles.AudioHeader, { [styles.expandedImage]: expandedImage() })}>
<div class={styles.cover}> <div class={styles.cover}>
<Image class={styles.image} src={props.cover} alt={props.title} width={200} /> <Image class={styles.image} src={props.cover} alt={props.title} width={100} />
<Show when={props.cover}> <Show when={props.cover}>
<button type="button" class={styles.expand} onClick={() => setExpandedImage(!expandedImage())}> <button type="button" class={styles.expand} onClick={() => setExpandedImage(!expandedImage())}>
<Icon name="expand-circle" /> <Icon name="expand-circle" />

View File

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

View File

@ -1,8 +1,11 @@
import { createEffect, createMemo, createSignal, on, onMount, Show } from 'solid-js' import { createEffect, createMemo, createSignal, on, onMount, Show } from 'solid-js'
import { MediaItem } from '../../../pages/types'
import { PlayerHeader } from './PlayerHeader' import { PlayerHeader } from './PlayerHeader'
import { PlayerPlaylist } from './PlayerPlaylist' import { PlayerPlaylist } from './PlayerPlaylist'
import styles from './AudioPlayer.module.scss' import styles from './AudioPlayer.module.scss'
import { MediaItem } from '../../../pages/types'
type Props = { type Props = {
media: MediaItem[] media: MediaItem[]
@ -35,8 +38,8 @@ export const AudioPlayer = (props: Props) => {
() => { () => {
setCurrentTrackDuration(0) setCurrentTrackDuration(0)
}, },
{ defer: true } { defer: true },
) ),
) )
const handlePlayMedia = async (trackIndex: number) => { const handlePlayMedia = async (trackIndex: number) => {
@ -131,7 +134,7 @@ export const AudioPlayer = (props: Props) => {
<div <div
class={styles.progressFilled} class={styles.progressFilled}
style={{ style={{
width: `${(currentTime() / currentTrackDuration()) * 100 || 0}%` width: `${(currentTime() / currentTrackDuration()) * 100 || 0}%`,
}} }}
/> />
</div> </div>

View File

@ -1,11 +1,11 @@
import { createSignal, Show } from 'solid-js'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { createSignal, Show } from 'solid-js'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import { Icon } from '../../_shared/Icon'
import styles from './AudioPlayer.module.scss'
import { MediaItem } from '../../../pages/types' import { MediaItem } from '../../../pages/types'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import { Icon } from '../../_shared/Icon'
import styles from './AudioPlayer.module.scss'
type Props = { type Props = {
onPlayMedia: () => void onPlayMedia: () => void
@ -18,7 +18,7 @@ type Props = {
export const PlayerHeader = (props: Props) => { export const PlayerHeader = (props: Props) => {
const volumeContainerRef: { current: HTMLDivElement } = { const volumeContainerRef: { current: HTMLDivElement } = {
current: null current: null,
} }
const [isVolumeBarOpened, setIsVolumeBarOpened] = createSignal(false) const [isVolumeBarOpened, setIsVolumeBarOpened] = createSignal(false)
@ -30,7 +30,7 @@ export const PlayerHeader = (props: Props) => {
useOutsideClickHandler({ useOutsideClickHandler({
containerRef: volumeContainerRef, containerRef: volumeContainerRef,
predicate: () => isVolumeBarOpened(), predicate: () => isVolumeBarOpened(),
handler: () => toggleVolumeBar() handler: () => toggleVolumeBar(),
}) })
return ( return (
@ -42,7 +42,7 @@ export const PlayerHeader = (props: Props) => {
onClick={props.onPlayMedia} onClick={props.onPlayMedia}
class={clsx( class={clsx(
styles.playButton, styles.playButton,
props.isPlaying ? styles.playButtonInvertPause : styles.playButtonInvertPlay props.isPlaying ? styles.playButtonInvertPause : styles.playButtonInvertPlay,
)} )}
aria-label="Play" aria-label="Play"
data-playing="false" data-playing="false"

View File

@ -1,14 +1,16 @@
import { createSignal, For, Show } from 'solid-js' import { createSignal, For, lazy, Show } from 'solid-js'
import { SharePopup, getShareUrl } from '../SharePopup'
import { getDescription } from '../../../utils/meta'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { Popover } from '../../_shared/Popover'
import { Icon } from '../../_shared/Icon'
import styles from './AudioPlayer.module.scss'
import { GrowingTextarea } from '../../_shared/GrowingTextarea'
import MD from '../MD'
import { MediaItem } from '../../../pages/types' import { MediaItem } from '../../../pages/types'
import SimplifiedEditor from '../../Editor/SimplifiedEditor' import { getDescription } from '../../../utils/meta'
import { Icon } from '../../_shared/Icon'
import { Popover } from '../../_shared/Popover'
import { SharePopup, getShareUrl } from '../SharePopup'
import styles from './AudioPlayer.module.scss'
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
const GrowingTextarea = lazy(() => import('../../_shared/GrowingTextarea/GrowingTextarea'))
type Props = { type Props = {
media: MediaItem[] media: MediaItem[]
@ -146,12 +148,12 @@ export const PlayerPlaylist = (props: Props) => {
<div class={styles.descriptionBlock}> <div class={styles.descriptionBlock}>
<Show when={mi.body}> <Show when={mi.body}>
<div class={styles.description}> <div class={styles.description}>
<MD body={mi.body} /> <div innerHTML={mi.body} />
</div> </div>
</Show> </Show>
<Show when={mi.lyrics}> <Show when={mi.lyrics}>
<div class={styles.lyrics}> <div class={styles.lyrics}>
<MD body={mi.lyrics} /> <div innerHTML={mi.lyrics} />
</div> </div>
</Show> </Show>
</div> </div>

View File

@ -5,8 +5,8 @@
position: relative; position: relative;
list-style: none; list-style: none;
& .comment { @include media-breakpoint-down(sm) {
margin-right: -1rem; padding-right: 0;
} }
&.isNew { &.isNew {
@ -14,11 +14,9 @@
background: rgb(38 56 217 / 5%); background: rgb(38 56 217 / 5%);
} }
@include media-breakpoint-down(sm) {
margin-right: -1.2rem;
}
.comment { .comment {
margin-right: -1rem;
&::before, &::before,
&::after { &::after {
content: ''; content: '';
@ -68,6 +66,8 @@
} }
.commentContent { .commentContent {
padding: 0 1rem 1rem 0;
&:hover { &:hover {
.commentControlReply, .commentControlReply,
.commentControlShare, .commentControlShare,
@ -78,6 +78,10 @@
opacity: 1; opacity: 1;
} }
} }
p:last-child {
margin-bottom: 0;
}
} }
.commentControlReply, .commentControlReply,

View File

@ -1,25 +1,22 @@
import { Show, createMemo, createSignal, For, lazy, Suspense } from 'solid-js'
import { clsx } from 'clsx'
import { getPagePath } from '@nanostores/router' import { getPagePath } from '@nanostores/router'
import { clsx } from 'clsx'
import { Show, createMemo, createSignal, For, lazy, Suspense } from 'solid-js'
import MD from '../MD' import { useConfirm } from '../../../context/confirm'
import { Userpic } from '../../Author/Userpic'
import { CommentRatingControl } from '../CommentRatingControl'
import { CommentDate } from '../CommentDate'
import { ShowIfAuthenticated } from '../../_shared/ShowIfAuthenticated'
import { Icon } from '../../_shared/Icon'
import { useSession } from '../../../context/session'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { useReactions } from '../../../context/reactions' import { useReactions } from '../../../context/reactions'
import { useSession } from '../../../context/session'
import { useSnackbar } from '../../../context/snackbar' import { useSnackbar } from '../../../context/snackbar'
import { useConfirm } from '../../../context/confirm'
import { Author, Reaction, ReactionKind } from '../../../graphql/types.gen' import { Author, Reaction, ReactionKind } from '../../../graphql/types.gen'
import { router } from '../../../stores/router' import { router } from '../../../stores/router'
import { Icon } from '../../_shared/Icon'
import { ShowIfAuthenticated } from '../../_shared/ShowIfAuthenticated'
import { AuthorLink } from '../../Author/AhtorLink'
import { Userpic } from '../../Author/Userpic'
import { CommentDate } from '../CommentDate'
import { CommentRatingControl } from '../CommentRatingControl'
import styles from './Comment.module.scss' import styles from './Comment.module.scss'
import { AuthorLink } from '../../Author/AhtorLink'
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
@ -44,15 +41,15 @@ export const Comment = (props: Props) => {
const { session } = useSession() const { session } = useSession()
const { const {
actions: { createReaction, deleteReaction, updateReaction } actions: { createReaction, deleteReaction, updateReaction },
} = useReactions() } = useReactions()
const { const {
actions: { showConfirm } actions: { showConfirm },
} = useConfirm() } = useConfirm()
const { const {
actions: { showSnackbar } actions: { showSnackbar },
} = useSnackbar() } = useSnackbar()
const isCommentAuthor = createMemo(() => props.comment.createdBy?.slug === session()?.user?.slug) const isCommentAuthor = createMemo(() => props.comment.createdBy?.slug === session()?.user?.slug)
@ -66,7 +63,7 @@ export const Comment = (props: Props) => {
confirmBody: t('Are you sure you want to delete this comment?'), confirmBody: t('Are you sure you want to delete this comment?'),
confirmButtonLabel: t('Delete'), confirmButtonLabel: t('Delete'),
confirmButtonVariant: 'danger', confirmButtonVariant: 'danger',
declineButtonVariant: 'primary' declineButtonVariant: 'primary',
}) })
if (isConfirmed) { if (isConfirmed) {
@ -87,7 +84,7 @@ export const Comment = (props: Props) => {
kind: ReactionKind.Comment, kind: ReactionKind.Comment,
replyTo: props.comment.id, replyTo: props.comment.id,
body: value, body: value,
shout: props.comment.shout.id shout: props.comment.shout.id,
}) })
setClearEditor(true) setClearEditor(true)
setIsReplyVisible(false) setIsReplyVisible(false)
@ -108,7 +105,7 @@ export const Comment = (props: Props) => {
await updateReaction(props.comment.id, { await updateReaction(props.comment.id, {
kind: ReactionKind.Comment, kind: ReactionKind.Comment,
body: value, body: value,
shout: props.comment.shout.id shout: props.comment.shout.id,
}) })
setEditMode(false) setEditMode(false)
setLoading(false) setLoading(false)
@ -123,7 +120,7 @@ export const Comment = (props: Props) => {
<li <li
id={`comment_${comment().id}`} id={`comment_${comment().id}`}
class={clsx(styles.comment, props.class, { class={clsx(styles.comment, props.class, {
[styles.isNew]: !isCommentAuthor() && createdAt > props.lastSeen [styles.isNew]: !isCommentAuthor() && createdAt > props.lastSeen,
})} })}
> >
<Show when={!!body()}> <Show when={!!body()}>
@ -136,7 +133,7 @@ export const Comment = (props: Props) => {
name={comment().createdBy.name} name={comment().createdBy.name}
userpic={comment().createdBy.userpic} userpic={comment().createdBy.userpic}
class={clsx({ class={clsx({
[styles.compactUserpic]: props.compact [styles.compactUserpic]: props.compact,
})} })}
/> />
<small> <small>
@ -171,7 +168,7 @@ export const Comment = (props: Props) => {
</div> </div>
</Show> </Show>
<div class={styles.commentBody}> <div class={styles.commentBody}>
<Show when={editMode()} fallback={<MD body={body()} />}> <Show when={editMode()} fallback={<div innerHTML={body()} />}>
<Suspense fallback={<p>{t('Loading')}</p>}> <Suspense fallback={<p>{t('Loading')}</p>}>
<SimplifiedEditor <SimplifiedEditor
initialContent={comment().body} initialContent={comment().body}

View File

@ -1,8 +1,11 @@
import { Show } from 'solid-js'
import { Icon } from '../../_shared/Icon'
import type { Reaction } from '../../../graphql/types.gen' import type { Reaction } from '../../../graphql/types.gen'
import { useLocalize } from '../../../context/localize'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { Icon } from '../../_shared/Icon'
import styles from './CommentDate.module.scss' import styles from './CommentDate.module.scss'
type Props = { type Props = {
@ -27,7 +30,7 @@ export const CommentDate = (props: Props) => {
<div <div
class={clsx(styles.commentDates, { class={clsx(styles.commentDates, {
[styles.commentDatesLastInRow]: props.isLastInRow, [styles.commentDatesLastInRow]: props.isLastInRow,
[styles.showOnHover]: props.showOnHover [styles.showOnHover]: props.showOnHover,
})} })}
> >
<time class={styles.date}>{formattedDate(props.comment.createdAt)}</time> <time class={styles.date}>{formattedDate(props.comment.createdAt)}</time>

View File

@ -1,16 +1,19 @@
import { clsx } from 'clsx'
import styles from './CommentRatingControl.module.scss'
import type { Reaction } from '../../graphql/types.gen' import type { Reaction } from '../../graphql/types.gen'
import { ReactionKind } from '../../graphql/types.gen'
import { useSession } from '../../context/session' import { clsx } from 'clsx'
import { useReactions } from '../../context/reactions'
import { createMemo } from 'solid-js' import { createMemo } from 'solid-js'
import { useLocalize } from '../../context/localize'
import { useReactions } from '../../context/reactions'
import { useSession } from '../../context/session'
import { useSnackbar } from '../../context/snackbar'
import { ReactionKind } from '../../graphql/types.gen'
import { loadShout } from '../../stores/zine/articles' import { loadShout } from '../../stores/zine/articles'
import { Popup } from '../_shared/Popup' import { Popup } from '../_shared/Popup'
import { useLocalize } from '../../context/localize'
import { useSnackbar } from '../../context/snackbar'
import { VotersList } from '../_shared/VotersList' import { VotersList } from '../_shared/VotersList'
import styles from './CommentRatingControl.module.scss'
type Props = { type Props = {
comment: Reaction comment: Reaction
} }
@ -19,11 +22,11 @@ export const CommentRatingControl = (props: Props) => {
const { t } = useLocalize() const { t } = useLocalize()
const { user } = useSession() const { user } = useSession()
const { const {
actions: { showSnackbar } actions: { showSnackbar },
} = useSnackbar() } = useSnackbar()
const { const {
reactionEntities, reactionEntities,
actions: { createReaction, deleteReaction, loadReactionsBy } actions: { createReaction, deleteReaction, loadReactionsBy },
} = useReactions() } = useReactions()
const checkReaction = (reactionKind: ReactionKind) => const checkReaction = (reactionKind: ReactionKind) =>
@ -32,7 +35,7 @@ export const CommentRatingControl = (props: Props) => {
r.kind === reactionKind && r.kind === reactionKind &&
r.createdBy.slug === user()?.slug && r.createdBy.slug === user()?.slug &&
r.shout.id === props.comment.shout.id && r.shout.id === props.comment.shout.id &&
r.replyTo === props.comment.id r.replyTo === props.comment.id,
) )
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like)) const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
const isDownvoted = createMemo(() => checkReaction(ReactionKind.Dislike)) const isDownvoted = createMemo(() => checkReaction(ReactionKind.Dislike))
@ -43,8 +46,8 @@ export const CommentRatingControl = (props: Props) => {
(r) => (r) =>
[ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) && [ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) &&
r.shout.id === props.comment.shout.id && r.shout.id === props.comment.shout.id &&
r.replyTo === props.comment.id r.replyTo === props.comment.id,
) ),
) )
const deleteCommentReaction = async (reactionKind: ReactionKind) => { const deleteCommentReaction = async (reactionKind: ReactionKind) => {
@ -53,7 +56,7 @@ export const CommentRatingControl = (props: Props) => {
r.kind === reactionKind && r.kind === reactionKind &&
r.createdBy.slug === user()?.slug && r.createdBy.slug === user()?.slug &&
r.shout.id === props.comment.shout.id && r.shout.id === props.comment.shout.id &&
r.replyTo === props.comment.id r.replyTo === props.comment.id,
) )
return deleteReaction(reactionToDelete.id) return deleteReaction(reactionToDelete.id)
} }
@ -68,7 +71,7 @@ export const CommentRatingControl = (props: Props) => {
await createReaction({ await createReaction({
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike, kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
shout: props.comment.shout.id, shout: props.comment.shout.id,
replyTo: props.comment.id replyTo: props.comment.id,
}) })
} }
} catch { } catch {
@ -77,7 +80,7 @@ export const CommentRatingControl = (props: Props) => {
await loadShout(props.comment.shout.slug) await loadShout(props.comment.shout.slug)
await loadReactionsBy({ await loadReactionsBy({
by: { shout: props.comment.shout.slug } by: { shout: props.comment.shout.slug },
}) })
} }
@ -88,7 +91,7 @@ export const CommentRatingControl = (props: Props) => {
disabled={!canVote() || !user()} disabled={!canVote() || !user()}
onClick={() => handleRatingChange(true)} onClick={() => handleRatingChange(true)}
class={clsx(styles.commentRatingControl, styles.commentRatingControlUp, { class={clsx(styles.commentRatingControl, styles.commentRatingControlUp, {
[styles.voted]: isUpvoted() [styles.voted]: isUpvoted(),
})} })}
/> />
<Popup <Popup
@ -96,7 +99,7 @@ export const CommentRatingControl = (props: Props) => {
<div <div
class={clsx(styles.commentRatingValue, { class={clsx(styles.commentRatingValue, {
[styles.commentRatingPositive]: props.comment.stat.rating > 0, [styles.commentRatingPositive]: props.comment.stat.rating > 0,
[styles.commentRatingNegative]: props.comment.stat.rating < 0 [styles.commentRatingNegative]: props.comment.stat.rating < 0,
})} })}
> >
{props.comment.stat.rating || 0} {props.comment.stat.rating || 0}
@ -114,7 +117,7 @@ export const CommentRatingControl = (props: Props) => {
disabled={!canVote() || !user()} disabled={!canVote() || !user()}
onClick={() => handleRatingChange(false)} onClick={() => handleRatingChange(false)}
class={clsx(styles.commentRatingControl, styles.commentRatingControlDown, { class={clsx(styles.commentRatingControl, styles.commentRatingControlDown, {
[styles.voted]: isDownvoted() [styles.voted]: isDownvoted(),
})} })}
/> />
</div> </div>

View File

@ -1,15 +1,19 @@
import { Show, createMemo, createSignal, onMount, For } from 'solid-js'
import { Comment } from './Comment'
import styles from './Article.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { Author, Reaction, ReactionKind } from '../../graphql/types.gen' import { Show, createMemo, createSignal, onMount, For, lazy } from 'solid-js'
import { useSession } from '../../context/session'
import { Button } from '../_shared/Button'
import { useReactions } from '../../context/reactions'
import { byCreated } from '../../utils/sortby'
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import SimplifiedEditor from '../Editor/SimplifiedEditor' import { useReactions } from '../../context/reactions'
import { useSession } from '../../context/session'
import { Author, Reaction, ReactionKind } from '../../graphql/types.gen'
import { byCreated } from '../../utils/sortby'
import { Button } from '../_shared/Button'
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
import { Comment } from './Comment'
import styles from './Article.module.scss'
const SimplifiedEditor = lazy(() => import('../Editor/SimplifiedEditor'))
type CommentsOrder = 'createdAt' | 'rating' | 'newOnly' type CommentsOrder = 'createdAt' | 'rating' | 'newOnly'
@ -48,11 +52,11 @@ export const CommentsTree = (props: Props) => {
const { const {
reactionEntities, reactionEntities,
actions: { createReaction } actions: { createReaction },
} = useReactions() } = useReactions()
const comments = createMemo(() => const comments = createMemo(() =>
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT') Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT'),
) )
const sortedComments = createMemo(() => { const sortedComments = createMemo(() => {
@ -96,7 +100,7 @@ export const CommentsTree = (props: Props) => {
await createReaction({ await createReaction({
kind: ReactionKind.Comment, kind: ReactionKind.Comment,
body: value, body: value,
shout: props.shoutId shout: props.shoutId,
}) })
setClearEditor(true) setClearEditor(true)
} catch (error) { } catch (error) {
@ -154,7 +158,7 @@ export const CommentsTree = (props: Props) => {
<Comment <Comment
sortedComments={sortedComments()} sortedComments={sortedComments()}
isArticleAuthor={Boolean( isArticleAuthor={Boolean(
props.articleAuthors.some((a) => a.slug === reaction.createdBy.slug) props.articleAuthors.some((a) => a.slug === reaction.createdBy.slug),
)} )}
comment={reaction} comment={reaction}
clickedReply={(id) => setClickedReplyId(id)} clickedReply={(id) => setClickedReplyId(id)}

View File

@ -0,0 +1,40 @@
import type { CoverImageProps } from './types'
import { CoverImage1 } from './images/CoverImage1'
import { CoverImage10 } from './images/CoverImage10'
import { CoverImage11 } from './images/CoverImage11'
import { CoverImage12 } from './images/CoverImage12'
import { CoverImage2 } from './images/CoverImage2'
import { CoverImage3 } from './images/CoverImage3'
import { CoverImage4 } from './images/CoverImage4'
import { CoverImage5 } from './images/CoverImage5'
import { CoverImage6 } from './images/CoverImage6'
import { CoverImage7 } from './images/CoverImage7'
import { CoverImage8 } from './images/CoverImage8'
import { CoverImage9 } from './images/CoverImage9'
// not pretty, but I don't want to use dynamic imports
const coverImages = [
CoverImage1,
CoverImage2,
CoverImage3,
CoverImage4,
CoverImage5,
CoverImage6,
CoverImage7,
CoverImage8,
CoverImage9,
CoverImage10,
CoverImage11,
CoverImage12,
]
let counter = 0
export const CoverImage = (props: CoverImageProps) => {
const CoverImageComponent = coverImages[counter]
counter++
if (counter === coverImages.length) {
counter = 0
}
return <CoverImageComponent {...props} />
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,14 @@
import { CoverImageProps } from '../types'
export const CoverImage10 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
fill-rule="evenodd"
clip-rule="evenodd"
stroke="#000"
stroke-width=".25"
stroke-miterlimit="3.864"
d="M-2.531-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM9.373-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM21.279-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM33.184-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM45.09-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM56.996-.107H65.5v8.503zm0 11.906H65.5v8.504zm0 11.905H65.5v8.504zm0 11.905H65.5v8.504zm0 11.907H65.5v8.504zm0 11.904H65.5v8.504zm0 11.906H65.5v8.504zm0 11.905H65.5v8.504zm0 11.906H65.5v8.504zm0 11.906H65.5v8.504zm0 11.905H65.5v8.504zM68.9-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM80.807-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM92.713-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM104.617-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zM116.523-.107h8.504v8.503zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.905h8.504v8.504zm0 11.907h8.504v8.504zm0 11.904h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504zm0 11.906h8.504v8.504zm0 11.906h8.504v8.504zm0 11.905h8.504v8.504z"
/>
</svg>
)

View File

@ -0,0 +1,15 @@
import { CoverImageProps } from '../types'
export const CoverImage11 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fill="none"
stroke="#000"
stroke-width="4.949"
stroke-miterlimit="3.864"
d="M2.457-.255v5.262h5.262m9.47-5.262v5.262h5.261m9.469-5.262v5.262h5.262m9.47-5.262v5.262h5.262m9.469-5.262v5.262h5.261m9.47-5.262v5.262h5.262m9.471-5.262v5.262h5.261m9.469-5.262v5.262h5.261M2.457 29.209v5.261h5.262m9.47-5.261v5.261h5.261m9.469-5.261v5.261h5.262m9.47-5.261v5.261h5.262m9.469-5.261v5.261h5.261m9.47-5.261v5.261h5.262m9.471-5.261v5.261h5.261m9.469-5.261v5.261h5.261M2.457 58.671v5.261h5.262m9.47-5.261v5.261h5.261m9.469-5.261v5.261h5.262m9.47-5.261v5.261h5.262m9.469-5.261v5.261h5.261m9.47-5.261v5.261h5.262m9.471-5.261v5.261h5.261m9.469-5.261v5.261h5.261M2.457 88.133v5.261h5.262m9.47-5.261v5.261h5.261m9.469-5.261v5.261h5.262m9.47-5.261v5.261h5.262m9.469-5.261v5.261h5.261m9.47-5.261v5.261h5.262m9.471-5.261v5.261h5.261m9.469-5.261v5.261h5.261M2.457 117.596v2.261m14.732-2.261v2.261m14.73-2.261v2.261m14.732-2.261v2.261m14.731-2.261v2.261m14.731-2.261v2.261m14.733-2.261v2.261m14.73-2.261v2.261M-1.909 19.737H.353m9.47-5.26v5.26h5.262m9.468-5.26v5.26h5.261m9.471-5.26v5.26h5.262m9.471-5.26v5.26h5.261m9.469-5.26v5.26h5.261m9.47-5.26v5.26h5.262m9.469-5.26v5.26h5.261m9.471-5.26v5.26h5.261M-1.909 49.199H.353m9.47-5.26v5.26h5.262m9.468-5.26v5.26h5.261m9.471-5.26v5.26h5.262m9.471-5.26v5.26h5.261m9.469-5.26v5.26h5.261m9.47-5.26v5.26h5.262m9.469-5.26v5.26h5.261m9.471-5.26v5.26h5.261M-1.909 78.663H.353m9.47-5.262v5.262h5.262m9.468-5.262v5.262h5.261m9.471-5.262v5.262h5.262m9.471-5.262v5.262h5.261m9.469-5.262v5.262h5.261m9.47-5.262v5.262h5.262m9.469-5.262v5.262h5.261m9.471-5.262v5.262h5.261M-1.909 108.126H.353m9.47-5.261v5.261h5.262m9.468-5.261v5.261h5.261m9.471-5.261v5.261h5.262m9.471-5.261v5.261h5.261m9.469-5.261v5.261h5.261m9.47-5.261v5.261h5.262m9.469-5.261v5.261h5.261m9.471-5.261v5.261h5.261"
/>
</svg>
)

View File

@ -0,0 +1,12 @@
import { CoverImageProps } from '../types'
export const CoverImage12 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
stroke="#000"
stroke-width="3.23"
stroke-miterlimit="3.864"
d="M-.509-2.401h4.578v4.578H-.509v-4.578zm0 12.819h4.578v4.578H-.509v-4.578zm0 12.817h4.578v4.578H-.509v-4.578zm0 12.82h4.578v4.578H-.509v-4.578zm0 12.819h4.578v4.578H-.509v-4.578zm0 12.818h4.578v4.579H-.509v-4.579zm0 12.82h4.578v4.578H-.509v-4.578zm0 12.818h4.578v4.579H-.509V87.33zm0 12.818h4.578v4.579H-.509v-4.579zm0 12.82h4.578v4.578H-.509v-4.578zM12.311-2.401h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.817h4.578v4.578h-4.578v-4.578zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578V87.33zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zM25.128-2.401h4.579v4.578h-4.579v-4.578zm0 12.819h4.579v4.578h-4.579v-4.578zm0 12.817h4.579v4.578h-4.579v-4.578zm0 12.82h4.579v4.578h-4.579v-4.578zm0 12.819h4.579v4.578h-4.579v-4.578zm0 12.818h4.579v4.579h-4.579v-4.579zm0 12.82h4.579v4.578h-4.579v-4.578zm0 12.818h4.579v4.579h-4.579V87.33zm0 12.818h4.579v4.579h-4.579v-4.579zm0 12.82h4.579v4.578h-4.579v-4.578zM37.947-2.401h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.817h4.578v4.578h-4.578v-4.578zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578V87.33zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zM50.767-2.401h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.817h4.578v4.578h-4.578v-4.578zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578V87.33zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zM63.586-2.401h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.817h4.578v4.578h-4.578v-4.578zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578V87.33zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zM76.404-2.401h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.817h4.578v4.578h-4.578v-4.578zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578V87.33zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zM89.223-2.401h4.579v4.578h-4.579v-4.578zm0 12.819h4.579v4.578h-4.579v-4.578zm0 12.817h4.579v4.578h-4.579v-4.578zm0 12.82h4.579v4.578h-4.579v-4.578zm0 12.819h4.579v4.578h-4.579v-4.578zm0 12.818h4.579v4.579h-4.579v-4.579zm0 12.82h4.579v4.578h-4.579v-4.578zm0 12.818h4.579v4.579h-4.579V87.33zm0 12.818h4.579v4.579h-4.579v-4.579zm0 12.82h4.579v4.578h-4.579v-4.578zm12.82-115.369h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.817h4.578v4.578h-4.578v-4.578zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578V87.33zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zM114.86-2.401h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.817h4.578v4.578h-4.578v-4.578zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.819h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578zm0 12.818h4.578v4.579h-4.578V87.33zm0 12.818h4.578v4.579h-4.578v-4.579zm0 12.82h4.578v4.578h-4.578v-4.578z"
/>
</svg>
)

View File

@ -0,0 +1,15 @@
import { CoverImageProps } from '../types'
export const CoverImage2 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fill="none"
stroke="#000"
stroke-width="2"
stroke-miterlimit="3.864"
d="m-1.753-1.524-3.592 7.179h7.18zm14.36 0-3.59 7.179h7.179zm14.362 0-3.592 7.179h7.18zm14.36 0-3.591 7.179h7.18zm14.361 0-3.591 7.179h7.18zm14.362 0-3.593 7.179h7.181zm14.36 0-3.593 7.179h7.182zm14.359 0-3.59 7.179h7.18zm14.362 0-3.591 7.179h7.18zM-1.753 27.199l-3.592 7.18h7.18zm14.36 0-3.59 7.18h7.179zm14.362 0-3.592 7.18h7.18zm14.36 0-3.591 7.18h7.18zm14.361 0-3.591 7.18h7.18zm14.362 0-3.593 7.18h7.181zm14.36 0-3.593 7.18h7.182zm14.359 0-3.59 7.18h7.18zm14.362 0-3.591 7.18h7.18zM-1.753 55.921l-3.592 7.18h7.18zm14.36 0-3.59 7.18h7.179zm14.362 0-3.592 7.18h7.18zm14.36 0-3.591 7.18h7.18zm14.361 0-3.591 7.18h7.18zm14.362 0-3.593 7.18h7.181zm14.36 0-3.593 7.18h7.182zm14.359 0-3.59 7.18h7.18zm14.362 0-3.591 7.18h7.18zM-1.753 84.64l-3.592 7.182h7.18zm14.36 0-3.59 7.182h7.179zm14.362 0-3.592 7.182h7.18zm14.36 0-3.591 7.182h7.18zm14.361 0-3.591 7.182h7.18zm14.362 0-3.593 7.182h7.181zm14.36 0-3.593 7.182h7.182zm14.359 0-3.59 7.182h7.18zm14.362 0-3.591 7.182h7.18zM-1.753 113.363l-3.592 7.182h7.18zm14.36 0-3.59 7.182h7.179zm14.362 0-3.592 7.182h7.18zm14.36 0-3.591 7.182h7.18zm14.361 0-3.591 7.182h7.18zm14.362 0-3.593 7.182h7.181zm14.36 0-3.593 7.182h7.182zm14.359 0-3.59 7.182h7.18zm14.362 0-3.591 7.182h7.18zM5.428 12.838l-3.593 7.18h7.182zm14.361 0-3.593 7.18h7.181zm14.36 0-3.592 7.18h7.181zm14.36 0-3.591 7.18h7.181zm14.361 0-3.591 7.18h7.18zm14.361 0-3.591 7.18h7.179zm14.361 0-3.591 7.18h7.18zm14.361 0-3.592 7.18h7.181zm14.36 0-3.591 7.18h7.18zM5.428 41.56l-3.593 7.179h7.182zm14.361 0-3.593 7.179h7.181zm14.36 0-3.592 7.179h7.181zm14.36 0-3.591 7.179h7.181zm14.361 0-3.591 7.179h7.18zm14.361 0-3.591 7.179h7.179zm14.361 0-3.591 7.179h7.18zm14.361 0-3.592 7.179h7.181zm14.36 0-3.591 7.179h7.18zM5.428 70.279l-3.593 7.183h7.182zm14.361 0-3.593 7.183h7.181zm14.36 0-3.592 7.183h7.181zm14.36 0-3.591 7.183h7.181zm14.361 0-3.591 7.183h7.18zm14.361 0-3.591 7.183h7.179zm14.361 0-3.591 7.183h7.18zm14.361 0-3.592 7.183h7.181zm14.36 0-3.591 7.183h7.18zM5.428 99.002l-3.593 7.182h7.182zm14.361 0-3.593 7.182h7.181zm14.36 0-3.592 7.182h7.181zm14.36 0-3.591 7.182h7.181zm14.361 0-3.591 7.182h7.18zm14.361 0-3.591 7.182h7.179zm14.361 0-3.591 7.182h7.18zm14.361 0-3.592 7.182h7.181zm14.36 0-3.591 7.182h7.18z"
/>
</svg>
)

View File

@ -0,0 +1,13 @@
import { CoverImageProps } from '../types'
export const CoverImage3 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
d="M3.647 8.782h5.291M1 .845v5.29m18.521 2.647h5.29M16.874.845v5.29m18.522 2.647h5.289M32.748.845v5.29M51.27 8.782h5.289M48.622.845v5.29m18.52 2.647h5.29M64.495.845v5.29m18.522 2.647h5.29M80.371.845v5.29m18.52 2.647h5.291M96.244.845v5.29m18.521 2.647h5.29M112.118.845v5.29M3.647 24.656h5.291M1 16.719v5.29m18.521 2.647h5.29m-7.937-7.937v5.289m18.522 2.648h5.289m-7.937-7.937v5.289m18.522 2.648h5.289m-7.937-7.937v5.289m18.52 2.648h5.29m-7.937-7.937v5.289m18.522 2.648h5.29m-7.936-7.937v5.289m18.52 2.648h5.291m-7.938-7.937v5.289m18.521 2.648h5.29m-7.937-7.937v5.289M3.647 40.53h5.291M1 32.593v5.29m18.521 2.647h5.29m-7.937-7.938v5.291m18.522 2.647h5.289m-7.937-7.938v5.291M51.27 40.53h5.289m-7.937-7.938v5.291m18.52 2.647h5.29m-7.937-7.938v5.291m18.522 2.647h5.29m-7.936-7.938v5.291m18.52 2.647h5.291m-7.938-7.938v5.291m18.521 2.647h5.29m-7.937-7.938v5.291M3.647 56.403h5.291M1 48.467v5.29m18.521 2.646h5.29m-7.937-7.936v5.29m18.522 2.646h5.289m-7.937-7.936v5.29m18.522 2.646h5.289m-7.937-7.936v5.29m18.52 2.646h5.29m-7.937-7.936v5.29m18.522 2.646h5.29m-7.936-7.936v5.29m18.52 2.646h5.291m-7.938-7.936v5.29m18.521 2.646h5.29m-7.937-7.936v5.29M3.647 72.277h5.291M1 64.34v5.29m18.521 2.647h5.29m-7.937-7.937v5.29m18.522 2.647h5.289m-7.937-7.937v5.29m18.522 2.647h5.289m-7.937-7.937v5.29m18.52 2.647h5.29m-7.937-7.937v5.29m18.522 2.647h5.29m-7.936-7.937v5.29m18.52 2.647h5.291m-7.938-7.937v5.29m18.521 2.647h5.29m-7.937-7.937v5.29M3.647 88.15h5.291M1 80.215v5.289m18.521 2.646h5.29m-7.937-7.935v5.289m18.522 2.646h5.289m-7.937-7.935v5.289M51.27 88.15h5.289m-7.937-7.935v5.289m18.52 2.646h5.29m-7.937-7.935v5.289m18.522 2.646h5.29m-7.936-7.935v5.289m18.52 2.646h5.291m-7.938-7.935v5.289m18.521 2.646h5.29m-7.937-7.935v5.289M3.647 104.025h5.291M1 96.088v5.29m18.521 2.647h5.29m-7.937-7.937v5.29m18.522 2.647h5.289m-7.937-7.937v5.29m18.522 2.647h5.289m-7.937-7.937v5.29m18.52 2.647h5.29m-7.937-7.937v5.29m18.522 2.647h5.29m-7.936-7.937v5.29m18.52 2.647h5.291m-7.938-7.937v5.29m18.521 2.647h5.29m-7.937-7.937v5.29M1 111.963v5.29m15.874-5.29v5.29m15.874-5.29v5.29m15.874-5.29v5.29m15.873-5.29v5.29m15.876-5.29v5.29m15.873-5.29v5.29m15.874-5.29v5.29"
fill="none"
stroke="#000"
stroke-width="3.733"
stroke-miterlimit="3.864"
/>
</svg>
)

View File

@ -0,0 +1,24 @@
import { CoverImageProps } from '../types'
export const CoverImage4 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fill="none"
stroke="#231F20"
stroke-width=".974"
stroke-miterlimit="3.864"
d="M-1.149-.979 1.772 120M1.611-.979 4.533 120M4.37-.979 7.293 120M7.133-.979 10.054 120m-.159-92.634 2.92 120.98m2.599-74.662 2.921 100.977M15.414-.979 18.336 120M18.174-.979 21.096 120M20.935-.979 23.856 120m1.299-92.634 2.921 120.98m2.6-58.311 2.922 120.979M29.216-.979 32.137 120M31.978-.979 34.899 120m-4.382-89.798 2.921 120.98M37.499-.979 40.42 120M40.259-.979 43.182 120M43.019-.979 45.94 120m-5.681-89.798 2.922 120.98M48.541-.979 51.462 120M51.301-.979 54.222 120M54.062-.979 56.982 120M49.84 49.547l2.921 120.98M62.343-.979 65.265 120M65.102-.979 68.023 120M67.863-.979 70.785 120M70.624-.979 73.546 120m-2.922-87.462 2.922 120.98M78.904-.979 81.826 120M81.666-.979 84.587 120M84.426-.979 87.348 120M87.188-.979 90.109 120M89.948-.979 92.869 120M92.708-.979 95.63 120m-4.383-32.145 2.922 100.979M98.229-.979 101.15 120M100.989-.979 103.91 120M103.75-.979 106.672 120M106.51-.979 109.432 120M109.271-.979 112.192 120m-5.682-64.286 2.922 120.979M114.791-.979 117.713 120m-.16-120.979L120.474 120M120.312-.979 123.234 120"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fill="none"
stroke="#231F20"
stroke-width=".984"
stroke-miterlimit="3.864"
d="M6.316-.016V120m11.858-60.97v120.016m8.442-165.37v120.015M31.418-.016V120M39.784-.016V120m11.517-31.662v100.014m8.281-132.156v120.016m9.742-113.424v120.016M89.987-.016V120M98.354-.016V120M106.722-.016V120M115.088-.016V120"
/>
</svg>
)

View File

@ -0,0 +1,15 @@
import { CoverImageProps } from '../types'
export const CoverImage5 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
d="M.679 8.708 8.666.722m0 7.986L16.65.722m0 7.986L24.635.722m0 7.986L32.622.722m0 7.986L40.607.722m0 7.986 7.99-7.986m0 7.986L56.58.722m0 7.986L64.568.722m0 7.986L72.555.722m0 7.986L80.542.722m0 7.986L88.527.722m0 7.986L96.513.722m0 7.986L104.5.722m0 7.986 7.985-7.986m0 7.986 7.988-7.986M.679 16.694l7.987-7.986m0 7.986 7.984-7.986m0 7.986 7.985-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.99-7.986m0 7.986 7.983-7.986m0 7.986 7.988-7.986m0 7.986 7.987-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.986-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.988-7.986M.679 24.681l7.987-7.987m0 7.987 7.984-7.987m0 7.987 7.985-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.99-7.987m0 7.987 7.983-7.987m0 7.987 7.988-7.987m0 7.987 7.987-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.986-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.988-7.987M.679 32.666l7.987-7.985m0 7.985 7.984-7.985m0 7.985 7.985-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.99-7.985m0 7.985 7.983-7.985m0 7.985 7.988-7.985m0 7.985 7.987-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.986-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.988-7.985M.679 40.653l7.987-7.987m0 7.987 7.984-7.987m0 7.987 7.985-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.99-7.987m0 7.987 7.983-7.987m0 7.987 7.988-7.987m0 7.987 7.987-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.986-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.988-7.987M.679 48.641l7.987-7.988m0 7.988 7.984-7.988m0 7.988 7.985-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.99-7.988m0 7.988 7.983-7.988m0 7.988 7.988-7.988m0 7.988 7.987-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.986-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.988-7.988M.679 56.625l7.987-7.984m0 7.984 7.984-7.984m0 7.984 7.985-7.984m0 7.984 7.987-7.984m0 7.984 7.985-7.984m0 7.984 7.99-7.984m0 7.984 7.983-7.984m0 7.984 7.988-7.984m0 7.984 7.987-7.984m0 7.984 7.987-7.984m0 7.984 7.985-7.984m0 7.984 7.986-7.984m0 7.984 7.987-7.984m0 7.984 7.985-7.984m0 7.984 7.988-7.984M.679 64.611l7.987-7.986m0 7.986 7.984-7.986m0 7.986 7.985-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.99-7.986m0 7.986 7.983-7.986m0 7.986 7.988-7.986m0 7.986 7.987-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.986-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.988-7.986M.679 72.597l7.987-7.986m0 7.986 7.984-7.986m0 7.986 7.985-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.99-7.986m0 7.986 7.983-7.986m0 7.986 7.988-7.986m0 7.986 7.987-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.986-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.988-7.986M.679 80.585l7.987-7.988m0 7.988 7.984-7.988m0 7.988 7.985-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.99-7.988m0 7.988 7.983-7.988m0 7.988 7.988-7.988m0 7.988 7.987-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.986-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.988-7.988M.679 88.57l7.987-7.985m0 7.985 7.984-7.985m0 7.985 7.985-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.99-7.985m0 7.985 7.983-7.985m0 7.985 7.988-7.985m0 7.985 7.987-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.986-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.988-7.985M.679 96.556l7.987-7.986m0 7.986 7.984-7.986m0 7.986 7.985-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.99-7.986m0 7.986 7.983-7.986m0 7.986 7.988-7.986m0 7.986 7.987-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.986-7.986m0 7.986 7.987-7.986m0 7.986 7.985-7.986m0 7.986 7.988-7.986M.679 104.543l7.987-7.987m0 7.987 7.984-7.987m0 7.987 7.985-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.99-7.987m0 7.987 7.983-7.987m0 7.987 7.988-7.987m0 7.987 7.987-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.986-7.987m0 7.987 7.987-7.987m0 7.987 7.985-7.987m0 7.987 7.988-7.987M.679 112.528l7.987-7.985m0 7.985 7.984-7.985m0 7.985 7.985-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.99-7.985m0 7.985 7.983-7.985m0 7.985 7.988-7.985m0 7.985 7.987-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.986-7.985m0 7.985 7.987-7.985m0 7.985 7.985-7.985m0 7.985 7.988-7.985M.679 120.516l7.987-7.988m0 7.988 7.984-7.988m0 7.988 7.985-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.99-7.988m0 7.988 7.983-7.988m0 7.988 7.988-7.988m0 7.988 7.987-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.986-7.988m0 7.988 7.987-7.988m0 7.988 7.985-7.988m0 7.988 7.988-7.988"
fill-rule="evenodd"
clip-rule="evenodd"
fill="none"
stroke="#000"
stroke-width="1.878"
stroke-miterlimit="3.864"
/>
</svg>
)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,209 @@
import { CoverImageProps } from '../types'
export const CoverImage8 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M129.44-7.29c0 11.343-11.396 11.343-11.396 22.792m.001 0c0 11.452 11.396 11.452 11.396 22.791 0 11.342-11.396 11.342-11.396 22.796m0 0c0 11.447 11.396 11.447 11.396 22.79 0 11.341-11.396 11.341-11.396 22.788m0 0c0 11.452 11.396 11.452 11.396 22.791M124.886-7.29c0 11.343-11.398 11.343-11.398 22.792"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M113.487 15.502c0 11.452 11.398 11.452 11.398 22.791 0 11.342-11.398 11.342-11.398 22.796m0 0c0 11.447 11.398 11.447 11.398 22.79 0 11.341-11.398 11.341-11.398 22.788m0 0c0 11.452 11.398 11.452 11.398 22.791M120.326-7.29c0 11.343-11.401 11.343-11.401 22.792"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M108.925 15.502c0 11.452 11.401 11.452 11.401 22.791 0 11.342-11.401 11.342-11.401 22.796m0 0c0 11.447 11.401 11.447 11.401 22.79 0 11.341-11.401 11.341-11.401 22.788m0 0c0 11.452 11.401 11.452 11.401 22.791m-4.669-136.83c.018 11.343-11.377 11.361-11.358 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M104.299 15.438c.018 11.453 11.412 11.435 11.431 22.773.019 11.341-11.377 11.359-11.358 22.814m-.001.001c.019 11.446 11.414 11.428 11.432 22.771.019 11.342-11.377 11.36-11.359 22.807m-.001 0c.019 11.453 11.414 11.435 11.432 22.773M111.096-7.365c.018 11.344-11.375 11.362-11.356 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M99.739 15.445c.018 11.453 11.41 11.435 11.429 22.773.018 11.341-11.375 11.359-11.356 22.814m0 .001c.019 11.447 11.411 11.428 11.429 22.771.019 11.341-11.374 11.359-11.356 22.806m-.001 0c.019 11.453 11.411 11.435 11.429 22.773m-4.778-136.74c.018 11.343-11.369 11.361-11.351 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M95.185 15.453c.019 11.452 11.404 11.434 11.423 22.772.018 11.342-11.368 11.36-11.35 22.815m0 .001c.019 11.446 11.405 11.428 11.423 22.771.019 11.341-11.368 11.36-11.351 22.807"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M95.33 106.618c.019 11.453 11.405 11.434 11.423 22.773M101.981-7.351c.018 11.344-11.375 11.363-11.356 22.811m0 0c.018 11.453 11.41 11.435 11.429 22.773.018 11.341-11.375 11.359-11.356 22.814m-.001.001c.019 11.446 11.412 11.428 11.43 22.771.019 11.341-11.375 11.359-11.357 22.806m0 0c.019 11.453 11.412 11.435 11.43 22.773M97.428-7.343C97.445 4 86.044 4.019 86.062 15.468"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M86.062 15.468C86.08 26.92 97.481 26.901 97.5 38.24c.018 11.342-11.383 11.36-11.364 22.814m0 .001c.019 11.447 11.419 11.429 11.437 22.771.02 11.341-11.382 11.359-11.364 22.807m-.001 0c.019 11.452 11.42 11.434 11.438 22.772M92.868-7.336c.018 11.343-11.384 11.362-11.365 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M81.503 15.475c.018 11.453 11.419 11.435 11.438 22.773.018 11.341-11.384 11.359-11.365 22.814m-.001 0c.019 11.446 11.42 11.428 11.438 22.771.019 11.342-11.383 11.36-11.365 22.807m-.001 0c.019 11.453 11.42 11.435 11.438 22.773M88.308-7.328c.018 11.343-11.377 11.361-11.358 22.81"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M76.949 15.481c.018 11.453 11.412 11.435 11.431 22.773.018 11.341-11.377 11.359-11.358 22.814m-.001.001c.019 11.447 11.414 11.429 11.432 22.771.019 11.341-11.377 11.359-11.359 22.807m0 0c.019 11.452 11.414 11.434 11.432 22.772M83.748-7.321c.018 11.342-11.38 11.361-11.361 22.81"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M72.387 15.489c.018 11.453 11.415 11.434 11.434 22.773.018 11.341-11.38 11.359-11.361 22.814m-.001.001c.02 11.446 11.416 11.428 11.434 22.771.019 11.342-11.378 11.36-11.36 22.807m-.001-.001c.019 11.453 11.415 11.435 11.433 22.773M79.191-7.314c.018 11.343-11.375 11.362-11.356 22.81"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M67.835 15.496c.018 11.453 11.411 11.435 11.43 22.773.018 11.341-11.376 11.359-11.356 22.814m-.001.001c.019 11.446 11.411 11.428 11.429 22.771.019 11.341-11.374 11.359-11.356 22.806m-.001 0c.019 11.453 11.411 11.435 11.429 22.773M74.632-7.307c.017 11.343-11.377 11.362-11.359 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M63.273 15.504c.018 11.452 11.412 11.434 11.431 22.772.018 11.342-11.377 11.36-11.358 22.815m0 .001c.019 11.446 11.413 11.428 11.431 22.771.019 11.341-11.376 11.359-11.358 22.807m-.001-.001c.019 11.452 11.414 11.434 11.432 22.773M70.078-7.3C70.096 4.044 58.701 4.062 58.72 15.511"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M58.72 15.511c.018 11.453 11.412 11.435 11.431 22.773.018 11.341-11.377 11.359-11.358 22.814m-.001.001c.019 11.446 11.413 11.428 11.431 22.771.019 11.341-11.376 11.359-11.358 22.806m-.001 0c.019 11.453 11.413 11.435 11.431 22.773M65.518-7.292c.018 11.343-11.375 11.361-11.356 22.81"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M54.161 15.518c.018 11.453 11.411 11.435 11.43 22.773.018 11.341-11.376 11.36-11.356 22.814m-.001 0c.019 11.447 11.411 11.429 11.429 22.771.019 11.341-11.374 11.359-11.356 22.807m0 .001c.019 11.452 11.411 11.434 11.429 22.772M60.964-7.285C60.981 4.058 49.581 4.076 49.6 15.525"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M49.6 15.525c.018 11.453 11.418 11.435 11.438 22.773.018 11.341-11.384 11.359-11.365 22.814m-.001.001c.019 11.446 11.42 11.428 11.438 22.771.019 11.342-11.383 11.36-11.365 22.807m-.001-.001c.019 11.453 11.42 11.435 11.438 22.773M56.398-7.278c.018 11.344-11.38 11.362-11.361 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M45.037 15.532c.018 11.453 11.415 11.435 11.434 22.773.018 11.341-11.38 11.359-11.361 22.814m-.001.001c.019 11.447 11.416 11.428 11.434 22.771.019 11.341-11.379 11.359-11.361 22.806m0 0c.02 11.453 11.416 11.435 11.434 22.773M51.842-7.271C51.859 4.072 40.467 4.091 40.485 15.54"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M40.485 15.54c.018 11.452 11.41 11.434 11.429 22.772.018 11.342-11.375 11.36-11.356 22.815m0 .001c.019 11.446 11.412 11.428 11.43 22.771.019 11.341-11.374 11.36-11.356 22.807m-.001-.001c.019 11.453 11.411 11.434 11.429 22.773M47.284-7.264c.018 11.344-11.38 11.362-11.36 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M35.924 15.547C35.941 27 47.338 26.981 47.356 38.32c.018 11.341-11.379 11.359-11.36 22.814m0 .001c.019 11.446 11.415 11.428 11.433 22.771.019 11.341-11.378 11.359-11.36 22.806m-.001 0c.019 11.453 11.416 11.435 11.434 22.773M42.722-7.256c.017 11.343-11.377 11.361-11.359 22.811"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M31.363 15.555c.019 11.452 11.412 11.434 11.432 22.772.018 11.342-11.378 11.36-11.359 22.814m0 .001c.018 11.447 11.414 11.428 11.431 22.771.019 11.341-11.377 11.359-11.359 22.807"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M31.508 106.72c.02 11.452 11.414 11.434 11.432 22.772M38.168-7.249C38.186 4.094 26.791 4.113 26.81 15.562m0 0c.019 11.453 11.412 11.435 11.431 22.773.018 11.341-11.377 11.359-11.358 22.814m-.001 0C26.9 72.596 38.296 72.577 38.313 83.92c.019 11.342-11.377 11.36-11.359 22.807m0 0c.019 11.453 11.414 11.435 11.432 22.773M33.614-7.24C33.632 4.102 22.23 4.12 22.25 15.568"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M22.25 15.568c.019 11.453 11.418 11.435 11.437 22.773.018 11.341-11.383 11.359-11.364 22.814m-.001.001c.019 11.447 11.42 11.429 11.438 22.771.019 11.341-11.383 11.359-11.365 22.807m0 0c.019 11.452 11.42 11.434 11.438 22.772M29.055-7.234c.017 11.342-11.386 11.361-11.367 22.81"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M17.688 15.576c.018 11.453 11.421 11.434 11.439 22.774.018 11.34-11.386 11.359-11.367 22.814m0 0c.019 11.446 11.422 11.428 11.439 22.771.019 11.342-11.385 11.36-11.367 22.807"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M17.832 106.741c.019 11.453 11.422 11.435 11.439 22.773M24.492-7.227c.018 11.344-11.377 11.362-11.358 22.81m0 0c.019 11.453 11.413 11.436 11.431 22.773.018 11.341-11.377 11.359-11.358 22.814m-.001.001c.019 11.446 11.413 11.428 11.431 22.771.02 11.341-11.376 11.359-11.358 22.806m-.001 0c.019 11.453 11.414 11.435 11.432 22.773M20.043-7.29c0 11.343-11.396 11.343-11.396 22.792"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M8.646 15.502c0 11.452 11.396 11.452 11.396 22.791 0 11.342-11.396 11.342-11.396 22.796m0 0c0 11.447 11.396 11.447 11.396 22.79 0 11.341-11.396 11.341-11.396 22.788m0 0c0 11.452 11.396 11.452 11.396 22.791M15.487-7.29c0 11.343-11.396 11.343-11.396 22.792"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M4.092 15.502c0 11.452 11.396 11.452 11.396 22.791 0 11.342-11.396 11.342-11.396 22.796m0 0c0 11.447 11.396 11.447 11.396 22.79 0 11.341-11.396 11.341-11.396 22.788m0 0c0 11.452 11.396 11.452 11.396 22.791M10.927-7.29C10.927 4.053-.47 4.053-.47 15.502"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M-.47 15.502c0 11.452 11.396 11.452 11.396 22.791C10.926 49.635-.47 49.635-.47 61.089m0 0c0 11.447 11.396 11.447 11.396 22.79C10.926 95.22-.47 95.22-.47 106.667m0 0c0 11.452 11.396 11.452 11.396 22.791M6.371-7.29c0 11.343-11.393 11.343-11.393 22.792"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M-5.021 15.502c0 11.452 11.393 11.452 11.393 22.791 0 11.342-11.393 11.342-11.393 22.796m0 0c0 11.447 11.393 11.447 11.393 22.79 0 11.341-11.393 11.341-11.393 22.788m0 0c0 11.452 11.393 11.452 11.393 22.791"
/>
<path
fill="none"
stroke="#000"
stroke-width="1.608"
stroke-miterlimit="3.864"
d="M-9.59 15.502c0 11.452 11.403 11.452 11.403 22.791 0 11.342-11.403 11.342-11.403 22.796m0 0c0 11.447 11.403 11.447 11.403 22.79C1.813 95.22-9.59 95.22-9.59 106.667m0 0c0 11.452 11.403 11.452 11.403 22.791"
/>
</svg>
)

View File

@ -0,0 +1,13 @@
import { CoverImageProps } from '../types'
export const CoverImage9 = (props: CoverImageProps) => (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 120 120" {...props}>
<path
fill="none"
stroke="#000"
stroke-width="5"
stroke-miterlimit="3.864"
d="M-23.957 4.94c21.724 0 21.724-3.6 43.237-3.6 21.512 0 21.512 3.601 43.234 3.601m.001-.001c21.724 0 21.724-3.601 43.237-3.601 21.515 0 21.515 3.601 43.237 3.601m-172.946 8.649c21.724 0 21.724-3.603 43.237-3.603 21.512 0 21.512 3.603 43.234 3.603m.001 0c21.724 0 21.724-3.603 43.237-3.603 21.515 0 21.515 3.603 43.237 3.603m-172.946 8.646c21.724 0 21.724-3.603 43.237-3.603 21.512 0 21.512 3.603 43.234 3.603m.001 0c21.724 0 21.724-3.603 43.237-3.603 21.515 0 21.515 3.603 43.237 3.603m-172.946 8.647c21.724 0 21.724-3.6 43.237-3.6 21.512 0 21.512 3.6 43.234 3.6m.001 0c21.724 0 21.724-3.6 43.237-3.6 21.515 0 21.515 3.6 43.237 3.6m-172.946 8.649c21.724 0 21.724-3.604 43.237-3.604 21.512 0 21.512 3.604 43.234 3.604m.001 0c21.724 0 21.724-3.604 43.237-3.604 21.515 0 21.515 3.604 43.237 3.604m-172.946 8.647c21.724 0 21.724-3.604 43.237-3.604 21.512 0 21.512 3.604 43.234 3.604m.001 0c21.724 0 21.724-3.604 43.237-3.604 21.515 0 21.515 3.604 43.237 3.604m-172.946 8.645c21.724 0 21.724-3.6 43.237-3.6 21.512 0 21.512 3.6 43.234 3.6m.001 0c21.724 0 21.724-3.6 43.237-3.6 21.515 0 21.515 3.6 43.237 3.6m-172.946 8.65c21.724 0 21.724-3.603 43.237-3.603 21.512 0 21.512 3.603 43.234 3.603m.001 0c21.724 0 21.724-3.603 43.237-3.603 21.515 0 21.515 3.603 43.237 3.603m-172.946 8.646c21.724 0 21.724-3.603 43.237-3.603 21.512 0 21.512 3.603 43.234 3.603m.001 0c21.724 0 21.724-3.603 43.237-3.603 21.515 0 21.515 3.603 43.237 3.603m-172.946 8.647c21.724 0 21.724-3.604 43.237-3.604 21.512 0 21.512 3.604 43.234 3.604m.001 0c21.724 0 21.724-3.604 43.237-3.604 21.515 0 21.515 3.604 43.237 3.604m-172.946 8.646c21.724 0 21.724-3.604 43.237-3.604 21.512 0 21.512 3.604 43.234 3.604m.001 0c21.724 0 21.724-3.604 43.237-3.604 21.515 0 21.515 3.604 43.237 3.604m-172.946 8.649c21.724 0 21.724-3.604 43.237-3.604 21.512 0 21.512 3.604 43.234 3.604m.001 0c21.724 0 21.724-3.604 43.237-3.604 21.515 0 21.515 3.604 43.237 3.604m-172.946 8.646c21.724 0 21.724-3.603 43.237-3.603 21.512 0 21.512 3.603 43.234 3.603m.001 0c21.724 0 21.724-3.603 43.237-3.603 21.515 0 21.515 3.603 43.237 3.603m-172.946 8.647c21.724 0 21.724-3.604 43.237-3.604 21.512 0 21.512 3.604 43.234 3.604m.001 0c21.724 0 21.724-3.604 43.237-3.604 21.515 0 21.515 3.604 43.237 3.604"
/>
</svg>
)

View File

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

View File

@ -0,0 +1,3 @@
export type CoverImageProps = {
class?: string
}

View File

@ -1,31 +1,41 @@
import { createEffect, For, createMemo, onMount, Show, createSignal, onCleanup } from 'solid-js'
import { Title } from '@solidjs/meta'
import { clsx } from 'clsx'
import { getPagePath } from '@nanostores/router'
import MD from './MD'
import type { Author, Shout } from '../../graphql/types.gen' import type { Author, Shout } from '../../graphql/types.gen'
import { useSession } from '../../context/session'
import { getPagePath } from '@nanostores/router'
import { createPopper } from '@popperjs/core'
import { Link, Meta } from '@solidjs/meta'
import { clsx } from 'clsx'
import { createEffect, For, createMemo, onMount, Show, createSignal, onCleanup } from 'solid-js'
import { isServer } from 'solid-js/web'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { useReactions } from '../../context/reactions' import { useReactions } from '../../context/reactions'
import { useSession } from '../../context/session'
import { MediaItem } from '../../pages/types' import { MediaItem } from '../../pages/types'
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router' import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
import { getDescription } from '../../utils/meta' import { showModal } from '../../stores/ui'
import { TableOfContents } from '../TableOfContents' import { getImageUrl, getOpenGraphImageUrl } from '../../utils/getImageUrl'
import { AudioPlayer } from './AudioPlayer' import { getDescription, getKeywords } from '../../utils/meta'
import { SharePopup } from './SharePopup'
import { ShoutRatingControl } from './ShoutRatingControl'
import { CommentsTree } from './CommentsTree'
import stylesHeader from '../Nav/Header/Header.module.scss'
import { AudioHeader } from './AudioHeader'
import { Popover } from '../_shared/Popover'
import { VideoPlayer } from '../_shared/VideoPlayer'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import { SolidSwiper } from '../_shared/SolidSwiper' import { Image } from '../_shared/Image'
import styles from './Article.module.scss' import { InviteCoAuthorsModal } from '../_shared/InviteCoAuthorsModal'
import { CardTopic } from '../Feed/CardTopic' import { Lightbox } from '../_shared/Lightbox'
import { createPopper } from '@popperjs/core' import { Popover } from '../_shared/Popover'
import { ShareModal } from '../_shared/ShareModal'
import { ImageSwiper } from '../_shared/SolidSwiper'
import { VideoPlayer } from '../_shared/VideoPlayer'
import { AuthorBadge } from '../Author/AuthorBadge' import { AuthorBadge } from '../Author/AuthorBadge'
import { getImageUrl } from '../../utils/getImageUrl' import { CardTopic } from '../Feed/CardTopic'
import { FeedArticlePopup } from '../Feed/FeedArticlePopup'
import { TableOfContents } from '../TableOfContents'
import { AudioHeader } from './AudioHeader'
import { AudioPlayer } from './AudioPlayer'
import { CommentsTree } from './CommentsTree'
import { getShareUrl, SharePopup } from './SharePopup'
import { ShoutRatingControl } from './ShoutRatingControl'
import styles from './Article.module.scss'
import stylesHeader from '../Nav/Header/Header.module.scss'
type Props = { type Props = {
article: Shout article: Shout
@ -43,26 +53,30 @@ const scrollTo = (el: HTMLElement) => {
window.scrollTo({ window.scrollTo({
top: top + window.scrollY - DEFAULT_HEADER_OFFSET, top: top + window.scrollY - DEFAULT_HEADER_OFFSET,
left: 0, left: 0,
behavior: 'smooth' behavior: 'smooth',
}) })
} }
const imgSrcRegExp = /<img[^>]+src\s*=\s*["']([^"']+)["']/gi
export const FullArticle = (props: Props) => { export const FullArticle = (props: Props) => {
const [selectedImage, setSelectedImage] = createSignal('')
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
const { t, formatDate } = useLocalize() const { t, formatDate } = useLocalize()
const { const {
user, user,
isAuthenticated, isAuthenticated,
actions: { requireAuthentication } actions: { requireAuthentication },
} = useSession() } = useSession()
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
const formattedDate = createMemo(() => formatDate(new Date(props.article.createdAt))) const formattedDate = createMemo(() => formatDate(new Date(props.article.createdAt)))
const mainTopic = createMemo( const mainTopic = createMemo(
() => () =>
props.article.topics?.find((topic) => topic?.slug === props.article.mainTopic) || props.article.topics?.find((topic) => topic?.slug === props.article.mainTopic) ||
props.article.topics[0] props.article.topics[0],
) )
const canEdit = () => props.article.authors?.some((a) => a.slug === user()?.slug) const canEdit = () => props.article.authors?.some((a) => a.slug === user()?.slug)
@ -87,8 +101,33 @@ export const FullArticle = (props: Props) => {
} }
return props.article.body return props.article.body
}) })
const media = createMemo(() => {
return JSON.parse(props.article.media || '[]') const imageUrls = createMemo(() => {
if (!body()) {
return []
}
if (isServer) {
const result: string[] = []
let match: RegExpMatchArray
while ((match = imgSrcRegExp.exec(body())) !== null) {
result.push(match[1])
}
return result
}
const imageElements = document.querySelectorAll<HTMLImageElement>('#shoutBody img')
// eslint-disable-next-line unicorn/prefer-spread
return Array.from(imageElements).map((img) => img.src)
})
const media = createMemo<MediaItem[]>(() => {
try {
return JSON.parse(props.article.media)
} catch {
return []
}
}) })
const commentsRef: { const commentsRef: {
@ -99,7 +138,7 @@ export const FullArticle = (props: Props) => {
scrollTo(commentsRef.current) scrollTo(commentsRef.current)
} }
const { searchParams, changeSearchParam } = useRouter<ArticlePageSearchParams>() const { searchParams, changeSearchParams } = useRouter<ArticlePageSearchParams>()
createEffect(() => { createEffect(() => {
if (props.scrollToComments) { if (props.scrollToComments) {
@ -110,8 +149,8 @@ export const FullArticle = (props: Props) => {
createEffect(() => { createEffect(() => {
if (searchParams()?.scrollTo === 'comments' && commentsRef.current) { if (searchParams()?.scrollTo === 'comments' && commentsRef.current) {
scrollToComments() scrollToComments()
changeSearchParam({ changeSearchParams({
scrollTo: null scrollTo: null,
}) })
} }
}) })
@ -119,10 +158,10 @@ export const FullArticle = (props: Props) => {
createEffect(() => { createEffect(() => {
if (searchParams().commentId && isReactionsLoaded()) { if (searchParams().commentId && isReactionsLoaded()) {
const commentElement = document.querySelector<HTMLElement>( const commentElement = document.querySelector<HTMLElement>(
`[id='comment_${searchParams().commentId}']` `[id='comment_${searchParams().commentId}']`,
) )
changeSearchParam({ commentId: null }) changeSearchParams({ commentId: null })
if (commentElement) { if (commentElement) {
scrollTo(commentElement) scrollTo(commentElement)
@ -131,17 +170,21 @@ export const FullArticle = (props: Props) => {
}) })
const { const {
actions: { loadReactionsBy } actions: { loadReactionsBy },
} = useReactions() } = useReactions()
onMount(async () => { onMount(async () => {
await loadReactionsBy({ await loadReactionsBy({
by: { shout: props.article.slug } by: { shout: props.article.slug },
}) })
setIsReactionsLoaded(true) setIsReactionsLoaded(true)
}) })
onMount(() => {
document.title = props.article.title
})
const clickHandlers = [] const clickHandlers = []
const documentClickHandlers = [] const documentClickHandlers = []
@ -151,7 +194,7 @@ export const FullArticle = (props: Props) => {
} }
const tooltipElements: NodeListOf<HTMLElement> = document.querySelectorAll( const tooltipElements: NodeListOf<HTMLElement> = document.querySelectorAll(
'[data-toggle="tooltip"], footnote' '[data-toggle="tooltip"], footnote',
) )
if (!tooltipElements) { if (!tooltipElements) {
return return
@ -168,7 +211,7 @@ export const FullArticle = (props: Props) => {
document.body.appendChild(tooltip) document.body.appendChild(tooltip)
if (element.hasAttribute('href')) { if (element.hasAttribute('href')) {
element.setAttribute('href', 'javascript: void(0);') element.setAttribute('href', 'javascript: void(0)')
} }
const popperInstance = createPopper(element, tooltip, { const popperInstance = createPopper(element, tooltip, {
@ -176,19 +219,19 @@ export const FullArticle = (props: Props) => {
modifiers: [ modifiers: [
{ {
name: 'eventListeners', name: 'eventListeners',
options: { scroll: false } options: { scroll: false },
}, },
{ {
name: 'offset', name: 'offset',
options: { options: {
offset: [0, 8] offset: [0, 8],
} },
}, },
{ {
name: 'flip', name: 'flip',
options: { fallbackPlacements: ['top'] } options: { fallbackPlacements: ['top'] },
} },
] ],
}) })
tooltip.style.visibility = 'hidden' tooltip.style.visibility = 'hidden'
@ -229,12 +272,53 @@ export const FullArticle = (props: Props) => {
}) })
}) })
const openLightbox = (image) => {
setSelectedImage(image)
}
const handleLightboxClose = () => {
setSelectedImage()
}
const handleArticleBodyClick = (event) => {
if (event.target.tagName === 'IMG' && !event.target.dataset['disableLightbox']) {
const src = event.target.src
openLightbox(getImageUrl(src))
}
}
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,
})
const description = getDescription(props.article.description || body())
const ogTitle = props.article.title
const keywords = getKeywords(props.article)
const shareUrl = getShareUrl({ pathname: `/${props.article.slug}` })
return ( return (
<> <>
<Title>{props.article.title}</Title> <Meta name="descprition" content={description} />
<Meta name="keywords" content={keywords} />
<Meta name="og:type" content="article" />
<Meta name="og:title" content={ogTitle} />
<Meta name="og:image" content={ogImage} />
<Meta name="og:description" content={description} />
<Meta name="twitter:card" content="summary_large_image" />
<Meta name="twitter:title" content={ogTitle} />
<Meta name="twitter:description" content={description} />
<Meta name="twitter:image" content={ogImage} />
<For each={imageUrls()}>{(imageUrl) => <Link rel="preload" as="image" href={imageUrl} />}</For>
<div class="wide-container"> <div class="wide-container">
<div class="row position-relative"> <div class="row position-relative">
<article class="col-md-16 col-lg-14 col-xl-12 offset-md-5"> <article
class={clsx('col-md-16 col-lg-14 col-xl-12 offset-md-5', styles.articleContent)}
onClick={handleArticleBodyClick}
>
{/*TODO: Check styles.shoutTopic*/} {/*TODO: Check styles.shoutTopic*/}
<Show when={props.article.layout !== 'music'}> <Show when={props.article.layout !== 'music'}>
<div class={styles.shoutHeader}> <div class={styles.shoutHeader}>
@ -264,12 +348,7 @@ export const FullArticle = (props: Props) => {
props.article.layout !== 'image' props.article.layout !== 'image'
} }
> >
<div <Image width={800} alt={props.article.title} src={props.article.cover} />
class={styles.shoutCover}
style={{
'background-image': `url('${getImageUrl(props.article.cover, { width: 1600 })}')`
}}
/>
</Show> </Show>
</div> </div>
</Show> </Show>
@ -301,7 +380,7 @@ export const FullArticle = (props: Props) => {
description={m.body} description={m.body}
/> />
<Show when={m?.body}> <Show when={m?.body}>
<MD body={m.body} /> <div innerHTML={m.body} />
</Show> </Show>
</div> </div>
)} )}
@ -310,11 +389,7 @@ export const FullArticle = (props: Props) => {
</Show> </Show>
<Show when={body()}> <Show when={body()}>
<div id="shoutBody" class={styles.shoutBody}> <div id="shoutBody" class={styles.shoutBody} innerHTML={body()} />
<Show when={!body().startsWith('<')} fallback={<div innerHTML={body()} />}>
<MD body={body()} />
</Show>
</div>
</Show> </Show>
</article> </article>
@ -331,7 +406,7 @@ export const FullArticle = (props: Props) => {
<div class="wide-container"> <div class="wide-container">
<div class="row"> <div class="row">
<div class="col-md-20 offset-md-2"> <div class="col-md-20 offset-md-2">
<SolidSwiper images={media()} /> <ImageSwiper images={media()} />
</div> </div>
</div> </div>
</div> </div>
@ -346,35 +421,58 @@ export const FullArticle = (props: Props) => {
<ShoutRatingControl shout={props.article} class={styles.ratingControl} /> <ShoutRatingControl shout={props.article} class={styles.ratingControl} />
</div> </div>
<Popover content={t('Comment')}> <Popover content={t('Comment')} disabled={isActionPopupActive()}>
{(triggerRef: (el) => void) => ( {(triggerRef: (el) => void) => (
<div class={styles.shoutStatsItem} ref={triggerRef} onClick={scrollToComments}> <div class={clsx(styles.shoutStatsItem)} ref={triggerRef} onClick={scrollToComments}>
<Icon name="comment" class={styles.icon} /> <Icon name="comment" class={styles.icon} />
<Icon name="comment-hover" class={clsx(styles.icon, styles.iconHover)} /> <Icon name="comment-hover" class={clsx(styles.icon, styles.iconHover)} />
{props.article.stat?.commented ?? ''} <Show
when={props.article.stat?.commented}
fallback={<span class={styles.commentsTextLabel}>{t('Add comment')}</span>}
>
{props.article.stat?.commented}
</Show>
</div> </div>
)} )}
</Popover> </Popover>
<Show when={props.article.stat?.viewed}> <Show when={props.article.stat?.viewed}>
<div class={clsx(styles.shoutStatsItem, styles.shoutStatsItemViews)}> <div class={clsx(styles.shoutStatsItem, styles.shoutStatsItemViews)}>
<Icon name="eye" class={styles.icon} /> {t('viewsWithCount', { count: props.article.stat?.viewed })}
<Icon name="eye" class={clsx(styles.icon, styles.iconHover)} />
<span class={styles.shoutStatsItemCount}>{props.article.stat?.viewed}</span>
<span class={styles.shoutStatsItemLabel}>
{t('viewsWithCount', { count: props.article.stat?.viewed })}
</span>
</div> </div>
</Show> </Show>
<Popover content={t('Share')}> <div class={clsx(styles.shoutStatsItem, styles.shoutStatsItemAdditionalData)}>
<div class={clsx(styles.shoutStatsItem, styles.shoutStatsItemAdditionalDataItem)}>
{formattedDate()}
</div>
</div>
<Popover content={t('Add to bookmarks')} disabled={isActionPopupActive()}>
{(triggerRef: (el) => void) => (
<div
class={clsx(styles.shoutStatsItem, styles.shoutStatsItemBookmarks)}
ref={triggerRef}
onClick={handleBookmarkButtonClick}
>
<div class={styles.shoutStatsItemInner}>
<Icon name="bookmark" class={styles.icon} />
<Icon name="bookmark-hover" class={clsx(styles.icon, styles.iconHover)} />
</div>
</div>
)}
</Popover>
<Popover content={t('Share')} disabled={isActionPopupActive()}>
{(triggerRef: (el) => void) => ( {(triggerRef: (el) => void) => (
<div class={styles.shoutStatsItem} ref={triggerRef}> <div class={styles.shoutStatsItem} ref={triggerRef}>
<SharePopup <SharePopup
title={props.article.title} title={props.article.title}
description={getDescription(props.article.body)} description={description}
imageUrl={props.article.cover} imageUrl={props.article.cover}
shareUrl={shareUrl}
containerCssClass={stylesHeader.control} containerCssClass={stylesHeader.control}
onVisibilityChange={(isVisible) => setIsActionPopupActive(isVisible)}
trigger={ trigger={
<div class={styles.shoutStatsItemInner}> <div class={styles.shoutStatsItemInner}>
<Icon name="share-outline" class={styles.icon} /> <Icon name="share-outline" class={styles.icon} />
@ -385,16 +483,7 @@ export const FullArticle = (props: Props) => {
</div> </div>
)} )}
</Popover> </Popover>
<Popover content={t('Add to bookmarks')}>
{(triggerRef: (el) => void) => (
<div class={styles.shoutStatsItem} ref={triggerRef} onClick={handleBookmarkButtonClick}>
<div class={styles.shoutStatsItemInner}>
<Icon name="bookmark" class={styles.icon} />
<Icon name="bookmark-hover" class={clsx(styles.icon, styles.iconHover)} />
</div>
</div>
)}
</Popover>
<Show when={canEdit()}> <Show when={canEdit()}>
<Popover content={t('Edit')}> <Popover content={t('Edit')}>
{(triggerRef: (el) => void) => ( {(triggerRef: (el) => void) => (
@ -410,20 +499,32 @@ export const FullArticle = (props: Props) => {
)} )}
</Popover> </Popover>
</Show> </Show>
<div class={clsx(styles.shoutStatsItem, styles.shoutStatsItemAdditionalData)}>
<div class={clsx(styles.shoutStatsItem, styles.shoutStatsItemAdditionalDataItem)}> <FeedArticlePopup
{formattedDate()} isOwner={canEdit()}
</div> containerCssClass={clsx(stylesHeader.control, styles.articlePopupOpener)}
</div> onShareClick={() => showModal('share')}
onInviteClick={() => showModal('inviteCoAuthors')}
onVisibilityChange={(isVisible) => setIsActionPopupActive(isVisible)}
trigger={
<button>
<Icon name="ellipsis" class={clsx(styles.icon)} />
<Icon name="ellipsis" class={clsx(styles.icon, styles.iconHover)} />
</button>
}
/>
</div> </div>
<div class={styles.help}>
<Show when={isAuthenticated() && !canEdit()}> <Show when={isAuthenticated() && !canEdit()}>
<div class={styles.help}>
<button class="button">{t('Cooperate')}</button> <button class="button">{t('Cooperate')}</button>
</Show> </div>
<Show when={canEdit()}> </Show>
<Show when={canEdit()}>
<div class={styles.help}>
<button class="button button--light">{t('Invite to collab')}</button> <button class="button button--light">{t('Invite to collab')}</button>
</Show> </div>
</div> </Show>
<Show when={props.article.topics.length}> <Show when={props.article.topics.length}>
<div class={styles.topicsList}> <div class={styles.topicsList}>
@ -461,6 +562,16 @@ export const FullArticle = (props: Props) => {
</div> </div>
</div> </div>
</div> </div>
<Show when={selectedImage()}>
<Lightbox image={selectedImage()} onClose={handleLightboxClose} />
</Show>
<InviteCoAuthorsModal title={t('Invite experts')} />
<ShareModal
title={props.article.title}
description={description}
imageUrl={props.article.cover}
shareUrl={shareUrl}
/>
</> </>
) )
} }

View File

@ -1,24 +0,0 @@
import MD from 'markdown-it'
import mdfig from 'markdown-it-implicit-figures'
import mdmark from 'markdown-it-mark'
import mdcustom from 'markdown-it-container'
import mdlinks from 'markdown-it-replace-link'
import { createMemo } from 'solid-js'
const mit = MD({
html: true,
linkify: true,
typographer: true
})
mit.use(mdmark)
mit.use(mdcustom)
mit.use(mdfig, {
dataType: false, // <figure data-type="image">
figcaption: true // <figcaption>alternative text</figcaption>
})
mit.use(mdlinks)
export default (props: { body: string }) => {
const body = createMemo(() => (props.body.startsWith('<') ? props.body : mit.render(props.body)))
return <div innerHTML={body()} />
}

View File

@ -1,19 +1,16 @@
import { Icon } from '../_shared/Icon'
import { createSocialShare, TWITTER, VK, FACEBOOK, TELEGRAM } from '@solid-primitives/share'
import styles from '../_shared/Popup/Popup.module.scss'
import type { PopupProps } from '../_shared/Popup' import type { PopupProps } from '../_shared/Popup'
import { Popup } from '../_shared/Popup'
import { useLocalize } from '../../context/localize'
import { createEffect, createSignal } from 'solid-js' import { createEffect, createSignal } from 'solid-js'
import { useSnackbar } from '../../context/snackbar'
import { Popup } from '../_shared/Popup'
import { ShareLinks } from '../_shared/ShareLinks'
type SharePopupProps = { type SharePopupProps = {
title: string title: string
shareUrl?: string shareUrl: string
imageUrl: string imageUrl: string
description: string description: string
isVisible?: (value: boolean) => void onVisibilityChange?: (value: boolean) => void
} & Omit<PopupProps, 'children'> } & Omit<PopupProps, 'children'>
export const getShareUrl = (params: { pathname?: string } = {}) => { export const getShareUrl = (params: { pathname?: string } = {}) => {
@ -23,63 +20,22 @@ export const getShareUrl = (params: { pathname?: string } = {}) => {
} }
export const SharePopup = (props: SharePopupProps) => { export const SharePopup = (props: SharePopupProps) => {
const { t } = useLocalize()
const [isVisible, setIsVisible] = createSignal(false) const [isVisible, setIsVisible] = createSignal(false)
const {
actions: { showSnackbar }
} = useSnackbar()
createEffect(() => { createEffect(() => {
if (props.isVisible) { if (props.onVisibilityChange) {
props.isVisible(isVisible()) props.onVisibilityChange(isVisible())
} }
}) })
const [share] = createSocialShare(() => ({
title: props.title,
url: props.shareUrl,
description: props.description
}))
const copyLink = async () => {
await navigator.clipboard.writeText(props.shareUrl)
showSnackbar({ body: t('Link copied') })
}
return ( return (
<Popup {...props} variant="bordered" onVisibilityChange={(value) => setIsVisible(value)}> <Popup {...props} variant="bordered" onVisibilityChange={(value) => setIsVisible(value)}>
<ul class="nodash"> <ShareLinks
<li> variant="inPopup"
<button role="button" class={styles.shareControl} onClick={() => share(VK)}> title={props.title}
<Icon name="vk-white" class={styles.icon} /> shareUrl={props.shareUrl}
VK imageUrl={props.imageUrl}
</button> description={props.description}
</li> />
<li>
<button role="button" class={styles.shareControl} onClick={() => share(FACEBOOK)}>
<Icon name="facebook-white" class={styles.icon} />
Facebook
</button>
</li>
<li>
<button role="button" class={styles.shareControl} onClick={() => share(TWITTER)}>
<Icon name="twitter-white" class={styles.icon} />
Twitter
</button>
</li>
<li>
<button role="button" class={styles.shareControl} onClick={() => share(TELEGRAM)}>
<Icon name="telegram-white" class={styles.icon} />
Telegram
</button>
</li>
<li>
<button role="button" class={styles.shareControl} onClick={copyLink}>
<Icon name="link-white" class={styles.icon} />
{t('Copy link')}
</button>
</li>
</ul>
</Popup> </Popup>
) )
} }

View File

@ -1,13 +1,15 @@
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { createMemo, Show } from 'solid-js' import { createMemo, Show } from 'solid-js'
import { useLocalize } from '../../context/localize'
import { useReactions } from '../../context/reactions'
import { useSession } from '../../context/session'
import { ReactionKind, Shout } from '../../graphql/types.gen' import { ReactionKind, Shout } from '../../graphql/types.gen'
import { loadShout } from '../../stores/zine/articles' import { loadShout } from '../../stores/zine/articles'
import { useSession } from '../../context/session' import { Icon } from '../_shared/Icon'
import { useReactions } from '../../context/reactions'
import { Popup } from '../_shared/Popup' import { Popup } from '../_shared/Popup'
import { VotersList } from '../_shared/VotersList' import { VotersList } from '../_shared/VotersList'
import { useLocalize } from '../../context/localize'
import { Icon } from '../_shared/Icon'
import styles from './ShoutRatingControl.module.scss' import styles from './ShoutRatingControl.module.scss'
interface ShoutRatingControlProps { interface ShoutRatingControlProps {
@ -19,12 +21,12 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
const { t } = useLocalize() const { t } = useLocalize()
const { const {
user, user,
actions: { requireAuthentication } actions: { requireAuthentication },
} = useSession() } = useSession()
const { const {
reactionEntities, reactionEntities,
actions: { createReaction, deleteReaction, loadReactionsBy } actions: { createReaction, deleteReaction, loadReactionsBy },
} = useReactions() } = useReactions()
const checkReaction = (reactionKind: ReactionKind) => const checkReaction = (reactionKind: ReactionKind) =>
@ -33,7 +35,7 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
r.kind === reactionKind && r.kind === reactionKind &&
r.createdBy.slug === user()?.slug && r.createdBy.slug === user()?.slug &&
r.shout.id === props.shout.id && r.shout.id === props.shout.id &&
!r.replyTo !r.replyTo,
) )
const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like)) const isUpvoted = createMemo(() => checkReaction(ReactionKind.Like))
@ -45,8 +47,8 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
(r) => (r) =>
[ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) && [ReactionKind.Like, ReactionKind.Dislike].includes(r.kind) &&
r.shout.id === props.shout.id && r.shout.id === props.shout.id &&
!r.replyTo !r.replyTo,
) ),
) )
const deleteShoutReaction = async (reactionKind: ReactionKind) => { const deleteShoutReaction = async (reactionKind: ReactionKind) => {
@ -55,7 +57,7 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
r.kind === reactionKind && r.kind === reactionKind &&
r.createdBy.slug === user()?.slug && r.createdBy.slug === user()?.slug &&
r.shout.id === props.shout.id && r.shout.id === props.shout.id &&
!r.replyTo !r.replyTo,
) )
return deleteReaction(reactionToDelete.id) return deleteReaction(reactionToDelete.id)
} }
@ -69,13 +71,13 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => {
} else { } else {
await createReaction({ await createReaction({
kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike, kind: isUpvote ? ReactionKind.Like : ReactionKind.Dislike,
shout: props.shout.id shout: props.shout.id,
}) })
} }
loadShout(props.shout.slug) loadShout(props.shout.slug)
loadReactionsBy({ loadReactionsBy({
by: { shout: props.shout.slug } by: { shout: props.shout.slug },
}) })
}, 'vote') }, 'vote')
} }

View File

@ -1,8 +1,9 @@
import { createEffect, JSX, Show } from 'solid-js' import { createEffect, JSX, Show } from 'solid-js'
import { useSession } from '../../context/session' import { useSession } from '../../context/session'
import { hideModal } from '../../stores/ui'
import { useRouter } from '../../stores/router'
import { RootSearchParams } from '../../pages/types' import { RootSearchParams } from '../../pages/types'
import { useRouter } from '../../stores/router'
import { hideModal } from '../../stores/ui'
import { AuthModalSearchParams } from '../Nav/AuthModal/types' import { AuthModalSearchParams } from '../Nav/AuthModal/types'
type Props = { type Props = {
@ -12,7 +13,7 @@ type Props = {
export const AuthGuard = (props: Props) => { export const AuthGuard = (props: Props) => {
const { isAuthenticated, isSessionLoaded } = useSession() const { isAuthenticated, isSessionLoaded } = useSession()
const { changeSearchParam } = useRouter<RootSearchParams & AuthModalSearchParams>() const { changeSearchParams } = useRouter<RootSearchParams & AuthModalSearchParams>()
createEffect(() => { createEffect(() => {
if (props.disabled) { if (props.disabled) {
@ -22,12 +23,12 @@ export const AuthGuard = (props: Props) => {
if (isAuthenticated()) { if (isAuthenticated()) {
hideModal() hideModal()
} else { } else {
changeSearchParam( changeSearchParams(
{ {
source: 'authguard', source: 'authguard',
modal: 'auth' modal: 'auth',
}, },
true true,
) )
} }
} }

View File

@ -1,8 +1,7 @@
.AuthorLink { .AuthorLink {
.link { .link {
display: inline-flex; display: inline-flex;
flex-direction: row; flex-flow: row nowrap;
flex-wrap: nowrap;
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
justify-content: center; justify-content: center;
@ -33,6 +32,7 @@
.link { .link {
gap: 0.5rem; gap: 0.5rem;
} }
.name { .name {
font-size: 1.2rem; font-size: 1.2rem;
margin: 0; margin: 0;
@ -43,8 +43,22 @@
.link { .link {
gap: 1rem; gap: 1rem;
} }
.name { .name {
font-size: 1.4rem; font-size: 1.4rem;
} }
} }
} }
.authorLinkFloorImportant {
.link {
&:hover {
background: #fff !important;
.name {
background: none;
color: #000;
}
}
}
}

View File

@ -1,17 +1,24 @@
import { clsx } from 'clsx' import { clsx } from 'clsx'
import styles from './AhtorLink.module.scss'
import { Author } from '../../../graphql/types.gen' import { Author } from '../../../graphql/types.gen'
import { Userpic } from '../Userpic' import { Userpic } from '../Userpic'
import styles from './AhtorLink.module.scss'
type Props = { type Props = {
author: Author author: Author
size?: 'XS' | 'M' | 'L' size?: 'XS' | 'M' | 'L'
class?: string class?: string
isFloorImportant?: boolean
} }
export const AuthorLink = (props: Props) => { export const AuthorLink = (props: Props) => {
return ( return (
<div class={clsx(styles.AuthorLink, props.class, styles[props.size ?? 'M'])}> <div
class={clsx(styles.AuthorLink, props.class, styles[props.size ?? 'M'], {
[styles.authorLinkFloorImportant]: props.isFloorImportant,
})}
>
<a class={styles.link} href={`/author/${props.author.slug}`}> <a class={styles.link} href={`/author/${props.author.slug}`}>
<Userpic size={props.size ?? 'M'} name={props.author.name} userpic={props.author.userpic} /> <Userpic size={props.size ?? 'M'} name={props.author.name} userpic={props.author.userpic} />
<div class={styles.name}>{props.author.name}</div> <div class={styles.name}>{props.author.name}</div>

View File

@ -1,27 +1,40 @@
.AuthorBadge { .AuthorBadge {
align-items: flex-start; align-items: flex-start;
display: flex; display: flex;
flex-flow: row nowrap;
margin-bottom: 2rem;
gap: 1rem; gap: 1rem;
margin-bottom: 2rem;
@include media-breakpoint-down(sm) { &.nameOnly {
flex-wrap: wrap; align-items: center;
margin-bottom: 3rem;
.info {
margin-bottom: 0;
}
} }
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
text-align: left; text-align: left;
} }
.basicInfo {
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 { .info {
@include font-size(1.4rem); @include font-size(1.4rem);
border: none; border: none;
display: flex; display: flex;
flex: 0 calc(100% - 5.2rem);
flex-direction: column; flex-direction: column;
line-height: 1.3; line-height: 1.3;
margin-bottom: 1rem;
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
flex: 1 100%; flex: 1 100%;
@ -43,6 +56,7 @@
.bio { .bio {
color: var(--black-400); color: var(--black-400);
font-weight: 500;
} }
} }
@ -53,6 +67,10 @@
margin-left: 5.2rem; margin-left: 5.2rem;
gap: 1rem; gap: 1rem;
@include media-breakpoint-down(sm) {
margin-left: 0;
}
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
margin-left: 2rem; margin-left: 2rem;
} }
@ -72,11 +90,26 @@
&.iconed { &.iconed {
padding: 6px !important; padding: 6px !important;
min-width: 32px; min-width: 4rem;
width: unset; width: unset;
&:hover img { &:hover img {
filter: invert(1); filter: invert(1);
} }
} }
&:hover {
.actionButtonLabel {
display: none;
}
.actionButtonLabelHovered {
display: block;
}
}
}
.actionButtonLabelHovered {
display: none;
} }
} }

View File

@ -1,34 +1,46 @@
import { clsx } from 'clsx'
import styles from './AuthorBadge.module.scss'
import { Userpic } from '../Userpic'
import { Author, FollowingEntity } from '../../../graphql/types.gen'
import { createMemo, createSignal, Match, Show, Switch } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { Button } from '../../_shared/Button'
import { useSession } from '../../../context/session'
import { follow, unfollow } from '../../../stores/zine/common'
import { CheckButton } from '../../_shared/CheckButton'
import { openPage } from '@nanostores/router' import { openPage } from '@nanostores/router'
import { clsx } from 'clsx'
import { createEffect, createMemo, createSignal, Match, Show, Switch } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { useMediaQuery } from '../../../context/mediaQuery'
import { useSession } from '../../../context/session'
import { Author, FollowingEntity } from '../../../graphql/types.gen'
import { router, useRouter } from '../../../stores/router' import { router, useRouter } from '../../../stores/router'
import { follow, unfollow } from '../../../stores/zine/common'
import { Button } from '../../_shared/Button'
import { CheckButton } from '../../_shared/CheckButton'
import { Icon } from '../../_shared/Icon' import { Icon } from '../../_shared/Icon'
import { Userpic } from '../Userpic'
import styles from './AuthorBadge.module.scss'
import stylesButton from '../../_shared/Button/Button.module.scss'
type Props = { type Props = {
author: Author author: Author
minimizeSubscribeButton?: boolean minimizeSubscribeButton?: boolean
showMessageButton?: boolean showMessageButton?: boolean
iconButtons?: boolean iconButtons?: boolean
nameOnly?: boolean
} }
export const AuthorBadge = (props: Props) => { export const AuthorBadge = (props: Props) => {
const { mediaMatches } = useMediaQuery()
const [isMobileView, setIsMobileView] = createSignal(false)
const [isSubscribing, setIsSubscribing] = createSignal(false) const [isSubscribing, setIsSubscribing] = createSignal(false)
createEffect(() => {
setIsMobileView(!mediaMatches.sm)
})
const { const {
session, session,
subscriptions, subscriptions,
actions: { loadSubscriptions, requireAuthentication } actions: { loadSubscriptions, requireAuthentication },
} = useSession() } = useSession()
const { changeSearchParam } = useRouter() const { changeSearchParams } = useRouter()
const { t, formatDate } = useLocalize() const { t, formatDate } = useLocalize()
const subscribed = createMemo(() => const subscribed = createMemo(() =>
subscriptions().authors.some((author) => author.slug === props.author.slug) subscriptions().authors.some((author) => author.slug === props.author.slug),
) )
const subscribe = async (really = true) => { const subscribe = async (really = true) => {
@ -50,49 +62,47 @@ export const AuthorBadge = (props: Props) => {
const initChat = () => { const initChat = () => {
requireAuthentication(() => { requireAuthentication(() => {
openPage(router, `inbox`) openPage(router, `inbox`)
changeSearchParam({ changeSearchParams({
initChat: props.author.id.toString() initChat: props.author.id.toString(),
}) })
}, 'discussions') }, 'discussions')
} }
const subscribeValue = createMemo(() => {
if (props.iconButtons) {
return <Icon name="author-subscribe" />
}
return isSubscribing() ? t('...subscribing') : t('Subscribe')
})
return ( return (
<div class={clsx(styles.AuthorBadge)}> <div class={clsx(styles.AuthorBadge, { [styles.nameOnly]: props.nameOnly })}>
<Userpic <div class={styles.basicInfo}>
hasLink={true} <Userpic
size={'M'} hasLink={true}
name={props.author.name} size={isMobileView() ? 'M' : 'L'}
userpic={props.author.userpic} name={props.author.name}
slug={props.author.slug} userpic={props.author.userpic}
/> slug={props.author.slug}
<a href={`/author/${props.author.slug}`} class={styles.info}> />
<div class={styles.name}> <a href={`/author/${props.author.slug}`} class={styles.info}>
<span>{props.author.name}</span> <div class={styles.name}>
</div> <span>{props.author.name}</span>
<Switch </div>
fallback={ <Show when={!props.nameOnly}>
<div class={styles.bio}> <Switch
{t('Registered since {date}', { date: formatDate(new Date(props.author.createdAt)) })} fallback={
</div> <div class={styles.bio}>
} {t('Registered since {date}', { date: formatDate(new Date(props.author.createdAt)) })}
> </div>
<Match when={props.author.bio}> }
<div class={clsx('text-truncate', styles.bio)} innerHTML={props.author.bio} /> >
</Match> <Match when={props.author.bio}>
<Match when={props.author?.stat && props.author?.stat.shouts > 0}> <div class={clsx('text-truncate', styles.bio)} innerHTML={props.author.bio} />
<div class={styles.bio}> </Match>
{t('PublicationsWithCount', { count: props.author.stat?.shouts ?? 0 })} <Match when={props.author?.stat && props.author?.stat.shouts > 0}>
</div> <div class={styles.bio}>
</Match> {t('PublicationsWithCount', { count: props.author.stat?.shouts ?? 0 })}
</Switch> </div>
</a> </Match>
<Show when={props.author.slug !== session()?.user.slug}> </Switch>
</Show>
</a>
</div>
<Show when={props.author.slug !== session()?.user.slug && !props.nameOnly}>
<div class={styles.actions}> <div class={styles.actions}>
<Show <Show
when={!props.minimizeSubscribeButton} when={!props.minimizeSubscribeButton}
@ -110,18 +120,49 @@ export const AuthorBadge = (props: Props) => {
<Button <Button
variant={props.iconButtons ? 'secondary' : 'bordered'} variant={props.iconButtons ? 'secondary' : 'bordered'}
size="S" size="S"
value={subscribeValue()} value={
<Show
when={props.iconButtons}
fallback={
<Show when={isSubscribing()} fallback={t('Subscribe')}>
{t('subscribing...')}
</Show>
}
>
<Icon name="author-subscribe" class={stylesButton.icon} />
</Show>
}
onClick={() => handleSubscribe(true)} onClick={() => handleSubscribe(true)}
class={clsx(styles.actionButton, { [styles.iconed]: props.iconButtons })} isSubscribeButton={true}
class={clsx(styles.actionButton, {
[styles.iconed]: props.iconButtons,
[stylesButton.subscribed]: subscribed(),
})}
/> />
} }
> >
<Button <Button
variant={props.iconButtons ? 'secondary' : 'bordered'} variant={props.iconButtons ? 'secondary' : 'bordered'}
size="S" size="S"
value={props.iconButtons ? <Icon name="author-unsubscribe" /> : t('Following')} value={
<Show
when={props.iconButtons}
fallback={
<>
<span class={styles.actionButtonLabel}>{t('Following')}</span>
<span class={styles.actionButtonLabelHovered}>{t('Unfollow')}</span>
</>
}
>
<Icon name="author-unsubscribe" class={stylesButton.icon} />
</Show>
}
onClick={() => handleSubscribe(false)} onClick={() => handleSubscribe(false)}
class={clsx(styles.actionButton, { [styles.iconed]: props.iconButtons })} isSubscribeButton={true}
class={clsx(styles.actionButton, {
[styles.iconed]: props.iconButtons,
[stylesButton.subscribed]: subscribed(),
})}
/> />
</Show> </Show>
</Show> </Show>

View File

@ -43,8 +43,23 @@
} }
} }
.authorActionsLabel {
@include media-breakpoint-down(sm) {
display: none;
}
}
.authorActionsLabelMobile {
display: none;
@include media-breakpoint-down(sm) {
display: block;
}
}
.authorDetails { .authorDetails {
display: block; display: block;
margin-bottom: 0;
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
flex: 1 100%; flex: 1 100%;
@ -77,37 +92,10 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
.buttonSubscribe { .buttonWriteMessage {
aspect-ratio: auto;
background-color: #000;
border: 1px solid #000;
border-radius: 0.8rem; border-radius: 0.8rem;
color: #fff;
float: none;
padding-bottom: 0.6rem; padding-bottom: 0.6rem;
padding-top: 0.6rem; padding-top: 0.6rem;
width: 10em;
.icon {
margin-right: 0.5em;
img {
filter: invert(1);
}
}
&:hover {
background: #fff;
color: #000;
.icon img {
filter: invert(0);
}
}
img {
vertical-align: text-top;
}
} }
} }
@ -174,7 +162,7 @@
.authorSubscribeSocial { .authorSubscribeSocial {
align-items: center; align-items: center;
display: flex; display: flex;
margin: 2rem 0; margin: 0.5rem 0 2rem -0.4rem;
.socialLink { .socialLink {
border: none; border: none;
@ -243,7 +231,8 @@
} }
} }
&[href*='telegram.com/'] { &[href*='telegram.com/'],
&[href*='t.me/'] {
&::before { &::before {
background-image: url(/icons/user-link-telegram.svg); background-image: url(/icons/user-link-telegram.svg);
} }
@ -429,7 +418,6 @@
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
flex: 1 100%; flex: 1 100%;
justify-content: center; justify-content: center;
margin-top: 1em;
} }
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
@ -446,7 +434,6 @@
flex-wrap: wrap; flex-wrap: wrap;
font-size: 1.4rem; font-size: 1.4rem;
margin-top: 1.5rem; margin-top: 1.5rem;
gap: 1rem;
@include media-breakpoint-down(md) { @include media-breakpoint-down(md) {
justify-content: center; justify-content: center;
@ -457,10 +444,18 @@
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
display: inline-flex; display: inline-flex;
margin-right: 3rem; margin: 0 2% 1rem;
vertical-align: top; vertical-align: top;
border-bottom: unset !important; border-bottom: unset !important;
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
.subscribersItem { .subscribersItem {
position: relative; position: relative;

View File

@ -1,37 +1,40 @@
import type { Author } from '../../../graphql/types.gen' import type { Author } from '../../../graphql/types.gen'
import { Userpic } from '../Userpic'
import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'
import { translit } from '../../../utils/ru2en'
import { follow, unfollow } from '../../../stores/zine/common'
import { clsx } from 'clsx'
import { useSession } from '../../../context/session'
import { ShowOnlyOnClient } from '../../_shared/ShowOnlyOnClient'
import { FollowingEntity, Topic } from '../../../graphql/types.gen'
import { router, useRouter } from '../../../stores/router'
import { openPage, redirectPage } from '@nanostores/router' import { openPage, redirectPage } from '@nanostores/router'
import { clsx } from 'clsx'
import { createEffect, createMemo, createSignal, For, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { Modal } from '../../Nav/Modal' import { useSession } from '../../../context/session'
import { FollowingEntity, Topic } from '../../../graphql/types.gen'
import { SubscriptionFilter } from '../../../pages/types' import { SubscriptionFilter } from '../../../pages/types'
import { router, useRouter } from '../../../stores/router'
import { follow, unfollow } from '../../../stores/zine/common'
import { isAuthor } from '../../../utils/isAuthor' import { isAuthor } from '../../../utils/isAuthor'
import { AuthorBadge } from '../AuthorBadge' import { translit } from '../../../utils/ru2en'
import { TopicBadge } from '../../Topic/TopicBadge'
import { Button } from '../../_shared/Button' import { Button } from '../../_shared/Button'
import { ShowOnlyOnClient } from '../../_shared/ShowOnlyOnClient'
import { getShareUrl, SharePopup } from '../../Article/SharePopup' import { getShareUrl, SharePopup } from '../../Article/SharePopup'
import { Modal } from '../../Nav/Modal'
import { TopicBadge } from '../../Topic/TopicBadge'
import { AuthorBadge } from '../AuthorBadge'
import { Userpic } from '../Userpic'
import styles from './AuthorCard.module.scss' import styles from './AuthorCard.module.scss'
import stylesButton from '../../_shared/Button/Button.module.scss'
type Props = { type Props = {
author: Author author: Author
followers?: Author[] followers?: Author[]
following?: Array<Author | Topic> following?: Array<Author | Topic>
} }
export const AuthorCard = (props: Props) => { export const AuthorCard = (props: Props) => {
const { t, lang } = useLocalize() const { t, lang } = useLocalize()
const { const {
session, session,
subscriptions, subscriptions,
isSessionLoaded, isSessionLoaded,
actions: { loadSubscriptions, requireAuthentication } actions: { loadSubscriptions, requireAuthentication },
} = useSession() } = useSession()
const [isSubscribing, setIsSubscribing] = createSignal(false) const [isSubscribing, setIsSubscribing] = createSignal(false)
@ -39,7 +42,7 @@ export const AuthorCard = (props: Props) => {
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all') const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
const subscribed = createMemo<boolean>(() => const subscribed = createMemo<boolean>(() =>
subscriptions().authors.some((author) => author.slug === props.author.slug) subscriptions().authors.some((author) => author.slug === props.author.slug),
) )
const subscribe = async (really = true) => { const subscribe = async (really = true) => {
@ -68,12 +71,12 @@ export const AuthorCard = (props: Props) => {
}) })
// TODO: reimplement AuthorCard // TODO: reimplement AuthorCard
const { changeSearchParam } = useRouter() const { changeSearchParams } = useRouter()
const initChat = () => { const initChat = () => {
requireAuthentication(() => { requireAuthentication(() => {
openPage(router, `inbox`) openPage(router, `inbox`)
changeSearchParam({ changeSearchParams({
initChat: props.author.id.toString() initChat: props.author.id.toString(),
}) })
}, 'discussions') }, 'discussions')
} }
@ -96,12 +99,22 @@ export const AuthorCard = (props: Props) => {
} }
}) })
const followButtonText = () => { const followButtonText = createMemo(() => {
if (isSubscribing()) { if (isSubscribing()) {
return t('...subscribing') return t('subscribing...')
} }
return t(subscribed() ? 'Unfollow' : 'Follow')
} if (subscribed()) {
return (
<>
<span class={stylesButton.buttonSubscribeLabel}>{t('Following')}</span>
<span class={stylesButton.buttonSubscribeLabelHovered}>{t('Unfollow')}</span>
</>
)
}
return t('Follow')
})
return ( return (
<div class={clsx(styles.author, 'row')}> <div class={clsx(styles.author, 'row')}>
@ -117,7 +130,9 @@ export const AuthorCard = (props: Props) => {
<div class={clsx('col-md-15 col-xl-13', styles.authorDetails)}> <div class={clsx('col-md-15 col-xl-13', styles.authorDetails)}>
<div class={styles.authorDetailsWrapper}> <div class={styles.authorDetailsWrapper}>
<div class={styles.authorName}>{name()}</div> <div class={styles.authorName}>{name()}</div>
<div class={styles.authorAbout} innerHTML={props.author.bio} /> <Show when={props.author.bio}>
<div class={styles.authorAbout} innerHTML={props.author.bio} />
</Show>
<Show <Show
when={ when={
(props.followers && props.followers.length > 0) || (props.followers && props.followers.length > 0) ||
@ -205,13 +220,16 @@ export const AuthorCard = (props: Props) => {
<Button <Button
onClick={handleSubscribe} onClick={handleSubscribe}
value={followButtonText()} value={followButtonText()}
class={styles.buttonSubscribe} isSubscribeButton={true}
class={clsx({
[stylesButton.subscribed]: subscribed(),
})}
/> />
<Button <Button
variant={'secondary'} variant={'secondary'}
value={t('Message')} value={t('Message')}
onClick={initChat} onClick={initChat}
class={styles.buttonSubscribe} class={styles.buttonWriteMessage}
/> />
</div> </div>
} }
@ -220,7 +238,12 @@ export const AuthorCard = (props: Props) => {
<Button <Button
variant="secondary" variant="secondary"
onClick={() => redirectPage(router, 'profileSettings')} onClick={() => redirectPage(router, 'profileSettings')}
value={t('Edit profile')} value={
<>
<span class={styles.authorActionsLabel}>{t('Edit profile')}</span>
<span class={styles.authorActionsLabelMobile}>{t('Edit')}</span>
</>
}
/> />
<SharePopup <SharePopup
title={props.author.name} title={props.author.name}
@ -234,7 +257,7 @@ export const AuthorCard = (props: Props) => {
</Show> </Show>
</ShowOnlyOnClient> </ShowOnlyOnClient>
<Show when={props.followers}> <Show when={props.followers}>
<Modal variant="medium" name="followers" maxHeight> <Modal variant="medium" isResponsive={true} name="followers" maxHeight>
<> <>
<h2>{t('Followers')}</h2> <h2>{t('Followers')}</h2>
<div class={styles.listWrapper}> <div class={styles.listWrapper}>
@ -250,7 +273,7 @@ export const AuthorCard = (props: Props) => {
</Modal> </Modal>
</Show> </Show>
<Show when={props.following}> <Show when={props.following}>
<Modal variant="medium" name="following" maxHeight> <Modal variant="medium" isResponsive={true} name="following" maxHeight>
<> <>
<h2>{t('Subscriptions')}</h2> <h2>{t('Subscriptions')}</h2>
<ul class="view-switcher"> <ul class="view-switcher">

View File

@ -1,7 +1,9 @@
import styles from './AuthorRatingControl.module.scss'
import { clsx } from 'clsx'
import type { Author } from '../../graphql/types.gen' import type { Author } from '../../graphql/types.gen'
import { clsx } from 'clsx'
import styles from './AuthorRatingControl.module.scss'
interface AuthorRatingControlProps { interface AuthorRatingControlProps {
author: Author author: Author
class?: string class?: string
@ -20,7 +22,7 @@ export const AuthorRatingControl = (props: AuthorRatingControlProps) => {
<div <div
class={clsx(styles.rating, props.class, { class={clsx(styles.rating, props.class, {
[styles.isUpvoted]: isUpvoted, [styles.isUpvoted]: isUpvoted,
[styles.isDownvoted]: isDownvoted [styles.isDownvoted]: isDownvoted,
})} })}
> >
<button <button

View File

@ -75,6 +75,16 @@
} }
} }
&.L {
height: 40px;
width: 40px;
min-width: 40px;
.letters {
font-size: 1.2rem;
}
}
&.XL { &.XL {
aspect-ratio: 1/1; aspect-ratio: 1/1;
margin: 0 auto 1rem; margin: 0 auto 1rem;

View File

@ -1,9 +1,11 @@
import { createMemo, Show } from 'solid-js'
import styles from './Userpic.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { createMemo, Show } from 'solid-js'
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper' import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
import { Loading } from '../../_shared/Loading'
import { Image } from '../../_shared/Image' import { Image } from '../../_shared/Image'
import { Loading } from '../../_shared/Loading'
import styles from './Userpic.module.scss'
type Props = { type Props = {
name: string name: string
@ -46,7 +48,7 @@ export const Userpic = (props: Props) => {
return ( return (
<div <div
class={clsx(styles.Userpic, props.class, styles[props.size ?? 'M'], { class={clsx(styles.Userpic, props.class, styles[props.size ?? 'M'], {
['cursorPointer']: props.onClick ['cursorPointer']: props.onClick,
})} })}
onClick={props.onClick} onClick={props.onClick}
> >
@ -55,7 +57,7 @@ export const Userpic = (props: Props) => {
condition={props.hasLink} condition={props.hasLink}
wrapper={(children) => <a href={`/author/${props.slug}`}>{children}</a>} wrapper={(children) => <a href={`/author/${props.slug}`}>{children}</a>}
> >
<Show when={props.userpic} fallback={<div class={styles.letters}>{letters()}</div>}> <Show keyed={true} when={props.userpic} fallback={<div class={styles.letters}>{letters()}</div>}>
<Image src={props.userpic} width={avatarSize()} height={avatarSize()} alt={props.name} /> <Image src={props.userpic} width={avatarSize()} height={avatarSize()} alt={props.name} />
</Show> </Show>
</ConditionalWrapper> </ConditionalWrapper>

View File

@ -1,8 +1,10 @@
import styles from './Banner.module.scss'
import { showModal } from '../../stores/ui'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { showModal } from '../../stores/ui'
import { Image } from '../_shared/Image'
import styles from './Banner.module.scss'
export default () => { export default () => {
const { t } = useLocalize() const { t } = useLocalize()
@ -21,7 +23,11 @@ export default () => {
</p> </p>
</div> </div>
<div class={clsx(styles.discoursBannerImage, 'col-lg-12 offset-lg-2')}> <div class={clsx(styles.discoursBannerImage, 'col-lg-12 offset-lg-2')}>
<img src="/discours-banner.jpg" alt={t('Discours')} /> <Image
src="https://images.discours.io/unsafe/production/image/discours-banner.jpg"
alt={t('Discours')}
width={600}
/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
.donate-form input, .donateForm input,
.donate-form label, .donateForm label,
.donate-form .btn { .donateForm .btn {
font-family: Muller, Arial, Helvetica, sans-serif; font-family: Muller, Arial, Helvetica, sans-serif;
border: solid 1px #595959; border: solid 1px #595959;
border-radius: 3px; border-radius: 3px;
@ -10,7 +10,7 @@
text-align: center; text-align: center;
} }
.donate-form input { .donateForm input {
&::-webkit-outer-spin-button, &::-webkit-outer-spin-button,
&::-webkit-inner-spin-button { &::-webkit-inner-spin-button {
appearance: none; appearance: none;
@ -48,12 +48,13 @@
} }
} }
.btn { .donateForm .btn {
cursor: pointer; cursor: pointer;
flex: 1; flex: 1;
padding: 5px 10px; padding: 5px 10px;
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;
transform: none !important;
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
&:last-of-type { &:last-of-type {
@ -62,7 +63,7 @@
} }
} }
.btn-group { .btnGroup {
input { input {
&:first-child + .btn { &:first-child + .btn {
border-top-right-radius: 0; border-top-right-radius: 0;
@ -75,12 +76,13 @@
} }
} }
.payment-type { .paymentType {
width: 50%; width: 50%;
} }
} }
.donate-buttons-container { .donateButtonsContainer {
align-items: center;
display: flex; display: flex;
flex: 1; flex: 1;
justify-content: space-between; justify-content: space-between;
@ -111,59 +113,34 @@
} }
} }
.donate-input { .donateInput {
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
flex: 1 100%; flex: 1 100%;
margin: 0 !important; margin: 0 !important;
} }
} }
.send-btn { .sendBtn {
border: 1px solid #000; border: 2px solid #000;
background-color: #000; background-color: #000;
color: #fff; color: #fff !important;
display: block; display: block;
font-weight: 700; font-weight: 700;
line-height: 1.8; line-height: 1.8;
letter-spacing: 0.05em; letter-spacing: 0.05em;
text-transform: uppercase; text-transform: uppercase;
width: 100%; width: 100%;
&:hover {
background-color: #fff !important;
color: #000 !important;
}
} }
.payment-choose { .paymentChoose {
display: flex; display: flex;
} }
.form-group:not(:first-child) { .formGroup:not(:first-child) {
margin-top: 20px; margin-top: 20px;
} }
.discours-help .modalwrap__inner {
max-width: 500px;
}
/*
.payment-form {
padding: 0 !important;
.button {
display: block;
padding-bottom: 1.5rem;
padding-top: 1.5rem;
width: 100%;
}
}
.delimiter-container {
position: relative;
}
.delimiter {
left: 100%;
line-height: 1;
position: absolute;
top: 50%;
transform: translate(-50%, calc(-50% - 0.8rem));
}
*/

View File

@ -1,8 +1,11 @@
import '../../styles/help.scss' import { clsx } from 'clsx'
import { createSignal, onMount } from 'solid-js' import { createSignal, onMount } from 'solid-js'
import { showModal } from '../../stores/ui'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { useSnackbar } from '../../context/snackbar' import { useSnackbar } from '../../context/snackbar'
import { showModal } from '../../stores/ui'
import styles from './Donate.module.scss'
export const Donate = () => { export const Donate = () => {
const { t } = useLocalize() const { t } = useLocalize()
@ -11,7 +14,7 @@ export const Donate = () => {
const cpOptions = { const cpOptions = {
publicId: 'pk_0a37bab30ffc6b77b2f93d65f2aed', publicId: 'pk_0a37bab30ffc6b77b2f93d65f2aed',
description: t('Help discours to grow'), description: t('Help discours to grow'),
currency: 'RUB' currency: 'RUB',
} }
let amountSwitchElement: HTMLDivElement | undefined let amountSwitchElement: HTMLDivElement | undefined
@ -22,13 +25,13 @@ export const Donate = () => {
const [period, setPeriod] = createSignal(monthly) const [period, setPeriod] = createSignal(monthly)
const [amount, setAmount] = createSignal(0) const [amount, setAmount] = createSignal(0)
const { const {
actions: { showSnackbar } actions: { showSnackbar },
} = useSnackbar() } = useSnackbar()
const initiated = () => { const initiated = () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const { const {
cp: { CloudPayments } cp: { CloudPayments },
} = window as any // Checkout(cpOptions) } = window as any // Checkout(cpOptions)
setWidget(new CloudPayments()) setWidget(new CloudPayments())
console.log('[donate] payments initiated') console.log('[donate] payments initiated')
@ -42,8 +45,8 @@ export const Donate = () => {
amount: amount() || 0, //сумма amount: amount() || 0, //сумма
vat: 20, //ставка НДС vat: 20, //ставка НДС
method: 0, // тег-1214 признак способа расчета - признак способа расчета method: 0, // тег-1214 признак способа расчета - признак способа расчета
object: 0 // тег-1212 признак предмета расчета - признак предмета товара, работы, услуги, платежа, выплаты, иного предмета расчета object: 0, // тег-1212 признак предмета расчета - признак предмета товара, работы, услуги, платежа, выплаты, иного предмета расчета
} },
], ],
// taxationSystem: 0, //система налогообложения; необязательный, если у вас одна система налогообложения // taxationSystem: 0, //система налогообложения; необязательный, если у вас одна система налогообложения
// email: 'user@example.com', //e-mail покупателя, если нужно отправить письмо с чеком // email: 'user@example.com', //e-mail покупателя, если нужно отправить письмо с чеком
@ -53,8 +56,8 @@ export const Donate = () => {
electronic: amount(), // Сумма оплаты электронными деньгами electronic: amount(), // Сумма оплаты электронными деньгами
advancePayment: 0, // Сумма из предоплаты (зачетом аванса) (2 знака после запятой) advancePayment: 0, // Сумма из предоплаты (зачетом аванса) (2 знака после запятой)
credit: 0, // Сумма постоплатой(в кредит) (2 знака после запятой) credit: 0, // Сумма постоплатой(в кредит) (2 знака после запятой)
provision: 0 // Сумма оплаты встречным предоставлением (сертификаты, др. мат.ценности) (2 знака после запятой) provision: 0, // Сумма оплаты встречным предоставлением (сертификаты, др. мат.ценности) (2 знака после запятой)
} },
}) })
} }
@ -93,10 +96,10 @@ export const Donate = () => {
recurrent: { recurrent: {
interval: period(), // local solid's signal interval: period(), // local solid's signal
period: 1, // internal widget's period: 1, // internal widget's
CustomerReciept: customerReciept() // чек для регулярных платежей CustomerReciept: customerReciept(), // чек для регулярных платежей
} },
} },
} },
}, },
(opts) => { (opts) => {
// success // success
@ -111,34 +114,34 @@ export const Donate = () => {
showSnackbar({ showSnackbar({
type: 'error', type: 'error',
body: reason body: reason,
}) })
} },
) )
} }
return ( return (
<form class="discours-form donate-form" action="" method="post"> <form class={styles.donateForm} action="" method="post">
<input type="hidden" name="shopId" value="156465" /> <input type="hidden" name="shopId" value="156465" />
<input value="148805" name="scid" type="hidden" /> <input value="148805" name="scid" type="hidden" />
<input value="0" name="customerNumber" type="hidden" /> <input value="0" name="customerNumber" type="hidden" />
<div class="form-group"> <div class={styles.formGroup}>
<div class="donate-buttons-container" ref={amountSwitchElement}> <div class={styles.donateButtonsContainer} ref={amountSwitchElement}>
<input type="radio" name="amount" id="fix250" value="250" /> <input type="radio" name="amount" id="fix250" value="250" />
<label for="fix250" class="btn donate-value-radio"> <label for="fix250" class={styles.btn}>
250&thinsp; 250&thinsp;
</label> </label>
<input type="radio" name="amount" id="fix500" value="500" checked /> <input type="radio" name="amount" id="fix500" value="500" checked />
<label for="fix500" class="btn donate-value-radio"> <label for="fix500" class={styles.btn}>
500&thinsp; 500&thinsp;
</label> </label>
<input type="radio" name="amount" id="fix1000" value="1000" /> <input type="radio" name="amount" id="fix1000" value="1000" />
<label for="fix1000" class="btn donate-value-radio"> <label for="fix1000" class={styles.btn}>
1000&thinsp; 1000&thinsp;
</label> </label>
<input <input
class="form-control donate-input" class={styles.donateInput}
required required
ref={customAmountElement} ref={customAmountElement}
type="number" type="number"
@ -148,8 +151,8 @@ export const Donate = () => {
</div> </div>
</div> </div>
<div class="form-group" id="payment-type" classList={{ showing: showingPayment() }}> <div class={styles.formGroup} id="payment-type" classList={{ showing: showingPayment() }}>
<div class="btn-group payment-choose" data-toggle="buttons"> <div class={clsx(styles.btnGroup, styles.paymentChoose)} data-toggle="buttons">
<input <input
type="radio" type="radio"
autocomplete="off" autocomplete="off"
@ -158,7 +161,11 @@ export const Donate = () => {
onClick={() => setPeriod(once)} onClick={() => setPeriod(once)}
checked={period() === once} checked={period() === once}
/> />
<label for="once" class="btn payment-type" classList={{ active: period() === once }}> <label
for="once"
class={clsx(styles.btn, styles.paymentType)}
classList={{ active: period() === once }}
>
{t('One time')} {t('One time')}
</label> </label>
<input <input
@ -169,16 +176,20 @@ export const Donate = () => {
onClick={() => setPeriod(monthly)} onClick={() => setPeriod(monthly)}
checked={period() === monthly} checked={period() === monthly}
/> />
<label for="monthly" class="btn payment-type" classList={{ active: period() === monthly }}> <label
for="monthly"
class={clsx(styles.btn, styles.paymentType)}
classList={{ active: period() === monthly }}
>
{t('Every month')} {t('Every month')}
</label> </label>
</div> </div>
</div> </div>
<div class="form-group"> <div class={styles.formGroup}>
<a href={''} class="btn send-btn donate" onClick={show}> <button type="button" class={clsx(styles.btn, styles.sendBtn)} onClick={show}>
{t('Help discours to grow')} {t('Help discours to grow')}
</a> </button>
</div> </div>
</form> </form>
) )

View File

@ -1,5 +1,5 @@
import { hideModal } from '../../stores/ui'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { hideModal } from '../../stores/ui'
import { Button } from '../_shared/Button' import { Button } from '../_shared/Button'
export const Feedback = () => { export const Feedback = () => {
@ -14,9 +14,9 @@ export const Feedback = () => {
method, method,
headers: { headers: {
accept: 'application/json', accept: 'application/json',
'content-type': 'application/json; charset=utf-8' 'content-type': 'application/json; charset=utf-8',
}, },
body: JSON.stringify({ contact: contactElement?.value, message: msgElement?.textContent }) body: JSON.stringify({ contact: contactElement?.value, message: msgElement?.textContent }),
}) })
hideModal() hideModal()
} }

View File

@ -22,7 +22,9 @@
a { a {
color: rgb(255 255 255 / 64%); color: rgb(255 255 255 / 64%);
transition: color 0.3s, background-color 0.3s; transition:
color 0.3s,
background-color 0.3s;
&:hover { &:hover {
background: #fff; background: #fff;
@ -66,6 +68,7 @@
} }
.footerCopyrightSocial { .footerCopyrightSocial {
align-items: center;
display: flex; display: flex;
.icon { .icon {
@ -94,6 +97,15 @@
margin-left: 0.3em; margin-left: 0.3em;
text-align: right; text-align: right;
} }
a:link {
border: none;
padding-bottom: 0;
}
img {
margin: 0 auto;
}
} }
.socialItemvk { .socialItemvk {

View File

@ -1,10 +1,11 @@
import { clsx } from 'clsx'
import { createMemo, For } from 'solid-js' import { createMemo, For } from 'solid-js'
import styles from './Footer.module.scss'
import { useLocalize } from '../../context/localize'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import { Subscribe } from '../_shared/Subscribe' import { Subscribe } from '../_shared/Subscribe'
import { clsx } from 'clsx' import styles from './Footer.module.scss'
import { useLocalize } from '../../context/localize'
export const Footer = () => { export const Footer = () => {
const { t, lang } = useLocalize() const { t, lang } = useLocalize()
@ -16,26 +17,26 @@ export const Footer = () => {
header: 'About the project', header: 'About the project',
items: [ items: [
{ {
title: 'Manifest', title: 'Discours Manifest',
slug: '/about/manifest' slug: '/about/manifest',
}, },
{ {
title: 'How it works', title: 'How it works',
slug: '/about/guide' slug: '/about/guide',
}, },
{ {
title: 'Dogma', title: 'Dogma',
slug: '/about/dogma' slug: '/about/dogma',
}, },
{ {
title: 'Principles', title: 'Principles',
slug: '/about/principles' slug: '/about/principles',
}, },
{ {
title: 'How to write an article', title: 'How to write an article',
slug: '/how-to-write-a-good-article' slug: '/how-to-write-a-good-article',
} },
] ],
}, },
{ {
@ -43,25 +44,21 @@ export const Footer = () => {
items: [ items: [
{ {
title: 'Suggest an idea', title: 'Suggest an idea',
slug: '/connect' slug: '/connect',
}, },
{ {
title: 'Become an author', title: 'Become an author',
slug: '/create' slug: '/create',
}, },
{ {
title: 'Support us', title: 'Support Discours',
slug: '/about/help' slug: '/about/help',
},
{
title: 'Feedback',
slug: '/#feedback'
}, },
{ {
title: 'Work with us', title: 'Work with us',
slug: 'https://docs.google.com/forms/d/e/1FAIpQLSeNNvIzKlXElJtkPkYiXl-jQjlvsL9u4-kpnoRjz1O8Wo40xQ/viewform' slug: 'https://docs.google.com/forms/d/e/1FAIpQLSeNNvIzKlXElJtkPkYiXl-jQjlvsL9u4-kpnoRjz1O8Wo40xQ/viewform',
} },
] ],
}, },
{ {
@ -69,46 +66,46 @@ export const Footer = () => {
items: [ items: [
{ {
title: 'Authors', title: 'Authors',
slug: '/authors' slug: '/authors',
}, },
{ {
title: 'Communities', title: 'Communities',
slug: '/community' slug: '/community',
}, },
{ {
title: 'Partners', title: 'Partners',
slug: '/about/partners' slug: '/about/partners',
}, },
{ {
title: 'Special projects', title: 'Special projects',
slug: '/about/projects' slug: '/about/projects',
}, },
{ {
title: changeLangTitle(), title: changeLangTitle(),
slug: changeLangLink(), slug: changeLangLink(),
rel: 'external' rel: 'external',
} },
] ],
} },
]) ])
const SOCIAL = [ const SOCIAL = [
{ {
name: 'facebook', name: 'facebook',
href: 'https://facebook.com/discoursio' href: 'https://facebook.com/discoursio',
}, },
{ {
name: 'vk', name: 'vk',
href: 'https://vk.com/discoursio' href: 'https://vk.com/discoursio',
}, },
{ {
name: 'twitter', name: 'twitter',
href: 'https://twitter.com/discours_io' href: 'https://twitter.com/discours_io',
}, },
{ {
name: 'telegram', name: 'telegram',
href: 'https://t.me/discoursio' href: 'https://t.me/discoursio',
} },
] ]
return ( return (
<footer class={styles.discoursFooter}> <footer class={styles.discoursFooter}>
@ -143,7 +140,7 @@ export const Footer = () => {
<div class={clsx(styles.footerCopyright, 'row')}> <div class={clsx(styles.footerCopyright, 'row')}>
<div class="col-md-18 col-lg-20"> <div class="col-md-18 col-lg-20">
{t( {t(
'Independant magazine with an open horizontal cooperation about culture, science and society' 'Independant magazine with an open horizontal cooperation about culture, science and society',
)} )}
. {t('Discours')} &copy; 2015&ndash;{new Date().getFullYear()}{' '} . {t('Discours')} &copy; 2015&ndash;{new Date().getFullYear()}{' '}
<a href="/about/terms-of-use">{t('Terms of use')}</a> <a href="/about/terms-of-use">{t('Terms of use')}</a>

View File

@ -1,5 +1,6 @@
.aboutDiscours { .aboutDiscours {
@include font-size(1.6rem); @include font-size(1.6rem);
background: #fef2f2; background: #fef2f2;
font-weight: 500; font-weight: 500;
margin-bottom: 6.4rem; margin-bottom: 6.4rem;
@ -8,6 +9,7 @@
h4 { h4 {
@include font-size(4rem); @include font-size(4rem);
font-weight: bold; font-weight: bold;
line-height: 1.1; line-height: 1.1;
margin-bottom: 2rem; margin-bottom: 2rem;

View File

@ -1,13 +1,13 @@
import styles from './Hero.module.scss'
import { showModal } from '../../stores/ui'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
import { showModal } from '../../stores/ui'
import { AuthModalSearchParams } from '../Nav/AuthModal/types' import { AuthModalSearchParams } from '../Nav/AuthModal/types'
import styles from './Hero.module.scss'
export default () => { export default () => {
const { t } = useLocalize() const { t } = useLocalize()
const { changeSearchParam } = useRouter<AuthModalSearchParams>() const { changeSearchParams } = useRouter<AuthModalSearchParams>()
return ( return (
<div class={styles.aboutDiscours}> <div class={styles.aboutDiscours}>
@ -17,7 +17,7 @@ export default () => {
<h4 innerHTML={t('Horizontal collaborative journalistic platform')} /> <h4 innerHTML={t('Horizontal collaborative journalistic platform')} />
<p <p
innerHTML={t( innerHTML={t(
'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',
)} )}
/> />
<div class={styles.aboutDiscoursActions}> <div class={styles.aboutDiscoursActions}>
@ -28,8 +28,8 @@ export default () => {
class="button" class="button"
onClick={() => { onClick={() => {
showModal('auth') showModal('auth')
changeSearchParam({ changeSearchParams({
mode: 'register' mode: 'register',
}) })
}} }}
> >

View File

@ -1,12 +1,15 @@
import { clsx } from 'clsx'
import styles from './Draft.module.scss'
import type { Shout } from '../../graphql/types.gen' import type { Shout } from '../../graphql/types.gen'
import { Icon } from '../_shared/Icon'
import { useLocalize } from '../../context/localize'
import { useConfirm } from '../../context/confirm'
import { useSnackbar } from '../../context/snackbar'
import { getPagePath } from '@nanostores/router' import { getPagePath } from '@nanostores/router'
import { clsx } from 'clsx'
import { useConfirm } from '../../context/confirm'
import { useLocalize } from '../../context/localize'
import { useSnackbar } from '../../context/snackbar'
import { router } from '../../stores/router' import { router } from '../../stores/router'
import { Icon } from '../_shared/Icon'
import styles from './Draft.module.scss'
type Props = { type Props = {
class?: string class?: string
@ -18,11 +21,11 @@ type Props = {
export const Draft = (props: Props) => { export const Draft = (props: Props) => {
const { t, formatDate } = useLocalize() const { t, formatDate } = useLocalize()
const { const {
actions: { showConfirm } actions: { showConfirm },
} = useConfirm() } = useConfirm()
const { const {
actions: { showSnackbar } actions: { showSnackbar },
} = useSnackbar() } = useSnackbar()
const handlePublishLinkClick = (e) => { const handlePublishLinkClick = (e) => {
@ -37,7 +40,7 @@ export const Draft = (props: Props) => {
confirmBody: t('Are you sure you want to delete this draft?'), confirmBody: t('Are you sure you want to delete this draft?'),
confirmButtonLabel: t('Delete'), confirmButtonLabel: t('Delete'),
confirmButtonVariant: 'danger', confirmButtonVariant: 'danger',
declineButtonVariant: 'primary' declineButtonVariant: 'primary',
}) })
if (isConfirmed) { if (isConfirmed) {
props.onDelete(props.shout) props.onDelete(props.shout)

View File

@ -5,6 +5,7 @@
.draggable { .draggable {
margin: 8px 0; margin: 8px 0;
padding: 8px 0; padding: 8px 0;
&:hover { &:hover {
background: var(--placeholder-color-semi); background: var(--placeholder-color-semi);
} }

View File

@ -1,12 +1,15 @@
import { Buffer } from 'buffer'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import styles from './AudioUploader.module.scss'
import { DropArea } from '../../_shared/DropArea'
import { useLocalize } from '../../../context/localize'
import { Show } from 'solid-js' import { Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { MediaItem } from '../../../pages/types' import { MediaItem } from '../../../pages/types'
import { composeMediaItems } from '../../../utils/composeMediaItems' import { composeMediaItems } from '../../../utils/composeMediaItems'
import { DropArea } from '../../_shared/DropArea'
import { AudioPlayer } from '../../Article/AudioPlayer' import { AudioPlayer } from '../../Article/AudioPlayer'
import { Buffer } from 'buffer'
import styles from './AudioUploader.module.scss'
window.Buffer = Buffer window.Buffer = Buffer

View File

@ -13,7 +13,7 @@
z-index: 2; z-index: 2;
font-weight: 500; font-weight: 500;
transition: 0.6s ease-in-out; transition: 0.6s ease-in-out;
background: rgba(white, 0.3); background: rgb(255 255 255 / 30%);
backdrop-filter: blur(4px); backdrop-filter: blur(4px);
border: 1px solid var(--secondary-color); border: 1px solid var(--secondary-color);
left: 100%; left: 100%;

View File

@ -1,7 +1,9 @@
import { clsx } from 'clsx' import { clsx } from 'clsx'
import styles from './AutoSaveNotice.module.scss'
import { Loading } from '../../_shared/Loading'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { Loading } from '../../_shared/Loading'
import styles from './AutoSaveNotice.module.scss'
type Props = { type Props = {
active: boolean active: boolean

View File

@ -1,9 +1,11 @@
import type { Editor } from '@tiptap/core' import type { Editor } from '@tiptap/core'
import styles from './BubbleMenu.module.scss'
import { Icon } from '../../_shared/Icon'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { Icon } from '../../_shared/Icon'
import { Popover } from '../../_shared/Popover' import { Popover } from '../../_shared/Popover'
import styles from './BubbleMenu.module.scss'
type Props = { type Props = {
editor: Editor editor: Editor
ref: (el: HTMLElement) => void ref: (el: HTMLElement) => void

View File

@ -1,12 +1,14 @@
import type { Editor } from '@tiptap/core' import type { Editor } from '@tiptap/core'
import styles from './BubbleMenu.module.scss'
import { Icon } from '../../_shared/Icon'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { Popover } from '../../_shared/Popover'
import { UploadModalContent } from '../UploadModalContent'
import { Modal } from '../../Nav/Modal'
import { UploadedFile } from '../../../pages/types' import { UploadedFile } from '../../../pages/types'
import { renderUploadedImage } from '../../../utils/renderUploadedImage' import { renderUploadedImage } from '../../../utils/renderUploadedImage'
import { Icon } from '../../_shared/Icon'
import { Popover } from '../../_shared/Popover'
import { Modal } from '../../Nav/Modal'
import { UploadModalContent } from '../UploadModalContent'
import styles from './BubbleMenu.module.scss'
type Props = { type Props = {
editor: Editor editor: Editor

View File

@ -1,9 +1,12 @@
import { createSignal, Show, For } from 'solid-js'
import type { Editor } from '@tiptap/core' import type { Editor } from '@tiptap/core'
import styles from './BubbleMenu.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { Icon } from '../../_shared/Icon' import { createSignal, Show, For } from 'solid-js'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { Icon } from '../../_shared/Icon'
import styles from './BubbleMenu.module.scss'
type Props = { type Props = {
editor: Editor editor: Editor

View File

@ -1,52 +1,56 @@
import { createEffect, createSignal, onCleanup } from 'solid-js'
import { createTiptapEditor, useEditorHTML } from 'solid-tiptap'
import uniqolor from 'uniqolor'
import * as Y from 'yjs'
import type { Doc } from 'yjs/dist/src/utils/Doc' import type { Doc } from 'yjs/dist/src/utils/Doc'
import { HocuspocusProvider } from '@hocuspocus/provider'
import { isTextSelection } from '@tiptap/core'
import { Bold } from '@tiptap/extension-bold' import { Bold } from '@tiptap/extension-bold'
import { BubbleMenu } from '@tiptap/extension-bubble-menu' import { BubbleMenu } from '@tiptap/extension-bubble-menu'
import { Dropcursor } from '@tiptap/extension-dropcursor'
import { Italic } from '@tiptap/extension-italic'
import { Strike } from '@tiptap/extension-strike'
import { HorizontalRule } from '@tiptap/extension-horizontal-rule'
import { Underline } from '@tiptap/extension-underline'
import { FloatingMenu } from '@tiptap/extension-floating-menu'
import { BulletList } from '@tiptap/extension-bullet-list' import { BulletList } from '@tiptap/extension-bullet-list'
import { OrderedList } from '@tiptap/extension-ordered-list'
import { ListItem } from '@tiptap/extension-list-item'
import { CharacterCount } from '@tiptap/extension-character-count' import { CharacterCount } from '@tiptap/extension-character-count'
import { Placeholder } from '@tiptap/extension-placeholder' import { Collaboration } from '@tiptap/extension-collaboration'
import { CollaborationCursor } from '@tiptap/extension-collaboration-cursor'
import { Document } from '@tiptap/extension-document'
import { Dropcursor } from '@tiptap/extension-dropcursor'
import { FloatingMenu } from '@tiptap/extension-floating-menu'
import Focus from '@tiptap/extension-focus'
import { Gapcursor } from '@tiptap/extension-gapcursor' import { Gapcursor } from '@tiptap/extension-gapcursor'
import { HardBreak } from '@tiptap/extension-hard-break' import { HardBreak } from '@tiptap/extension-hard-break'
import { Heading } from '@tiptap/extension-heading' import { Heading } from '@tiptap/extension-heading'
import { Highlight } from '@tiptap/extension-highlight' import { Highlight } from '@tiptap/extension-highlight'
import { Link } from '@tiptap/extension-link' import { HorizontalRule } from '@tiptap/extension-horizontal-rule'
import { Document } from '@tiptap/extension-document'
import { Text } from '@tiptap/extension-text'
import { CollaborationCursor } from '@tiptap/extension-collaboration-cursor'
import { isTextSelection } from '@tiptap/core'
import { Paragraph } from '@tiptap/extension-paragraph'
import Focus from '@tiptap/extension-focus'
import { Collaboration } from '@tiptap/extension-collaboration'
import { HocuspocusProvider } from '@hocuspocus/provider'
import { CustomBlockquote } from './extensions/CustomBlockquote'
import { Figure } from './extensions/Figure'
import { Figcaption } from './extensions/Figcaption'
import { Embed } from './extensions/Embed'
import { useSession } from '../../context/session'
import { useLocalize } from '../../context/localize'
import { useEditorContext } from '../../context/editor'
import { TrailingNode } from './extensions/TrailingNode'
import Article from './extensions/Article'
import { TextBubbleMenu } from './TextBubbleMenu'
import { FigureBubbleMenu, BlockquoteBubbleMenu, IncutBubbleMenu } from './BubbleMenu'
import { EditorFloatingMenu } from './EditorFloatingMenu'
import './Prosemirror.scss'
import { Image } from '@tiptap/extension-image' import { Image } from '@tiptap/extension-image'
import { Footnote } from './extensions/Footnote' import { Italic } from '@tiptap/extension-italic'
import { Link } from '@tiptap/extension-link'
import { ListItem } from '@tiptap/extension-list-item'
import { OrderedList } from '@tiptap/extension-ordered-list'
import { Paragraph } from '@tiptap/extension-paragraph'
import { Placeholder } from '@tiptap/extension-placeholder'
import { Strike } from '@tiptap/extension-strike'
import { Text } from '@tiptap/extension-text'
import { Underline } from '@tiptap/extension-underline'
import { createEffect, createSignal, onCleanup } from 'solid-js'
import { createTiptapEditor, useEditorHTML } from 'solid-tiptap'
import uniqolor from 'uniqolor'
import * as Y from 'yjs'
import { useEditorContext } from '../../context/editor'
import { useLocalize } from '../../context/localize'
import { useSession } from '../../context/session'
import { useSnackbar } from '../../context/snackbar' import { useSnackbar } from '../../context/snackbar'
import { handleImageUpload } from '../../utils/handleImageUpload' import { handleImageUpload } from '../../utils/handleImageUpload'
import { FigureBubbleMenu, BlockquoteBubbleMenu, IncutBubbleMenu } from './BubbleMenu'
import { EditorFloatingMenu } from './EditorFloatingMenu'
import Article from './extensions/Article'
import { CustomBlockquote } from './extensions/CustomBlockquote'
import { Figcaption } from './extensions/Figcaption'
import { Figure } from './extensions/Figure'
import { Footnote } from './extensions/Footnote'
import { Iframe } from './extensions/Iframe'
import { TrailingNode } from './extensions/TrailingNode'
import { TextBubbleMenu } from './TextBubbleMenu'
import './Prosemirror.scss'
type Props = { type Props = {
shoutId: number shoutId: number
initialContent?: string initialContent?: string
@ -61,7 +65,7 @@ const allowedImageTypes = new Set([
'image/png', 'image/png',
'image/tiff', 'image/tiff',
'image/webp', 'image/webp',
'image/x-icon' 'image/x-icon',
]) ])
const yDocs: Record<string, Doc> = {} const yDocs: Record<string, Doc> = {}
@ -75,7 +79,7 @@ export const Editor = (props: Props) => {
const [shouldShowTextBubbleMenu, setShouldShowTextBubbleMenu] = createSignal(false) const [shouldShowTextBubbleMenu, setShouldShowTextBubbleMenu] = createSignal(false)
const { const {
actions: { showSnackbar } actions: { showSnackbar },
} = useSnackbar() } = useSnackbar()
const docName = `shout-${props.shoutId}` const docName = `shout-${props.shoutId}`
@ -88,49 +92,44 @@ export const Editor = (props: Props) => {
providers[docName] = new HocuspocusProvider({ providers[docName] = new HocuspocusProvider({
url: 'wss://hocuspocus.discours.io', url: 'wss://hocuspocus.discours.io',
name: docName, name: docName,
document: yDocs[docName] document: yDocs[docName],
}) })
} }
const editorElRef: { const editorElRef: {
current: HTMLDivElement current: HTMLDivElement
} = { } = {
current: null current: null,
} }
const textBubbleMenuRef: { const textBubbleMenuRef: {
current: HTMLDivElement current: HTMLDivElement
} = { } = {
current: null current: null,
} }
const incutBubbleMenuRef: { const incutBubbleMenuRef: {
current: HTMLElement current: HTMLElement
} = { } = {
current: null current: null,
} }
const figureBubbleMenuRef: { const figureBubbleMenuRef: {
current: HTMLElement current: HTMLElement
} = { } = {
current: null current: null,
} }
const blockquoteBubbleMenuRef: { const blockquoteBubbleMenuRef: {
current: HTMLElement current: HTMLElement
} = { } = {
current: null current: null,
} }
const floatingMenuRef: { const floatingMenuRef: {
current: HTMLDivElement current: HTMLDivElement
} = { } = {
current: null current: null,
} }
const ImageFigure = Figure.extend({
name: 'capturedImage',
content: 'figcaption image'
})
const handleClipboardPaste = async () => { const handleClipboardPaste = async () => {
try { try {
const clipboardItems = await navigator.clipboard.read() const clipboardItems = await navigator.clipboard.read()
@ -149,7 +148,7 @@ export const Editor = (props: Props) => {
source: blob.toString(), source: blob.toString(),
name: file.name, name: file.name,
size: file.size, size: file.size,
file file,
} }
showSnackbar({ body: t('Uploading image') }) showSnackbar({ body: t('Uploading image') })
@ -159,24 +158,18 @@ export const Editor = (props: Props) => {
.chain() .chain()
.focus() .focus()
.insertContent({ .insertContent({
type: 'capturedImage', type: 'figure',
attrs: { 'data-type': 'image' },
content: [ content: [
{ {
type: 'figcaption', type: 'image',
content: [ attrs: { src: result.url },
{
type: 'text',
text: result.originalFilename
}
]
}, },
{ {
type: 'image', type: 'figcaption',
attrs: { content: [{ type: 'text', text: result.originalFilename }],
src: result.url },
} ],
}
]
}) })
.run() .run()
} catch (error) { } catch (error) {
@ -190,7 +183,7 @@ export const Editor = (props: Props) => {
element: editorElRef.current, element: editorElRef.current,
editorProps: { editorProps: {
attributes: { attributes: {
class: 'articleEditor' class: 'articleEditor',
}, },
transformPastedHTML(html) { transformPastedHTML(html) {
return html.replaceAll(/<img.*?>/g, '') return html.replaceAll(/<img.*?>/g, '')
@ -198,7 +191,7 @@ export const Editor = (props: Props) => {
handlePaste: () => { handlePaste: () => {
handleClipboardPaste() handleClipboardPaste()
return false return false
} },
}, },
extensions: [ extensions: [
Document, Document,
@ -211,31 +204,31 @@ export const Editor = (props: Props) => {
Strike, Strike,
HorizontalRule.configure({ HorizontalRule.configure({
HTMLAttributes: { HTMLAttributes: {
class: 'horizontalRule' class: 'horizontalRule',
} },
}), }),
Underline, Underline,
Link.configure({ Link.configure({
openOnClick: false openOnClick: false,
}), }),
Heading.configure({ Heading.configure({
levels: [2, 3, 4] levels: [2, 3, 4],
}), }),
BulletList, BulletList,
OrderedList, OrderedList,
ListItem, ListItem,
Collaboration.configure({ Collaboration.configure({
document: yDocs[docName] document: yDocs[docName],
}), }),
CollaborationCursor.configure({ CollaborationCursor.configure({
provider: providers[docName], provider: providers[docName],
user: { user: {
name: user().name, name: user().name,
color: uniqolor(user().slug).color color: uniqolor(user().slug).color,
} },
}), }),
Placeholder.configure({ Placeholder.configure({
placeholder: t('Add a link or click plus to embed media') placeholder: t('Add a link or click plus to embed media'),
}), }),
Focus, Focus,
Gapcursor, Gapcursor,
@ -243,14 +236,14 @@ export const Editor = (props: Props) => {
Highlight.configure({ Highlight.configure({
multicolor: true, multicolor: true,
HTMLAttributes: { HTMLAttributes: {
class: 'highlight' class: 'highlight',
} },
}), }),
ImageFigure,
Image, Image,
Iframe,
Figure,
Figcaption, Figcaption,
Footnote, Footnote,
Embed,
CharacterCount.configure(), // https://github.com/ueberdosis/tiptap/issues/2589#issuecomment-1093084689 CharacterCount.configure(), // https://github.com/ueberdosis/tiptap/issues/2589#issuecomment-1093084689
BubbleMenu.configure({ BubbleMenu.configure({
pluginKey: 'textBubbleMenu', pluginKey: 'textBubbleMenu',
@ -261,14 +254,19 @@ export const Editor = (props: Props) => {
const isEmptyTextBlock = doc.textBetween(from, to).length === 0 && isTextSelection(selection) const isEmptyTextBlock = doc.textBetween(from, to).length === 0 && isTextSelection(selection)
setIsCommonMarkup(e.isActive('figcaption')) setIsCommonMarkup(e.isActive('figcaption'))
const result = const result =
(view.hasFocus() && !empty && !isEmptyTextBlock && !e.isActive('image')) || (view.hasFocus() &&
e.isActive('footnote') !empty &&
!isEmptyTextBlock &&
!e.isActive('image') &&
!e.isActive('figure')) ||
e.isActive('footnote') ||
(e.isActive('figcaption') && !empty)
setShouldShowTextBubbleMenu(result) setShouldShowTextBubbleMenu(result)
return result return result
}, },
tippyOptions: { tippyOptions: {
sticky: true sticky: true,
} },
}), }),
BubbleMenu.configure({ BubbleMenu.configure({
pluginKey: 'blockquoteBubbleMenu', pluginKey: 'blockquoteBubbleMenu',
@ -286,8 +284,8 @@ export const Editor = (props: Props) => {
if (selectedElement) { if (selectedElement) {
return selectedElement.getBoundingClientRect() return selectedElement.getBoundingClientRect()
} }
} },
} },
}), }),
BubbleMenu.configure({ BubbleMenu.configure({
pluginKey: 'incutBubbleMenu', pluginKey: 'incutBubbleMenu',
@ -305,31 +303,31 @@ export const Editor = (props: Props) => {
if (selectedElement) { if (selectedElement) {
return selectedElement.getBoundingClientRect() return selectedElement.getBoundingClientRect()
} }
} },
} },
}), }),
BubbleMenu.configure({ BubbleMenu.configure({
pluginKey: 'imageBubbleMenu', pluginKey: 'imageBubbleMenu',
element: figureBubbleMenuRef.current, element: figureBubbleMenuRef.current,
shouldShow: ({ editor: e, view }) => { shouldShow: ({ editor: e, view }) => {
return view.hasFocus() && e.isActive('image') return view.hasFocus() && e.isActive('image')
} },
}), }),
FloatingMenu.configure({ FloatingMenu.configure({
tippyOptions: { tippyOptions: {
placement: 'left' placement: 'left',
}, },
element: floatingMenuRef.current element: floatingMenuRef.current,
}), }),
TrailingNode, TrailingNode,
Article Article,
], ],
enablePasteRules: [Link], enablePasteRules: [Link],
content: initialContent ?? null content: initialContent ?? null,
})) }))
const { const {
actions: { countWords, setEditor } actions: { countWords, setEditor },
} = useEditorContext() } = useEditorContext()
setEditor(editor) setEditor(editor)
@ -341,7 +339,7 @@ export const Editor = (props: Props) => {
if (html()) { if (html()) {
countWords({ countWords({
characters: editor().storage.characterCount.characters(), characters: editor().storage.characterCount.characters(),
words: editor().storage.characterCount.words() words: editor().storage.characterCount.words(),
}) })
} }
}) })

View File

@ -1,18 +1,21 @@
import { createEffect, createSignal, Show } from 'solid-js'
import type { Editor, JSONContent } from '@tiptap/core'
import { Icon } from '../../_shared/Icon'
import { InlineForm } from '../InlineForm'
import styles from './EditorFloatingMenu.module.scss'
import HTMLParser from 'html-to-json-parser'
import { useLocalize } from '../../../context/localize'
import { Modal } from '../../Nav/Modal'
import { Menu } from './Menu'
import type { MenuItem } from './Menu/Menu' import type { MenuItem } from './Menu/Menu'
import { showModal } from '../../../stores/ui' import type { Editor } from '@tiptap/core'
import { UploadModalContent } from '../UploadModalContent'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler' import { createEffect, createSignal, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { UploadedFile } from '../../../pages/types' import { UploadedFile } from '../../../pages/types'
import { showModal } from '../../../stores/ui'
import { renderUploadedImage } from '../../../utils/renderUploadedImage' import { renderUploadedImage } from '../../../utils/renderUploadedImage'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import { Icon } from '../../_shared/Icon'
import { Modal } from '../../Nav/Modal'
import { InlineForm } from '../InlineForm'
import { UploadModalContent } from '../UploadModalContent'
import { Menu } from './Menu'
import styles from './EditorFloatingMenu.module.scss'
type FloatingMenuProps = { type FloatingMenuProps = {
editor: Editor editor: Editor
@ -20,10 +23,17 @@ type FloatingMenuProps = {
} }
const embedData = async (data) => { const embedData = async (data) => {
const result = (await HTMLParser(data, false)) as JSONContent const element = document.createRange().createContextualFragment(data)
if ('type' in result && result.type === 'iframe') { const { attributes } = element.firstChild as HTMLIFrameElement
return result.attributes
const result: { src: string; width?: string; height?: string } = { src: '' }
for (let i = 0; i < attributes.length; i++) {
const attribute = attributes[i]
result[attribute.name] = attribute.value
} }
return result
} }
export const EditorFloatingMenu = (props: FloatingMenuProps) => { export const EditorFloatingMenu = (props: FloatingMenuProps) => {
@ -35,12 +45,33 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => {
const handleEmbedFormSubmit = async (value: string) => { const handleEmbedFormSubmit = async (value: string) => {
// TODO: add support instagram embed (blockquote) // TODO: add support instagram embed (blockquote)
const emb = await embedData(value) const emb = await embedData(value)
props.editor.chain().focus().setIframe(emb).run() props.editor
.chain()
.focus()
.insertContent({
type: 'figure',
attrs: { 'data-type': 'iframe' },
content: [
{
type: 'iframe',
attrs: {
src: emb.src,
width: emb.width,
height: emb.height,
},
},
{
type: 'figcaption',
content: [{ type: 'text', text: t('Description') }],
},
],
})
.run()
} }
const validateEmbed = async (value) => { const validateEmbed = async (value) => {
const iframeData = (await HTMLParser(value, false)) as JSONContent const element = document.createRange().createContextualFragment(value)
if (iframeData.type !== 'iframe') { if (element.firstChild?.nodeName !== 'IFRAME') {
return t('Error') return t('Error')
} }
} }
@ -62,6 +93,7 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => {
const closeUploadModalHandler = () => { const closeUploadModalHandler = () => {
setSelectedMenuItem() setSelectedMenuItem()
setMenuOpen(false) setMenuOpen(false)
setSelectedMenuItem()
} }
useOutsideClickHandler({ useOutsideClickHandler({
@ -73,8 +105,9 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => {
if (menuOpen()) { if (menuOpen()) {
setMenuOpen(false) setMenuOpen(false)
setSelectedMenuItem()
} }
} },
}) })
const handleUpload = (image: UploadedFile) => { const handleUpload = (image: UploadedFile) => {

View File

@ -1,7 +1,8 @@
import styles from './Menu.module.scss' import { useLocalize } from '../../../../context/localize'
import { Icon } from '../../../_shared/Icon' import { Icon } from '../../../_shared/Icon'
import { Popover } from '../../../_shared/Popover' import { Popover } from '../../../_shared/Popover'
import { useLocalize } from '../../../../context/localize'
import styles from './Menu.module.scss'
export type MenuItem = 'image' | 'embed' | 'horizontal-rule' export type MenuItem = 'image' | 'embed' | 'horizontal-rule'

View File

@ -1,11 +1,12 @@
.InlineForm { .InlineForm {
border: 1px solid;
position: relative; position: relative;
width: 100%; width: 100%;
.form { .form {
display: flex; display: flex;
flex-flow: row nowrap; flex-flow: row nowrap;
padding: 6px 11px; padding: 8px 11px;
width: 100%; width: 100%;
input { input {
@ -24,6 +25,10 @@
outline: none; outline: none;
} }
} }
button {
margin-left: 8px;
}
} }
.linkError { .linkError {
@ -39,7 +44,9 @@
border: 1px solid #e9e9ee; border: 1px solid #e9e9ee;
border-radius: 2px; border-radius: 2px;
opacity: 0; opacity: 0;
transition: height 0.3s ease-in-out, opacity 0.3s ease-in-out; transition:
height 0.3s ease-in-out,
opacity 0.3s ease-in-out;
&.visible { &.visible {
height: 32px; height: 32px;

View File

@ -1,9 +1,11 @@
import styles from './InlineForm.module.scss'
import { Icon } from '../../_shared/Icon'
import { createSignal, onMount } from 'solid-js'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { Popover } from '../../_shared/Popover' import { createSignal, onMount } from 'solid-js'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { Icon } from '../../_shared/Icon'
import { Popover } from '../../_shared/Popover'
import styles from './InlineForm.module.scss'
type Props = { type Props = {
onClose: () => void onClose: () => void

View File

@ -1,8 +1,9 @@
import { Editor } from '@tiptap/core' import { Editor } from '@tiptap/core'
import { createEditorTransaction } from 'solid-tiptap'
import { useLocalize } from '../../../context/localize'
import { validateUrl } from '../../../utils/validateUrl' import { validateUrl } from '../../../utils/validateUrl'
import { InlineForm } from '../InlineForm' import { InlineForm } from '../InlineForm'
import { useLocalize } from '../../../context/localize'
import { createEditorTransaction } from 'solid-tiptap'
type Props = { type Props = {
editor: Editor editor: Editor
@ -24,7 +25,7 @@ export const InsertLinkForm = (props: Props) => {
() => props.editor, () => props.editor,
(ed) => { (ed) => {
return (ed && ed.getAttributes('link').href) || '' return (ed && ed.getAttributes('link').href) || ''
} },
) )
const handleClearLinkForm = () => { const handleClearLinkForm = () => {
if (currentUrl()) { if (currentUrl()) {

View File

@ -0,0 +1,4 @@
.LinkBubbleMenu {
background: var(--editor-bubble-menu-background);
box-shadow: 0 4px 10px rgba(#000, 0.25);
}

View File

@ -0,0 +1,19 @@
import type { Editor } from '@tiptap/core'
import { InsertLinkForm } from '../InsertLinkForm'
import styles from './LinkBubbleMenu.module.scss'
type Props = {
editor: Editor
ref: (el: HTMLDivElement) => void
onClose: () => void
}
export const LinkBubbleMenuModule = (props: Props) => {
return (
<div ref={props.ref} class={styles.LinkBubbleMenu}>
<InsertLinkForm editor={props.editor} onClose={props.onClose} />
</div>
)
}

View File

@ -0,0 +1 @@
export { LinkBubbleMenuModule } from './LinkBubbleMenu.module'

View File

@ -1,17 +1,20 @@
import { clsx } from 'clsx'
import { Button } from '../../_shared/Button'
import { Icon } from '../../_shared/Icon'
import { useLocalize } from '../../../context/localize'
import styles from './Panel.module.scss'
import { useEditorContext } from '../../../context/editor'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler'
import { getPagePath } from '@nanostores/router' import { getPagePath } from '@nanostores/router'
import { router } from '../../../stores/router' import { clsx } from 'clsx'
import { createSignal, Show } from 'solid-js'
import { useEditorHTML } from 'solid-tiptap' import { useEditorHTML } from 'solid-tiptap'
import Typograf from 'typograf' import Typograf from 'typograf'
import { createSignal, Show } from 'solid-js'
import { useEditorContext } from '../../../context/editor'
import { useLocalize } from '../../../context/localize'
import { router } from '../../../stores/router'
import { showModal } from '../../../stores/ui'
import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler'
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
import { Button } from '../../_shared/Button'
import { DarkModeToggle } from '../../_shared/DarkModeToggle' import { DarkModeToggle } from '../../_shared/DarkModeToggle'
import { Icon } from '../../_shared/Icon'
import styles from './Panel.module.scss'
const typograf = new Typograf({ locale: ['ru', 'en-US'] }) const typograf = new Typograf({ locale: ['ru', 'en-US'] })
@ -26,11 +29,11 @@ export const Panel = (props: Props) => {
wordCounter, wordCounter,
editorRef, editorRef,
form, form,
actions: { toggleEditorPanel, saveShout, publishShout } actions: { toggleEditorPanel, saveShout, publishShout },
} = useEditorContext() } = useEditorContext()
const containerRef: { current: HTMLElement } = { const containerRef: { current: HTMLElement } = {
current: null current: null,
} }
const [isShortcutsVisible, setIsShortcutsVisible] = createSignal(false) const [isShortcutsVisible, setIsShortcutsVisible] = createSignal(false)
@ -39,7 +42,7 @@ export const Panel = (props: Props) => {
useOutsideClickHandler({ useOutsideClickHandler({
containerRef, containerRef,
predicate: () => isEditorPanelVisible(), predicate: () => isEditorPanelVisible(),
handler: () => toggleEditorPanel() handler: () => toggleEditorPanel(),
}) })
useEscKeyDownHandler(() => { useEscKeyDownHandler(() => {
@ -89,7 +92,9 @@ export const Panel = (props: Props) => {
<section> <section>
<p> <p>
<a class={styles.link}>{t('Invite co-authors')}</a> <span class={styles.link} onClick={() => showModal('inviteCoAuthors')}>
{t('Invite co-authors')}
</span>
</p> </p>
<p> <p>
<a <a

View File

@ -265,10 +265,29 @@ mark.highlight {
} }
} }
figure[data-type='capturedImage'] { .ProseMirror-hideselection figure[data-type='figure'] {
flex-direction: column-reverse; & > figcaption {
--selection-color: rgb(0 0 0 / 60%);
}
} }
figure[data-type='figure'] {
width: 100% !important;
.iframe-wrapper {
position: relative;
overflow: hidden;
width: 100%;
height: auto;
iframe {
display: block;
width: 100%;
}
}
}
/* stylelint-disable-next-line selector-type-no-unknown */
footnote { footnote {
display: inline-flex; display: inline-flex;
position: relative; position: relative;
@ -276,7 +295,7 @@ footnote {
width: 0.8rem; width: 0.8rem;
height: 1em; height: 1em;
&:before { &::before {
content: ''; content: '';
position: absolute; position: absolute;
width: 10px; width: 10px;

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