Merge branch 'create-shout-2' into 'dev'
Create shout new flow WIP See merge request discoursio/discoursio-webapp!59
This commit is contained in:
commit
821fb428de
|
@ -68,7 +68,7 @@ module.exports = {
|
||||||
'unicorn/prefer-dom-node-append': 'off', // FIXME
|
'unicorn/prefer-dom-node-append': 'off', // FIXME
|
||||||
'unicorn/prefer-top-level-await': 'warn',
|
'unicorn/prefer-top-level-await': 'warn',
|
||||||
'unicorn/consistent-function-scoping': 'warn',
|
'unicorn/consistent-function-scoping': 'warn',
|
||||||
'sonarjs/no-duplicate-string': 'warn',
|
'sonarjs/no-duplicate-string': ['warn', 5],
|
||||||
|
|
||||||
// Promise
|
// Promise
|
||||||
// 'promise/catch-or-return': 'off', // Should be enabled
|
// 'promise/catch-or-return': 'off', // Should be enabled
|
||||||
|
|
14
.stylelintrc
14
.stylelintrc
|
@ -19,7 +19,19 @@
|
||||||
],
|
],
|
||||||
"scss/dollar-variable-pattern": ["^[a-z][a-zA-Z]+$", {
|
"scss/dollar-variable-pattern": ["^[a-z][a-zA-Z]+$", {
|
||||||
"ignore": "global"
|
"ignore": "global"
|
||||||
}]
|
}],
|
||||||
|
"selector-pseudo-class-no-unknown": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"ignorePseudoClasses": ["global", "export"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"property-no-vendor-prefix": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"ignoreProperties": ["box-decoration-break"]
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"defaultSeverity": "warning"
|
"defaultSeverity": "warning"
|
||||||
}
|
}
|
||||||
|
|
31
.stylelintrc.bak
Normal file
31
.stylelintrc.bak
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"stylelint-config-standard-scss"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"stylelint-order",
|
||||||
|
"stylelint-scss"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"selector-class-pattern": null,
|
||||||
|
"no-descending-specificity": null,
|
||||||
|
"scss/function-no-unknown": null,
|
||||||
|
"scss/no-global-function-names": null,
|
||||||
|
"function-url-quotes": null,
|
||||||
|
"font-family-no-missing-generic-family-keyword": null,
|
||||||
|
"order/order": [
|
||||||
|
"custom-properties",
|
||||||
|
"declarations"
|
||||||
|
],
|
||||||
|
"scss/dollar-variable-pattern": ["^[a-z][a-zA-Z]+$", {
|
||||||
|
"ignore": "global"
|
||||||
|
}],
|
||||||
|
"selector-pseudo-class-no-unknown": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"ignorePseudoClasses": ["global", "export"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"defaultSeverity": "warning"
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
overwrite: true
|
overwrite: true
|
||||||
#schema: 'http://localhost:8080'
|
schema: 'http://127.0.0.1:8080'
|
||||||
schema: 'https://v2.discours.io'
|
#schema: 'https://v2.discours.io'
|
||||||
generates:
|
generates:
|
||||||
src/graphql/introspec.gen.ts:
|
src/graphql/introspec.gen.ts:
|
||||||
plugins:
|
plugins:
|
||||||
|
|
7254
package-lock.json
generated
7254
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
132
package.json
132
package.json
|
@ -32,69 +32,68 @@
|
||||||
"@aws-sdk/abort-controller": "3.303.0",
|
"@aws-sdk/abort-controller": "3.303.0",
|
||||||
"@aws-sdk/client-s3": "3.303.0",
|
"@aws-sdk/client-s3": "3.303.0",
|
||||||
"@aws-sdk/lib-storage": "3.303.0",
|
"@aws-sdk/lib-storage": "3.303.0",
|
||||||
"@hocuspocus/provider": "2.0.1",
|
"@hocuspocus/provider": "2.0.6",
|
||||||
"formidable": "2.1.1",
|
"formidable": "2.1.1",
|
||||||
"html-to-json-parser": "1.1.0",
|
"i18next": "22.4.15",
|
||||||
"i18next": "22.4.13",
|
|
||||||
"mailgun.js": "8.2.1",
|
"mailgun.js": "8.2.1",
|
||||||
"node-fetch": "3.3.1"
|
"node-fetch": "3.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.21.3",
|
"@babel/core": "7.21.8",
|
||||||
"@graphql-codegen/cli": "3.2.2",
|
"@graphql-codegen/cli": "3.2.2",
|
||||||
"@graphql-codegen/typescript": "3.0.2",
|
"@graphql-codegen/typescript": "3.0.4",
|
||||||
"@graphql-codegen/typescript-operations": "3.0.2",
|
"@graphql-codegen/typescript-operations": "3.0.4",
|
||||||
"@graphql-codegen/typescript-urql": "3.7.3",
|
"@graphql-codegen/typescript-urql": "3.7.3",
|
||||||
"@graphql-codegen/urql-introspection": "2.2.1",
|
"@graphql-codegen/urql-introspection": "2.2.1",
|
||||||
"@graphql-tools/url-loader": "7.17.14",
|
"@graphql-tools/url-loader": "7.17.18",
|
||||||
"@graphql-typed-document-node/core": "3.2.0",
|
"@graphql-typed-document-node/core": "3.2.0",
|
||||||
"@nanostores/router": "0.8.3",
|
"@nanostores/router": "0.8.3",
|
||||||
"@nanostores/solid": "0.3.2",
|
"@nanostores/solid": "0.3.2",
|
||||||
"@popperjs/core": "2.11.7",
|
"@popperjs/core": "2.11.7",
|
||||||
"@solid-primitives/memo": "1.2.3",
|
"@solid-primitives/memo": "1.2.4",
|
||||||
"@solid-primitives/share": "2.0.4",
|
"@solid-primitives/share": "2.0.4",
|
||||||
"@solid-primitives/storage": "1.3.8",
|
"@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.28.2",
|
||||||
"@thisbeyond/solid-select": "0.13.0",
|
"@thisbeyond/solid-select": "0.13.0",
|
||||||
"@tiptap/core": "2.0.1",
|
"@tiptap/core": "2.0.3",
|
||||||
"@tiptap/extension-blockquote": "2.0.1",
|
"@tiptap/extension-blockquote": "2.0.3",
|
||||||
"@tiptap/extension-bold": "2.0.1",
|
"@tiptap/extension-bold": "2.0.3",
|
||||||
"@tiptap/extension-bubble-menu": "2.0.1",
|
"@tiptap/extension-bubble-menu": "2.0.3",
|
||||||
"@tiptap/extension-bullet-list": "2.0.1",
|
"@tiptap/extension-bullet-list": "2.0.3",
|
||||||
"@tiptap/extension-character-count": "2.0.1",
|
"@tiptap/extension-character-count": "2.0.3",
|
||||||
"@tiptap/extension-collaboration": "2.0.1",
|
"@tiptap/extension-collaboration": "2.0.3",
|
||||||
"@tiptap/extension-collaboration-cursor": "2.0.1",
|
"@tiptap/extension-collaboration-cursor": "2.0.3",
|
||||||
"@tiptap/extension-document": "2.0.1",
|
"@tiptap/extension-document": "2.0.3",
|
||||||
"@tiptap/extension-dropcursor": "2.0.1",
|
"@tiptap/extension-dropcursor": "2.0.3",
|
||||||
"@tiptap/extension-floating-menu": "2.0.1",
|
"@tiptap/extension-floating-menu": "2.0.3",
|
||||||
"@tiptap/extension-focus": "2.0.1",
|
"@tiptap/extension-focus": "2.0.3",
|
||||||
"@tiptap/extension-gapcursor": "2.0.1",
|
"@tiptap/extension-gapcursor": "2.0.3",
|
||||||
"@tiptap/extension-hard-break": "2.0.1",
|
"@tiptap/extension-hard-break": "2.0.3",
|
||||||
"@tiptap/extension-heading": "2.0.1",
|
"@tiptap/extension-heading": "2.0.3",
|
||||||
"@tiptap/extension-highlight": "2.0.1",
|
"@tiptap/extension-highlight": "2.0.3",
|
||||||
"@tiptap/extension-history": "2.0.1",
|
"@tiptap/extension-history": "2.0.3",
|
||||||
"@tiptap/extension-horizontal-rule": "2.0.1",
|
"@tiptap/extension-horizontal-rule": "2.0.3",
|
||||||
"@tiptap/extension-image": "2.0.1",
|
"@tiptap/extension-image": "2.0.3",
|
||||||
"@tiptap/extension-italic": "2.0.1",
|
"@tiptap/extension-italic": "2.0.3",
|
||||||
"@tiptap/extension-link": "2.0.1",
|
"@tiptap/extension-link": "2.0.3",
|
||||||
"@tiptap/extension-list-item": "2.0.1",
|
"@tiptap/extension-list-item": "2.0.3",
|
||||||
"@tiptap/extension-ordered-list": "2.0.1",
|
"@tiptap/extension-ordered-list": "2.0.3",
|
||||||
"@tiptap/extension-paragraph": "2.0.1",
|
"@tiptap/extension-paragraph": "2.0.3",
|
||||||
"@tiptap/extension-placeholder": "2.0.1",
|
"@tiptap/extension-placeholder": "2.0.3",
|
||||||
"@tiptap/extension-strike": "2.0.1",
|
"@tiptap/extension-strike": "2.0.3",
|
||||||
"@tiptap/extension-text": "2.0.1",
|
"@tiptap/extension-text": "2.0.3",
|
||||||
"@tiptap/extension-underline": "2.0.1",
|
"@tiptap/extension-underline": "2.0.3",
|
||||||
"@tiptap/extension-youtube": "2.0.1",
|
"@tiptap/extension-youtube": "2.0.3",
|
||||||
"@types/express": "4.17.17",
|
"@types/express": "4.17.17",
|
||||||
"@types/node": "18.15.11",
|
"@types/node": "18.16.3",
|
||||||
"@types/uuid": "9.0.1",
|
"@types/uuid": "9.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "5.57.0",
|
"@typescript-eslint/eslint-plugin": "5.59.2",
|
||||||
"@typescript-eslint/parser": "5.57.0",
|
"@typescript-eslint/parser": "5.59.2",
|
||||||
"@urql/core": "3.2.2",
|
"@urql/core": "3.2.2",
|
||||||
"@urql/devtools": "2.0.3",
|
"@urql/devtools": "2.0.3",
|
||||||
"@urql/exchange-graphcache": "5.2.0",
|
"@urql/exchange-graphcache": "5.2.0",
|
||||||
"babel-preset-solid": "1.7.0",
|
"babel-preset-solid": "1.7.4",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bootstrap": "5.2.3",
|
"bootstrap": "5.2.3",
|
||||||
"clsx": "1.2.1",
|
"clsx": "1.2.1",
|
||||||
|
@ -102,27 +101,27 @@
|
||||||
"cookie-signature": "1.2.1",
|
"cookie-signature": "1.2.1",
|
||||||
"cosmiconfig-toml-loader": "1.0.0",
|
"cosmiconfig-toml-loader": "1.0.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"eslint": "8.37.0",
|
"eslint": "8.39.0",
|
||||||
"eslint-config-stylelint": "18.0.0",
|
"eslint-config-stylelint": "18.0.0",
|
||||||
"eslint-import-resolver-typescript": "3.5.4",
|
"eslint-import-resolver-typescript": "3.5.5",
|
||||||
"eslint-plugin-import": "2.27.5",
|
"eslint-plugin-import": "2.27.5",
|
||||||
"eslint-plugin-jsx-a11y": "6.7.1",
|
"eslint-plugin-jsx-a11y": "6.7.1",
|
||||||
"eslint-plugin-promise": "6.1.1",
|
"eslint-plugin-promise": "6.1.1",
|
||||||
"eslint-plugin-solid": "0.12.0",
|
"eslint-plugin-solid": "0.12.1",
|
||||||
"eslint-plugin-sonarjs": "0.19.0",
|
"eslint-plugin-sonarjs": "0.19.0",
|
||||||
"eslint-plugin-unicorn": "46.0.0",
|
"eslint-plugin-unicorn": "46.0.0",
|
||||||
"graphql": "16.6.0",
|
"graphql": "16.6.0",
|
||||||
"graphql-tag": "2.12.6",
|
"graphql-tag": "2.12.6",
|
||||||
"graphql-ws": "5.12.0",
|
"graphql-ws": "5.12.1",
|
||||||
"hast-util-select": "5.0.5",
|
"hast-util-select": "5.0.5",
|
||||||
|
"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",
|
||||||
"idb": "7.1.1",
|
"idb": "7.1.1",
|
||||||
"install": "0.13.0",
|
|
||||||
"jest": "29.5.0",
|
"jest": "29.5.0",
|
||||||
"js-cookie": "3.0.1",
|
"js-cookie": "3.0.5",
|
||||||
"lint-staged": "13.2.0",
|
"lint-staged": "13.2.2",
|
||||||
"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",
|
"markdown-it": "13.0.1",
|
||||||
|
@ -131,9 +130,8 @@
|
||||||
"markdown-it-mark": "3.0.1",
|
"markdown-it-mark": "3.0.1",
|
||||||
"markdown-it-replace-link": "1.2.0",
|
"markdown-it-replace-link": "1.2.0",
|
||||||
"nanostores": "0.7.4",
|
"nanostores": "0.7.4",
|
||||||
"npm": "9.6.3",
|
|
||||||
"orderedmap": "2.1.0",
|
"orderedmap": "2.1.0",
|
||||||
"prettier": "2.8.7",
|
"prettier": "2.8.8",
|
||||||
"prettier-eslint": "15.0.1",
|
"prettier-eslint": "15.0.1",
|
||||||
"prosemirror-commands": "1.5.1",
|
"prosemirror-commands": "1.5.1",
|
||||||
"prosemirror-dropcursor": "1.8.0",
|
"prosemirror-dropcursor": "1.8.0",
|
||||||
|
@ -148,43 +146,43 @@
|
||||||
"prosemirror-schema-list": "1.2.2",
|
"prosemirror-schema-list": "1.2.2",
|
||||||
"prosemirror-state": "1.4.2",
|
"prosemirror-state": "1.4.2",
|
||||||
"prosemirror-view": "1.30.2",
|
"prosemirror-view": "1.30.2",
|
||||||
"rollup": "3.20.2",
|
"rollup": "3.21.3",
|
||||||
"rollup-plugin-visualizer": "5.9.0",
|
"rollup-plugin-visualizer": "5.9.0",
|
||||||
"sass": "1.60.0",
|
"sass": "1.62.1",
|
||||||
"solid-js": "1.7.0",
|
"solid-js": "1.7.3",
|
||||||
"solid-tiptap": "0.5.1",
|
"solid-tiptap": "0.6.0",
|
||||||
"solid-transition-group": "0.2.2",
|
"solid-transition-group": "0.2.2",
|
||||||
"sort-package-json": "2.4.1",
|
"sort-package-json": "2.4.1",
|
||||||
"stylelint": "15.3.0",
|
"stylelint": "15.6.1",
|
||||||
"stylelint-config-standard-scss": "7.0.1",
|
"stylelint-config-standard-scss": "9.0.0",
|
||||||
"stylelint-order": "6.0.3",
|
"stylelint-order": "6.0.3",
|
||||||
"stylelint-scss": "4.6.0",
|
"stylelint-scss": "5.0.0",
|
||||||
"swiper": "8.4.7",
|
"swiper": "8.4.7",
|
||||||
"ts-node": "10.9.1",
|
"ts-node": "10.9.1",
|
||||||
"typescript": "5.0.3",
|
"typescript": "5.0.4",
|
||||||
"undici": "5.21.0",
|
"undici": "5.21.0",
|
||||||
"uniqolor": "1.1.0",
|
"uniqolor": "1.1.0",
|
||||||
"unique-names-generator": "4.7.1",
|
"unique-names-generator": "4.7.1",
|
||||||
"uuid": "9.0.0",
|
"uuid": "9.0.0",
|
||||||
"vite": "4.2.1",
|
"vite": "4.3.4",
|
||||||
"vite-plugin-sass-dts": "1.3.2",
|
"vite-plugin-sass-dts": "1.3.4",
|
||||||
"vite-plugin-solid": "2.6.1",
|
"vite-plugin-solid": "2.7.0",
|
||||||
"vite-plugin-ssr": "0.4.108",
|
"vite-plugin-ssr": "0.4.121",
|
||||||
"wonka": "6.3.1",
|
"wonka": "6.3.1",
|
||||||
"ws": "8.13.0",
|
"ws": "8.13.0",
|
||||||
"y-indexeddb": "9.0.10",
|
"y-indexeddb": "9.0.10",
|
||||||
"y-prosemirror": "1.2.0",
|
"y-prosemirror": "1.2.1",
|
||||||
"y-protocols": "1.0.5",
|
"y-protocols": "1.0.5",
|
||||||
"y-webrtc": "10.2.5",
|
"y-webrtc": "10.2.5",
|
||||||
"y-websocket": "1.5.0",
|
"y-websocket": "1.5.0",
|
||||||
"yjs": "13.5.51"
|
"yjs": "13.6.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@tiptap/extension-collaboration": {
|
"@tiptap/extension-collaboration": {
|
||||||
"y-prosemirror": "1.2.0"
|
"y-prosemirror": "1.2.1"
|
||||||
},
|
},
|
||||||
"@tiptap/extension-collaboration-cursor": {
|
"@tiptap/extension-collaboration-cursor": {
|
||||||
"y-prosemirror": "1.2.0"
|
"y-prosemirror": "1.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,8 +67,8 @@
|
||||||
"Enter your new password": "Введите новый пароль",
|
"Enter your new password": "Введите новый пароль",
|
||||||
"Error": "Ошибка",
|
"Error": "Ошибка",
|
||||||
"Everything is ok, please give us your email address": "Ничего страшного, просто укажите свою почту, чтобы получить ссылку для сброса пароля.",
|
"Everything is ok, please give us your email address": "Ничего страшного, просто укажите свою почту, чтобы получить ссылку для сброса пароля.",
|
||||||
"FAQ": "Советы и предложения",
|
|
||||||
"Favorite": "Избранное",
|
"Favorite": "Избранное",
|
||||||
|
"FAQ": "Советы и предложения",
|
||||||
"Favorite topics": "Избранные темы",
|
"Favorite topics": "Избранные темы",
|
||||||
"Feed settings": "Настройки ленты",
|
"Feed settings": "Настройки ленты",
|
||||||
"Feedback": "Обратная связь",
|
"Feedback": "Обратная связь",
|
||||||
|
@ -102,8 +102,8 @@
|
||||||
"Independant magazine with an open horizontal cooperation about culture, science and society": "Независимый журнал с открытой горизонтальной редакцией о культуре, науке и обществе",
|
"Independant magazine with an open horizontal cooperation about culture, science and society": "Независимый журнал с открытой горизонтальной редакцией о культуре, науке и обществе",
|
||||||
"Introduce": "Представление",
|
"Introduce": "Представление",
|
||||||
"Invalid email": "Проверьте правильность ввода почты",
|
"Invalid email": "Проверьте правильность ввода почты",
|
||||||
"Invalid url format": "Неверный формат ссылки",
|
|
||||||
"Invite co-authors": "Пригласить соавторов",
|
"Invite co-authors": "Пригласить соавторов",
|
||||||
|
"Invalid url format": "Неверный формат ссылки",
|
||||||
"Invite experts": "Пригласить экспертов",
|
"Invite experts": "Пригласить экспертов",
|
||||||
"Invite to collab": "Пригласить к участию",
|
"Invite to collab": "Пригласить к участию",
|
||||||
"It does not look like url": "Это не похоже на ссылку",
|
"It does not look like url": "Это не похоже на ссылку",
|
||||||
|
@ -154,11 +154,13 @@
|
||||||
"Please, confirm email": "Пожалуйста, подтвердите электронную почту",
|
"Please, confirm email": "Пожалуйста, подтвердите электронную почту",
|
||||||
"Popular": "Популярное",
|
"Popular": "Популярное",
|
||||||
"Popular authors": "Популярные авторы",
|
"Popular authors": "Популярные авторы",
|
||||||
|
"Preview": "Предпросмотр",
|
||||||
"Principles": "Принципы сообщества",
|
"Principles": "Принципы сообщества",
|
||||||
"Profile": "Профиль",
|
"Profile": "Профиль",
|
||||||
"Profile settings": "Настройки профиля",
|
"Profile settings": "Настройки профиля",
|
||||||
"Profile successfully saved": "Профиль успешно сохранён",
|
"Profile successfully saved": "Профиль успешно сохранён",
|
||||||
"Publications": "Публикации",
|
"Publications": "Публикации",
|
||||||
|
"Publication settings": "Настройки публикации",
|
||||||
"Publish": "Опубликовать",
|
"Publish": "Опубликовать",
|
||||||
"Quit": "Выйти",
|
"Quit": "Выйти",
|
||||||
"Quotes": "Цитаты",
|
"Quotes": "Цитаты",
|
||||||
|
|
|
@ -27,6 +27,7 @@ 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 { CreatePage } from '../pages/create.page'
|
||||||
|
import { EditPage } from '../pages/edit.page'
|
||||||
import { ConnectPage } from '../pages/connect.page'
|
import { ConnectPage } from '../pages/connect.page'
|
||||||
import { InboxPage } from '../pages/inbox.page'
|
import { InboxPage } from '../pages/inbox.page'
|
||||||
import { LayoutShoutsPage } from '../pages/layoutShouts.page'
|
import { LayoutShoutsPage } from '../pages/layoutShouts.page'
|
||||||
|
@ -34,6 +35,7 @@ import { SessionProvider } from '../context/session'
|
||||||
import { ProfileSettingsPage } from '../pages/profile/profileSettings.page'
|
import { ProfileSettingsPage } from '../pages/profile/profileSettings.page'
|
||||||
import { ProfileSecurityPage } from '../pages/profile/profileSecurity.page'
|
import { ProfileSecurityPage } from '../pages/profile/profileSecurity.page'
|
||||||
import { ProfileSubscriptionsPage } from '../pages/profile/profileSubscriptions.page'
|
import { ProfileSubscriptionsPage } from '../pages/profile/profileSubscriptions.page'
|
||||||
|
import { DraftsPage } from '../pages/drafts.page'
|
||||||
import { SnackbarProvider } from '../context/snackbar'
|
import { SnackbarProvider } from '../context/snackbar'
|
||||||
import { LocalizeProvider } from '../context/localize'
|
import { LocalizeProvider } from '../context/localize'
|
||||||
import { EditorProvider } from '../context/editor'
|
import { EditorProvider } from '../context/editor'
|
||||||
|
@ -46,7 +48,9 @@ const pagesMap: Record<keyof typeof ROUTES, Component<PageProps>> = {
|
||||||
expo: LayoutShoutsPage,
|
expo: LayoutShoutsPage,
|
||||||
connect: ConnectPage,
|
connect: ConnectPage,
|
||||||
create: CreatePage,
|
create: CreatePage,
|
||||||
createSettings: CreatePage,
|
edit: EditPage,
|
||||||
|
editSettings: EditPage,
|
||||||
|
drafts: DraftsPage,
|
||||||
home: HomePage,
|
home: HomePage,
|
||||||
topics: AllTopicsPage,
|
topics: AllTopicsPage,
|
||||||
topic: TopicPage,
|
topic: TopicPage,
|
||||||
|
|
|
@ -101,9 +101,9 @@ img {
|
||||||
}
|
}
|
||||||
|
|
||||||
.writeComment {
|
.writeComment {
|
||||||
border: 2px solid #f6f6f6;
|
|
||||||
@include font-size(1.7rem);
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
|
border: 2px solid #f6f6f6;
|
||||||
outline: none;
|
outline: none;
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -114,9 +114,9 @@ img {
|
||||||
}
|
}
|
||||||
|
|
||||||
.commentWarning {
|
.commentWarning {
|
||||||
background: #f6f6f6;
|
|
||||||
@include font-size(2.2rem);
|
@include font-size(2.2rem);
|
||||||
|
|
||||||
|
background: #f6f6f6;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
padding: 2.4rem 1.8rem;
|
padding: 2.4rem 1.8rem;
|
||||||
}
|
}
|
||||||
|
@ -173,6 +173,7 @@ img {
|
||||||
a {
|
a {
|
||||||
border: none;
|
border: none;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: unset;
|
background: unset;
|
||||||
color: #000;
|
color: #000;
|
||||||
|
@ -185,9 +186,9 @@ img {
|
||||||
}
|
}
|
||||||
|
|
||||||
.shoutStatsItemInner {
|
.shoutStatsItemInner {
|
||||||
cursor: pointer;
|
|
||||||
margin: -0.3em -0.3em 0;
|
margin: -0.3em -0.3em 0;
|
||||||
padding: 0.3em;
|
padding: 0.3em;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
@ -199,7 +200,7 @@ img {
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #000;
|
background: #000;
|
||||||
cursor: pointer;
|
|
||||||
img {
|
img {
|
||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ export default (props: { shout: Shout }) => {
|
||||||
return []
|
return []
|
||||||
})
|
})
|
||||||
let audioRef: HTMLAudioElement
|
let audioRef: HTMLAudioElement
|
||||||
const [currentTrack, setCurrentTrack] = createSignal(media()[0])
|
const [currentTrack] = createSignal(media()[0])
|
||||||
const [paused, setPaused] = createSignal(true)
|
const [paused, setPaused] = createSignal(true)
|
||||||
const togglePlayPause = () => setPaused(!paused())
|
const togglePlayPause = () => setPaused(!paused())
|
||||||
const playMedia = (m: MediaItem) => {
|
const playMedia = (m: MediaItem) => {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
&.isNew {
|
&.isNew {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: rgba(38, 56, 217, 0.05);
|
background: rgb(38 56 217 / 5%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(sm) {
|
||||||
|
@ -178,6 +178,7 @@
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
color: rgb(0 0 0 / 30%);
|
color: rgb(0 0 0 / 30%);
|
||||||
|
|
||||||
@include font-size(1.2rem);
|
@include font-size(1.2rem);
|
||||||
|
|
||||||
.date {
|
.date {
|
||||||
|
@ -189,6 +190,7 @@
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media-breakpoint-down(md) {
|
@include media-breakpoint-down(md) {
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import styles from './Comment.module.scss'
|
import styles from './Comment.module.scss'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { AuthorCard } from '../Author/Card'
|
import { AuthorCard } from '../Author/AuthorCard'
|
||||||
import { Show, createMemo, createSignal, For, lazy, Suspense, createEffect } from 'solid-js'
|
import { Show, createMemo, createSignal, For, lazy, Suspense } from 'solid-js'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import type { Author, Reaction } from '../../graphql/types.gen'
|
import type { Author, Reaction } from '../../graphql/types.gen'
|
||||||
import MD from './MD'
|
import MD from './MD'
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: opacity 0.3s ease-in-out;
|
transition: opacity 0.3s ease-in-out;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
@ -31,6 +32,7 @@
|
||||||
|
|
||||||
.commentRatingControlUp {
|
.commentRatingControlUp {
|
||||||
border-bottom: 8px solid rgb(0 0 0 / 40%);
|
border-bottom: 8px solid rgb(0 0 0 / 40%);
|
||||||
|
|
||||||
&.voted {
|
&.voted {
|
||||||
border-bottom-color: #2bb452;
|
border-bottom-color: #2bb452;
|
||||||
}
|
}
|
||||||
|
@ -38,6 +40,7 @@
|
||||||
|
|
||||||
.commentRatingControlDown {
|
.commentRatingControlDown {
|
||||||
border-top: 8px solid rgb(0 0 0 / 40%);
|
border-top: 8px solid rgb(0 0 0 / 40%);
|
||||||
|
|
||||||
&.voted {
|
&.voted {
|
||||||
border-top-color: #d00820;
|
border-top-color: #d00820;
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,6 +122,7 @@ export const CommentsTree = (props: Props) => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCommentsOrder('newOnly')
|
setCommentsOrder('newOnly')
|
||||||
}}
|
}}
|
||||||
|
class={styles.commentsViewSwitcherButton}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</Show>
|
</Show>
|
||||||
|
@ -132,6 +133,7 @@ export const CommentsTree = (props: Props) => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCommentsOrder('createdAt')
|
setCommentsOrder('createdAt')
|
||||||
}}
|
}}
|
||||||
|
class={styles.commentsViewSwitcherButton}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
<li classList={{ selected: commentsOrder() === 'rating' }}>
|
<li classList={{ selected: commentsOrder() === 'rating' }}>
|
||||||
|
@ -141,6 +143,7 @@ export const CommentsTree = (props: Props) => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCommentsOrder('rating')
|
setCommentsOrder('rating')
|
||||||
}}
|
}}
|
||||||
|
class={styles.commentsViewSwitcherButton}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { capitalize, formatDate } from '../../utils'
|
import { capitalize, formatDate } from '../../utils'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { AuthorCard } from '../Author/Card'
|
import { AuthorCard } from '../Author/AuthorCard'
|
||||||
import { createEffect, createMemo, createSignal, For, Match, onMount, Show, Switch } from 'solid-js'
|
import { createEffect, createMemo, createSignal, For, Match, onMount, Show, Switch } from 'solid-js'
|
||||||
import type { Author, Shout } from '../../graphql/types.gen'
|
import type { Author, Shout } from '../../graphql/types.gen'
|
||||||
import MD from './MD'
|
import MD from './MD'
|
||||||
|
@ -77,7 +77,8 @@ export const FullArticle = (props: ArticleProps) => {
|
||||||
|
|
||||||
const canEdit = () => props.article.authors?.some((a) => a.slug === user()?.slug)
|
const canEdit = () => props.article.authors?.some((a) => a.slug === user()?.slug)
|
||||||
|
|
||||||
const bookmark = (ev) => {
|
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||||
|
const handleBookmarkButtonClick = (ev) => {
|
||||||
// TODO: implement bookmark clicked
|
// TODO: implement bookmark clicked
|
||||||
ev.preventDefault()
|
ev.preventDefault()
|
||||||
}
|
}
|
||||||
|
@ -97,14 +98,14 @@ export const FullArticle = (props: ArticleProps) => {
|
||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const { searchParams } = useRouter()
|
const { searchParams, changeSearchParam } = useRouter()
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (props.scrollToComments) {
|
if (props.scrollToComments) {
|
||||||
scrollToComments()
|
scrollToComments()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const { changeSearchParam } = useRouter()
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (searchParams()?.scrollTo === 'comments' && commentsRef.current) {
|
if (searchParams()?.scrollTo === 'comments' && commentsRef.current) {
|
||||||
scrollToComments()
|
scrollToComments()
|
||||||
|
@ -228,15 +229,17 @@ export const FullArticle = (props: ArticleProps) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class={styles.shoutStatsItem} onClick={bookmark}>
|
<div class={styles.shoutStatsItem} onClick={handleBookmarkButtonClick}>
|
||||||
<div class={styles.shoutStatsItemInner}>
|
<div class={styles.shoutStatsItemInner}>
|
||||||
<Icon name="bookmark" class={styles.icon} />
|
<Icon name="bookmark" class={styles.icon} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={canEdit()}>
|
<Show when={canEdit()}>
|
||||||
<div class={styles.shoutStatsItem}>
|
<div class={styles.shoutStatsItem}>
|
||||||
<a href="/edit" class={styles.shoutStatsItemInner}>
|
<a
|
||||||
|
href={getPagePath(router, 'edit', { shoutSlug: props.article.slug })}
|
||||||
|
class={styles.shoutStatsItemInner}
|
||||||
|
>
|
||||||
<Icon name="edit" class={clsx(styles.icon, styles.iconEdit)} />
|
<Icon name="edit" class={clsx(styles.icon, styles.iconEdit)} />
|
||||||
{t('Edit')}
|
{t('Edit')}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import styles from './ShoutRatingControl.module.scss'
|
import styles from './ShoutRatingControl.module.scss'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createMemo, For } from 'solid-js'
|
import { createMemo } from 'solid-js'
|
||||||
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 { useSession } from '../../context/session'
|
||||||
|
|
|
@ -169,12 +169,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonWrite {
|
.buttonWrite {
|
||||||
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
color: #000;
|
color: #000;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
transition: background-color 0.3s, color 0.3s;
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
|
||||||
@include font-size(1.5rem);
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #000;
|
background: #000;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
@ -208,8 +208,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.authorAbout {
|
.authorAbout {
|
||||||
color: #696969;
|
|
||||||
@include font-size(1.7rem);
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
|
color: #696969;
|
||||||
}
|
}
|
||||||
|
|
||||||
.authorSubscribe {
|
.authorSubscribe {
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Author, User } from '../../graphql/types.gen'
|
import type { Author } from '../../graphql/types.gen'
|
||||||
import Userpic from './Userpic'
|
import Userpic from './Userpic'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import styles from './Card.module.scss'
|
import styles from './AuthorCard.module.scss'
|
||||||
import { createMemo, createSignal, For, Show } from 'solid-js'
|
import { createMemo, createSignal, For, Show } from 'solid-js'
|
||||||
import { translit } from '../../utils/ru2en'
|
import { translit } from '../../utils/ru2en'
|
||||||
import { follow, unfollow } from '../../stores/zine/common'
|
import { follow, unfollow } from '../../stores/zine/common'
|
|
@ -11,6 +11,7 @@ export const AuthorRatingControl = (props: AuthorRatingControlProps) => {
|
||||||
const isUpvoted = false
|
const isUpvoted = false
|
||||||
const isDownvoted = false
|
const isDownvoted = false
|
||||||
|
|
||||||
|
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||||
const handleRatingChange = (isUpvote: boolean) => {
|
const handleRatingChange = (isUpvote: boolean) => {
|
||||||
console.log('handleRatingChange', { isUpvote })
|
console.log('handleRatingChange', { isUpvote })
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Author } from '../../graphql/types.gen'
|
import type { Author } from '../../graphql/types.gen'
|
||||||
import { AuthorCard } from './Card'
|
import { AuthorCard } from './AuthorCard'
|
||||||
import './Full.scss'
|
import './Full.scss'
|
||||||
|
|
||||||
export const AuthorFull = (props: { author: Author }) => {
|
export const AuthorFull = (props: { author: Author }) => {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Show } from 'solid-js'
|
import { Show } from 'solid-js'
|
||||||
import type { Author } from '../../graphql/types.gen'
|
import type { Author, User } from '../../graphql/types.gen'
|
||||||
import styles from './Userpic.module.scss'
|
import styles from './Userpic.module.scss'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
interface UserpicProps {
|
interface UserpicProps {
|
||||||
user: Author
|
user: Author | User
|
||||||
hasLink?: boolean
|
hasLink?: boolean
|
||||||
isBig?: boolean
|
isBig?: boolean
|
||||||
class?: string
|
class?: string
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
.discoursFooter {
|
.discoursFooter {
|
||||||
background: #000;
|
|
||||||
color: rgb(255 255 255 / 64%);
|
|
||||||
@include font-size(1.7rem);
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
|
background: #000;
|
||||||
|
color: rgb(255 255 255 / 64%);
|
||||||
padding: 2.4rem 0 4.2rem;
|
padding: 2.4rem 0 4.2rem;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
@ -48,10 +48,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.footerCopyright {
|
.footerCopyright {
|
||||||
border-top: 5px solid #404040;
|
|
||||||
color: #696969;
|
|
||||||
@include font-size(1.5rem);
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
|
border-top: 5px solid #404040;
|
||||||
|
color: #696969;
|
||||||
padding-top: 1.6rem;
|
padding-top: 1.6rem;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
.about-discours {
|
.about-discours {
|
||||||
background: #000;
|
|
||||||
color: #fff;
|
|
||||||
@include font-size(1.7rem);
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
|
background: #000;
|
||||||
|
color: #fff;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
margin-bottom: 6.4rem;
|
margin-bottom: 6.4rem;
|
||||||
padding: 3.6rem 0;
|
padding: 3.6rem 0;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.navigationHeader {
|
.navigationHeader {
|
||||||
@include font-size(1.8rem);
|
@include font-size(1.8rem);
|
||||||
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-top: 1.1em;
|
margin-top: 1.1em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,13 +11,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
|
@include font-size(2rem);
|
||||||
|
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 1px solid;
|
border-bottom: 1px solid;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
@include font-size(2rem);
|
|
||||||
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
@ -30,13 +30,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: none;
|
border: none;
|
||||||
color: #000;
|
color: #000;
|
||||||
display: flex;
|
display: flex;
|
||||||
@include font-size(1.5rem);
|
|
||||||
|
|
||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { TrailingNode } from './extensions/TrailingNode'
|
||||||
import { EditorBubbleMenu } from './EditorBubbleMenu/EditorBubbleMenu'
|
import { EditorBubbleMenu } from './EditorBubbleMenu/EditorBubbleMenu'
|
||||||
import { EditorFloatingMenu } from './EditorFloatingMenu'
|
import { EditorFloatingMenu } from './EditorFloatingMenu'
|
||||||
import * as Y from 'yjs'
|
import * as Y from 'yjs'
|
||||||
|
// import { WebrtcProvider } from 'y-webrtc'
|
||||||
import { CollaborationCursor } from '@tiptap/extension-collaboration-cursor'
|
import { CollaborationCursor } from '@tiptap/extension-collaboration-cursor'
|
||||||
import { Collaboration } from '@tiptap/extension-collaboration'
|
import { Collaboration } from '@tiptap/extension-collaboration'
|
||||||
import './Prosemirror.scss'
|
import './Prosemirror.scss'
|
||||||
|
@ -40,7 +41,7 @@ import { Embed } from './extensions/embed'
|
||||||
import { useEditorContext } from '../../context/editor'
|
import { useEditorContext } from '../../context/editor'
|
||||||
|
|
||||||
type EditorProps = {
|
type EditorProps = {
|
||||||
shoutId: number
|
shoutSlug: string
|
||||||
initialContent?: string
|
initialContent?: string
|
||||||
onChange: (text: string) => void
|
onChange: (text: string) => void
|
||||||
}
|
}
|
||||||
|
@ -53,7 +54,7 @@ export const Editor = (props: EditorProps) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { user } = useSession()
|
const { user } = useSession()
|
||||||
|
|
||||||
const docName = `shout-${props.shoutId}`
|
const docName = `shout-${props.shoutSlug}`
|
||||||
|
|
||||||
if (!providers[docName]) {
|
if (!providers[docName]) {
|
||||||
providers[docName] = new HocuspocusProvider({
|
providers[docName] = new HocuspocusProvider({
|
||||||
|
@ -88,6 +89,8 @@ export const Editor = (props: EditorProps) => {
|
||||||
|
|
||||||
const editor = createTiptapEditor(() => ({
|
const editor = createTiptapEditor(() => ({
|
||||||
element: editorElRef.current,
|
element: editorElRef.current,
|
||||||
|
content: props.initialContent,
|
||||||
|
//onTransaction: handleEditorTransaction,
|
||||||
extensions: [
|
extensions: [
|
||||||
Document,
|
Document,
|
||||||
Text,
|
Text,
|
||||||
|
@ -105,18 +108,10 @@ export const Editor = (props: EditorProps) => {
|
||||||
Heading.configure({
|
Heading.configure({
|
||||||
levels: [1, 2, 3]
|
levels: [1, 2, 3]
|
||||||
}),
|
}),
|
||||||
BubbleMenu.configure({
|
|
||||||
element: bubbleMenuRef.current
|
|
||||||
}),
|
|
||||||
FloatingMenu.configure({
|
|
||||||
tippyOptions: {
|
|
||||||
placement: 'left'
|
|
||||||
},
|
|
||||||
element: floatingMenuRef.current
|
|
||||||
}),
|
|
||||||
BulletList,
|
BulletList,
|
||||||
OrderedList,
|
OrderedList,
|
||||||
ListItem,
|
ListItem,
|
||||||
|
CharacterCount,
|
||||||
Collaboration.configure({
|
Collaboration.configure({
|
||||||
document: yDoc
|
document: yDoc
|
||||||
}),
|
}),
|
||||||
|
@ -135,10 +130,17 @@ export const Editor = (props: EditorProps) => {
|
||||||
HardBreak,
|
HardBreak,
|
||||||
Highlight,
|
Highlight,
|
||||||
Image,
|
Image,
|
||||||
TrailingNode,
|
|
||||||
Embed,
|
Embed,
|
||||||
TrailingNode,
|
TrailingNode,
|
||||||
CharacterCount
|
BubbleMenu.configure({
|
||||||
|
element: bubbleMenuRef.current
|
||||||
|
}),
|
||||||
|
FloatingMenu.configure({
|
||||||
|
tippyOptions: {
|
||||||
|
placement: 'left'
|
||||||
|
},
|
||||||
|
element: floatingMenuRef.current
|
||||||
|
})
|
||||||
]
|
]
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
@ -40,8 +40,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: row;
|
flex-flow: row nowrap;
|
||||||
flex-wrap: nowrap;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.dropDown {
|
.dropDown {
|
||||||
|
@ -50,7 +49,7 @@
|
||||||
top: calc(100% + 8px);
|
top: calc(100% + 8px);
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 4px 10px rgb(0 0 0 / 25%);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: #898c94;
|
color: #898c94;
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ export const EditorBubbleMenu = (props: BubbleMenuProps) => {
|
||||||
const [listBubbleOpen, setListBubbleOpen] = createSignal<boolean>(false)
|
const [listBubbleOpen, setListBubbleOpen] = createSignal<boolean>(false)
|
||||||
const [linkEditorOpen, setLinkEditorOpen] = createSignal<boolean>(false)
|
const [linkEditorOpen, setLinkEditorOpen] = createSignal<boolean>(false)
|
||||||
|
|
||||||
const isActive = (name: string, attributes?: any) =>
|
const isActive = (name: string, attributes?: unknown) =>
|
||||||
createEditorTransaction(
|
createEditorTransaction(
|
||||||
() => props.editor,
|
() => props.editor,
|
||||||
(editor) => {
|
(editor) => {
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
.LinkForm {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
padding: 6px 11px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin: 0 12px 0 0;
|
||||||
|
padding: 0;
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
min-width: 200px;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: rgba(#000, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkError {
|
||||||
|
padding: 6px 11px;
|
||||||
|
color: red;
|
||||||
|
font-size: 0.7em;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -3rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 0;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 4px 10px rgba(#000, 0.25);
|
||||||
|
opacity: 0;
|
||||||
|
transition: height 0.3s ease-in-out, opacity 0.3s ease-in-out;
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
height: 32px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
src/components/Editor/EditorBubbleMenu/LinkForm/LinkForm.tsx
Normal file
85
src/components/Editor/EditorBubbleMenu/LinkForm/LinkForm.tsx
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import styles from './LinkForm.module.scss'
|
||||||
|
import { Icon } from '../../../_shared/Icon'
|
||||||
|
import { createEditorTransaction } from 'solid-tiptap'
|
||||||
|
import validateUrl from '../../../../utils/validateUrl'
|
||||||
|
import type { Editor } from '@tiptap/core'
|
||||||
|
import { createSignal } from 'solid-js'
|
||||||
|
import { useLocalize } from '../../../../context/localize'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
editor: Editor
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LinkForm = (props: Props) => {
|
||||||
|
const { t } = useLocalize()
|
||||||
|
const [url, setUrl] = createSignal('')
|
||||||
|
const [linkError, setLinkError] = createSignal('')
|
||||||
|
|
||||||
|
const currentUrl = createEditorTransaction(
|
||||||
|
() => props.editor,
|
||||||
|
(editor) => {
|
||||||
|
return (editor && editor.getAttributes('link').href) || ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const clearLinkForm = () => {
|
||||||
|
if (currentUrl()) {
|
||||||
|
props.editor.chain().focus().unsetLink().run()
|
||||||
|
}
|
||||||
|
setUrl('')
|
||||||
|
props.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUrlInput = (value) => {
|
||||||
|
setUrl(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSaveButtonClick = () => {
|
||||||
|
if (!validateUrl(url())) {
|
||||||
|
setLinkError(t('Invalid url format'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
props.editor.chain().focus().setLink({ href: url() }).run()
|
||||||
|
props.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleKeyPress = (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
setLinkError('')
|
||||||
|
const key = event.key
|
||||||
|
|
||||||
|
if (key === 'Enter') {
|
||||||
|
handleSaveButtonClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'Esc') {
|
||||||
|
clearLinkForm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={styles.LinkForm}>
|
||||||
|
<div class={styles.form}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={t('Enter URL address')}
|
||||||
|
autofocus
|
||||||
|
value={currentUrl()}
|
||||||
|
onKeyPress={(e) => handleKeyPress(e)}
|
||||||
|
onInput={(e) => handleUrlInput(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<button type="button" onClick={handleSaveButtonClick} disabled={linkError() !== ''}>
|
||||||
|
<Icon name="status-done" />
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => clearLinkForm()}>
|
||||||
|
{currentUrl() ? 'Ж' : <Icon name="status-cancel" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={clsx(styles.linkError, { [styles.visible]: Boolean(linkError()) })}>{linkError()}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
1
src/components/Editor/EditorBubbleMenu/LinkForm/index.ts
Normal file
1
src/components/Editor/EditorBubbleMenu/LinkForm/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { LinkForm } from './LinkForm'
|
|
@ -12,25 +12,35 @@ type FloatingMenuProps = {
|
||||||
|
|
||||||
const embedData = async (data) => {
|
const embedData = async (data) => {
|
||||||
const result = await HTMLParser(data, false)
|
const result = await HTMLParser(data, false)
|
||||||
|
|
||||||
|
if (typeof result === 'string') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (result && 'type' in result && result.type === 'iframe') {
|
if (result && 'type' in result && result.type === 'iframe') {
|
||||||
return result.attributes
|
return result.attributes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const validateEmbed = async (value: string): Promise<string> => {
|
||||||
|
const iframeData = await HTMLParser(value, false)
|
||||||
|
|
||||||
|
if (typeof iframeData === 'string') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iframeData && iframeData.type !== 'iframe') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const EditorFloatingMenu = (props: FloatingMenuProps) => {
|
export const EditorFloatingMenu = (props: FloatingMenuProps) => {
|
||||||
const [inlineEditorOpen, setInlineEditorOpen] = createSignal<boolean>(false)
|
const [inlineEditorOpen, setInlineEditorOpen] = createSignal<boolean>(false)
|
||||||
|
|
||||||
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 { src } = (await embedData(value)) as { src: string }
|
||||||
props.editor.chain().focus().setIframe(emb).run()
|
props.editor.chain().focus().setIframe({ src }).run()
|
||||||
}
|
|
||||||
|
|
||||||
const validateEmbed = async (value) => {
|
|
||||||
const iframeData = await HTMLParser(value, false)
|
|
||||||
if (iframeData && iframeData.type !== 'iframe') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&.inBubble {
|
&.inBubble {
|
||||||
//...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
&.inFloating {
|
&.inFloating {
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
button {
|
button {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
||||||
&:disabled,
|
&:disabled,
|
||||||
&:disabled:hover {
|
&:disabled:hover {
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
|
@ -24,8 +25,7 @@
|
||||||
|
|
||||||
.form {
|
.form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-flow: row nowrap;
|
||||||
flex-wrap: nowrap;
|
|
||||||
padding: 6px 11px;
|
padding: 6px 11px;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
|
@ -39,6 +39,7 @@
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: rgba(#000, 0.3);
|
color: rgba(#000, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import styles from './InlineForm.module.scss'
|
import styles from './InlineForm.module.scss'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { createEditorTransaction } from 'solid-tiptap'
|
|
||||||
import type { Editor } from '@tiptap/core'
|
|
||||||
import { createSignal, Show } from 'solid-js'
|
import { createSignal, Show } from 'solid-js'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
@ -50,7 +48,7 @@ export const InlineForm = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.InlineForm, {
|
class={clsx(styles.InlineForm, {
|
||||||
[styles.inBubble]: props.variant === 'inBubble',
|
// [styles.inBubble]: props.variant === 'inBubble',
|
||||||
[styles.inFloating]: props.variant === 'inFloating'
|
[styles.inFloating]: props.variant === 'inFloating'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.Panel {
|
.Panel {
|
||||||
background: #1f1f1f;
|
background: #1f1f1f;
|
||||||
color: rgb(255 255 255 / 0.35);
|
color: rgb(255 255 255 / 35%);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font-size: 1.7rem;
|
font-size: 1.7rem;
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
border-bottom: 2px solid rgb(255 255 255 / 0.1);
|
border-bottom: 2px solid rgb(255 255 255 / 10%);
|
||||||
padding: 1.8rem 0;
|
padding: 1.8rem 0;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
|
@ -56,18 +56,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonWithIcon {
|
.linkWithIcon {
|
||||||
margin-left: -1.6rem;
|
margin-left: -1.6rem;
|
||||||
|
|
||||||
.icon {
|
|
||||||
filter: invert(0.5);
|
|
||||||
margin-right: 0.3em;
|
|
||||||
width: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats {
|
.stats {
|
||||||
|
@ -79,7 +69,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: rgb(255 255 255 / 0.35);
|
color: rgb(255 255 255 / 35%);
|
||||||
font-weight: normal !important;
|
font-weight: normal !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -87,7 +77,19 @@
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
filter: invert(0.5);
|
||||||
|
margin-right: 0.3em;
|
||||||
|
width: 1em;
|
||||||
|
|
||||||
|
img {
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import { Show } from 'solid-js'
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { Button } from '../../_shared/Button'
|
import { Button } from '../../_shared/Button'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import styles from './Panel.module.scss'
|
import styles from './Panel.module.scss'
|
||||||
import { useEditorContext } from '../../../context/editor'
|
import { useEditorContext } from '../../../context/editor'
|
||||||
|
import { useOutsideClickHandler } from '../../../utils/useOutsideClickHandler'
|
||||||
|
import { useEscKeyDownHandler } from '../../../utils/useEscKeyDownHandler'
|
||||||
|
import { getPagePath } from '@nanostores/router'
|
||||||
|
import { router } from '../../../stores/router'
|
||||||
|
|
||||||
type Props = {
|
type PanelProps = {
|
||||||
// isVisible: boolean
|
shoutSlug: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Panel = (props: Props) => {
|
export const Panel = (props: PanelProps) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const {
|
const {
|
||||||
isEditorPanelVisible,
|
isEditorPanelVisible,
|
||||||
|
@ -18,8 +21,27 @@ export const Panel = (props: Props) => {
|
||||||
actions: { toggleEditorPanel }
|
actions: { toggleEditorPanel }
|
||||||
} = useEditorContext()
|
} = useEditorContext()
|
||||||
|
|
||||||
|
const containerRef: { current: HTMLElement } = {
|
||||||
|
current: null
|
||||||
|
}
|
||||||
|
|
||||||
|
useOutsideClickHandler({
|
||||||
|
containerRef,
|
||||||
|
predicate: () => isEditorPanelVisible(),
|
||||||
|
handler: () => toggleEditorPanel()
|
||||||
|
})
|
||||||
|
|
||||||
|
useEscKeyDownHandler(() => {
|
||||||
|
if (isEditorPanelVisible()) {
|
||||||
|
toggleEditorPanel()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside class={clsx('col-md-6', styles.Panel, { [styles.hidden]: !isEditorPanelVisible() })}>
|
<aside
|
||||||
|
ref={(el) => (containerRef.current = el)}
|
||||||
|
class={clsx('col-md-6', styles.Panel, { [styles.hidden]: !isEditorPanelVisible() })}
|
||||||
|
>
|
||||||
<div class={styles.actionsHolder}>
|
<div class={styles.actionsHolder}>
|
||||||
<Button
|
<Button
|
||||||
value={<Icon name="close" />}
|
value={<Icon name="close" />}
|
||||||
|
@ -30,47 +52,50 @@ export const Panel = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
<div class={clsx(styles.actionsHolder, styles.scrolled)}>
|
<div class={clsx(styles.actionsHolder, styles.scrolled)}>
|
||||||
<section>
|
<section>
|
||||||
<Button value={t('Publish')} variant={'inline'} class={styles.button} />
|
<p>
|
||||||
<Button value={t('Save draft')} variant={'inline'} class={styles.button} />
|
<a>{t('Publish')}</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a>{t('Save draft')}</a>
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<Button
|
<p>
|
||||||
value={
|
<a class={styles.linkWithIcon}>
|
||||||
<>
|
<Icon name="eye" class={styles.icon} />
|
||||||
<Icon name="eye" class={styles.icon} />
|
{t('Preview')}
|
||||||
{t('Preview')}
|
</a>
|
||||||
</>
|
</p>
|
||||||
}
|
<p>
|
||||||
variant={'inline'}
|
<a
|
||||||
class={clsx(styles.button, styles.buttonWithIcon)}
|
class={styles.linkWithIcon}
|
||||||
/>
|
href={getPagePath(router, 'edit', { shoutSlug: props.shoutSlug })}
|
||||||
<Button
|
>
|
||||||
value={
|
<Icon name="pencil-outline" class={styles.icon} />
|
||||||
<>
|
{t('Editing')}
|
||||||
<Icon name="pencil-outline" class={styles.icon} />
|
</a>
|
||||||
{t('Editing')}
|
</p>
|
||||||
</>
|
<p>
|
||||||
}
|
<a class={styles.linkWithIcon}>
|
||||||
variant={'inline'}
|
<Icon name="feed-discussion" class={styles.icon} />
|
||||||
class={clsx(styles.button, styles.buttonWithIcon)}
|
{t('FAQ')}
|
||||||
/>
|
</a>
|
||||||
<Button
|
</p>
|
||||||
value={
|
|
||||||
<>
|
|
||||||
<Icon name="feed-discussion" class={styles.icon} />
|
|
||||||
{t('FAQ')}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
variant={'inline'}
|
|
||||||
class={clsx(styles.button, styles.buttonWithIcon)}
|
|
||||||
/>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<Button value={t('Invite co-authors')} variant={'inline'} class={styles.button} />
|
<p>
|
||||||
<Button value={t('Publication settings')} variant={'inline'} class={styles.button} />
|
<a>{t('Invite co-authors')}</a>
|
||||||
<Button value={t('Corrections history')} variant={'inline'} class={styles.button} />
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href={getPagePath(router, 'editSettings', { shoutSlug: props.shoutSlug })}>
|
||||||
|
{t('Publication settings')}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a>{t('Corrections history')}</a>
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
@ -92,14 +117,9 @@ export const Panel = (props: Props) => {
|
||||||
<div>
|
<div>
|
||||||
{t('Words')}: <em>{wordCounter().words}</em>
|
{t('Words')}: <em>{wordCounter().words}</em>
|
||||||
</div>
|
</div>
|
||||||
<Show when={wordCounter().paragraphs}>
|
{/*<div>*/}
|
||||||
<div>
|
{/* {t('Last rev.')}: <em>22.03.22 в 18:20</em>*/}
|
||||||
{t('Paragraphs')}: <em>{wordCounter().paragraphs}</em>
|
{/*</div>*/}
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<div>
|
|
||||||
{t('Last rev.')}: <em>22.03.22 в 18:20</em>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
border-left: 2px solid;
|
|
||||||
@include font-size(1.6rem);
|
@include font-size(1.6rem);
|
||||||
|
|
||||||
|
border-left: 2px solid;
|
||||||
margin: 1.5em 0;
|
margin: 1.5em 0;
|
||||||
padding-left: 1.6em;
|
padding-left: 1.6em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { createOptions, Select } from '@thisbeyond/solid-select'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import '@thisbeyond/solid-select/style.css'
|
import '@thisbeyond/solid-select/style.css'
|
||||||
import './TopicSelect.scss'
|
import './TopicSelect.scss'
|
||||||
import { clone } from '../../../utils/clone'
|
|
||||||
|
|
||||||
type TopicSelectProps = {
|
type TopicSelectProps = {
|
||||||
topics: Topic[]
|
topics: Topic[]
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import { mergeAttributes, Node } from '@tiptap/core'
|
import { mergeAttributes, Node } from '@tiptap/core'
|
||||||
import { NodeRange } from 'prosemirror-model'
|
|
||||||
import { insert } from 'solid-js/web'
|
|
||||||
import { TextSelection } from 'prosemirror-state'
|
|
||||||
|
|
||||||
export interface IframeOptions {
|
export interface IframeOptions {
|
||||||
allowFullscreen: boolean
|
allowFullscreen: boolean
|
||||||
|
@ -48,7 +45,7 @@ export const Embed = Node.create<IframeOptions>({
|
||||||
const iframe = document.createElement('iframe')
|
const iframe = document.createElement('iframe')
|
||||||
iframe.width = node.attrs.width
|
iframe.width = node.attrs.width
|
||||||
iframe.height = node.attrs.height
|
iframe.height = node.attrs.height
|
||||||
iframe.allowfullscreen = node.attrs.allowfullscreen
|
iframe.allowFullscreen = node.attrs.allowFullscreen
|
||||||
iframe.src = node.attrs.src
|
iframe.src = node.attrs.src
|
||||||
div.append(iframe)
|
div.append(iframe)
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -5,10 +5,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarContainer {
|
.sidebarContainer {
|
||||||
display: none; // режим отладки
|
|
||||||
color: rgb(255 255 255 / 50%);
|
|
||||||
@include font-size(1.6rem);
|
@include font-size(1.6rem);
|
||||||
|
|
||||||
|
display: none; // режим отладки
|
||||||
|
color: rgb(255 255 255 / 50%);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
@ -85,9 +85,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
border-left: 2px solid;
|
|
||||||
@include font-size(1.6rem);
|
@include font-size(1.6rem);
|
||||||
|
|
||||||
|
border-left: 2px solid;
|
||||||
margin: 1.5em 0;
|
margin: 1.5em 0;
|
||||||
padding-left: 1.6em;
|
padding-left: 1.6em;
|
||||||
}
|
}
|
||||||
|
@ -287,10 +287,7 @@ li.ProseMirror-selectednode {
|
||||||
li.ProseMirror-selectednode::after {
|
li.ProseMirror-selectednode::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -32px;
|
inset: -2px -2px -2px -32px;
|
||||||
right: -2px;
|
|
||||||
top: -2px;
|
|
||||||
bottom: -2px;
|
|
||||||
border: 2px solid #8cf;
|
border: 2px solid #8cf;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,9 +132,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.shoutCardSubtitle {
|
.shoutCardSubtitle {
|
||||||
color: #696969;
|
|
||||||
@include font-size(1.7rem);
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
|
color: #696969;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
margin-bottom: 0.8rem;
|
margin-bottom: 0.8rem;
|
||||||
|
@ -263,9 +263,9 @@
|
||||||
|
|
||||||
.shoutCardTitle,
|
.shoutCardTitle,
|
||||||
.shoutCardSubtitle {
|
.shoutCardSubtitle {
|
||||||
display: inline;
|
|
||||||
@include font-size(2.6rem);
|
@include font-size(2.6rem);
|
||||||
|
|
||||||
|
display: inline;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -554,24 +554,25 @@
|
||||||
|
|
||||||
.shoutCardBigTitle {
|
.shoutCardBigTitle {
|
||||||
.shoutCardTitle {
|
.shoutCardTitle {
|
||||||
display: block;
|
|
||||||
@include font-size(3.2rem);
|
@include font-size(3.2rem);
|
||||||
|
|
||||||
|
display: block;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shoutCardSubtitle {
|
.shoutCardSubtitle {
|
||||||
color: #696969;
|
|
||||||
@include font-size(2.4rem);
|
@include font-size(2.4rem);
|
||||||
|
|
||||||
|
color: #696969;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shoutCardCompact {
|
.shoutCardCompact {
|
||||||
.shoutCardTitle,
|
.shoutCardTitle,
|
||||||
.shoutCardSubtitle {
|
.shoutCardSubtitle {
|
||||||
display: inline;
|
|
||||||
@include font-size(2.6rem);
|
@include font-size(2.6rem);
|
||||||
|
|
||||||
|
display: inline;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -669,9 +670,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.shoutCardSubtitle {
|
.shoutCardSubtitle {
|
||||||
|
@include font-size(2.4rem);
|
||||||
|
|
||||||
color: #696969;
|
color: #696969;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@include font-size(2.4rem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import type { Shout } from '../../graphql/types.gen'
|
||||||
import { capitalize } from '../../utils'
|
import { capitalize } from '../../utils'
|
||||||
import { translit } from '../../utils/ru2en'
|
import { translit } from '../../utils/ru2en'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import styles from './Card.module.scss'
|
import styles from './ArticleCard.module.scss'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { CardTopic } from './CardTopic'
|
import { CardTopic } from './CardTopic'
|
||||||
import { ShoutRatingControl } from '../Article/ShoutRatingControl'
|
import { ShoutRatingControl } from '../Article/ShoutRatingControl'
|
||||||
|
@ -37,7 +37,6 @@ interface ArticleCardProps {
|
||||||
isBeside?: boolean
|
isBeside?: boolean
|
||||||
}
|
}
|
||||||
article: Shout
|
article: Shout
|
||||||
scrollTo: 'comments'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTitleAndSubtitle = (article: Shout): { title: string; subtitle: string } => {
|
const getTitleAndSubtitle = (article: Shout): { title: string; subtitle: string } => {
|
|
@ -66,9 +66,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
border: none;
|
|
||||||
@include font-size(1.5rem);
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
|
border: none;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding-right: 0.3em;
|
padding-right: 0.3em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// TODO: additional entities list column + article
|
// TODO: additional entities list column + article
|
||||||
|
|
||||||
import { For, Show } from 'solid-js'
|
import { For, Show } from 'solid-js'
|
||||||
import { ArticleCard } from './Card'
|
import { ArticleCard } from './ArticleCard'
|
||||||
import { AuthorCard } from '../Author/Card'
|
import { AuthorCard } from '../Author/AuthorCard'
|
||||||
import { TopicCard } from '../Topic/Card'
|
import { TopicCard } from '../Topic/Card'
|
||||||
import styles from './Beside.module.scss'
|
import styles from './Beside.module.scss'
|
||||||
import type { Author, Shout, Topic, User } from '../../graphql/types.gen'
|
import type { Author, Shout, Topic, User } from '../../graphql/types.gen'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { JSX } from 'solid-js/jsx-runtime'
|
import type { JSX } from 'solid-js/jsx-runtime'
|
||||||
import { For, Show } from 'solid-js'
|
import { For, Show } from 'solid-js'
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
import { ArticleCard } from './Card'
|
import { ArticleCard } from './ArticleCard'
|
||||||
import './Group.scss'
|
import './Group.scss'
|
||||||
|
|
||||||
interface GroupProps {
|
interface GroupProps {
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
.article-preview {
|
|
||||||
background: center url('loading.gif') no-repeat;
|
|
||||||
margin: auto;
|
|
||||||
vertical-align: middle;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
/* TODO: centered full viewport */
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
import { Row1 } from './Row1'
|
|
||||||
import { Row2 } from './Row2'
|
|
||||||
import { Row3 } from './Row3'
|
|
||||||
import { shuffle } from '../../utils'
|
|
||||||
import { createMemo, createSignal, For, Suspense } from 'solid-js'
|
|
||||||
import type { JSX } from 'solid-js'
|
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
|
||||||
import './List.scss'
|
|
||||||
import { useLocalize } from '../../context/localize'
|
|
||||||
|
|
||||||
export const Block6 = (props: { articles: Shout[] }) => {
|
|
||||||
const dice = createMemo(() => shuffle([Row1, Row2, Row3]))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<For each={dice()}>{(c: (ppp: Shout[]) => JSX.Element) => c(props.articles)}</For>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ArticleListProps {
|
|
||||||
articles: Shout[]
|
|
||||||
page: number
|
|
||||||
size: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export default (props: ArticleListProps) => {
|
|
||||||
const { t } = useLocalize()
|
|
||||||
const [articles, setArticles] = createSignal(
|
|
||||||
props.articles.slice(props.page * props.size, props.size * (props.page + 1)) || []
|
|
||||||
)
|
|
||||||
const [loadingMore, setLoadingMore] = createSignal(false)
|
|
||||||
// const [, { more }] = useZine()
|
|
||||||
const handleMore = () => {
|
|
||||||
setArticles(props.articles.slice(props.page * props.size, props.size * (props.page + 1)))
|
|
||||||
if (props.size * (props.page + 1) > props.articles.length) {
|
|
||||||
console.log('[article-list] load more')
|
|
||||||
setLoadingMore(true)
|
|
||||||
// TODO: more()
|
|
||||||
setLoadingMore(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const x: number = Math.floor(articles().length / 6)
|
|
||||||
// eslint-disable-next-line unicorn/new-for-builtins
|
|
||||||
const numbers: number[] = [...Array(x).keys()]
|
|
||||||
return (
|
|
||||||
<Suspense fallback={<div class="article-preview">{t('Loading')}</div>}>
|
|
||||||
<For each={numbers}>
|
|
||||||
{() => <Block6 articles={articles().slice(0, Math.min(6, articles().length))} />}
|
|
||||||
</For>
|
|
||||||
<a href={''} onClick={handleMore} classList={{ disabled: loadingMore() }}>
|
|
||||||
{loadingMore() ? '...' : t('More')}
|
|
||||||
</a>
|
|
||||||
</Suspense>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Show } from 'solid-js'
|
import { Show } from 'solid-js'
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
import { ArticleCard } from './Card'
|
import { ArticleCard } from './ArticleCard'
|
||||||
|
|
||||||
export const Row1 = (props: { article: Shout; nodate?: boolean }) => (
|
export const Row1 = (props: { article: Shout; nodate?: boolean }) => (
|
||||||
<Show when={!!props.article}>
|
<Show when={!!props.article}>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { createComputed, createSignal, Show, For } from 'solid-js'
|
import { createComputed, createSignal, Show, For } from 'solid-js'
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
import { ArticleCard } from './Card'
|
import { ArticleCard } from './ArticleCard'
|
||||||
|
|
||||||
const x = [
|
const x = [
|
||||||
['12', '12'],
|
['12', '12'],
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { JSX } from 'solid-js/jsx-runtime'
|
import type { JSX } from 'solid-js/jsx-runtime'
|
||||||
import { For } from 'solid-js'
|
import { For } from 'solid-js'
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
import { ArticleCard } from './Card'
|
import { ArticleCard } from './ArticleCard'
|
||||||
|
|
||||||
export const Row3 = (props: { articles: Shout[]; header?: JSX.Element; nodate?: boolean }) => {
|
export const Row3 = (props: { articles: Shout[]; header?: JSX.Element; nodate?: boolean }) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
import { ArticleCard } from './Card'
|
import { ArticleCard } from './ArticleCard'
|
||||||
|
|
||||||
export const Row5 = (props: { articles: Shout[]; nodate?: boolean }) => {
|
export const Row5 = (props: { articles: Shout[]; nodate?: boolean }) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { For } from 'solid-js'
|
import { For } from 'solid-js'
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
import { ArticleCard } from './Card'
|
import { ArticleCard } from './ArticleCard'
|
||||||
|
|
||||||
export default (props: { articles: Shout[] }) => (
|
export default (props: { articles: Shout[] }) => (
|
||||||
<div class="floor floor--7">
|
<div class="floor floor--7">
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
.counter {
|
.counter {
|
||||||
@include font-size(1.2rem);
|
@include font-size(1.2rem);
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
background: #f6f6f6;
|
background: #f6f6f6;
|
||||||
|
|
|
@ -84,8 +84,8 @@ export const Sidebar = (props: FeedSidebarProps) => {
|
||||||
<div class={styles.sidebar}>
|
<div class={styles.sidebar}>
|
||||||
<ul>
|
<ul>
|
||||||
<For each={menuItems}>
|
<For each={menuItems}>
|
||||||
{(item: ListItem, index) => (
|
{(item: ListItem) => (
|
||||||
<li key={index}>
|
<li>
|
||||||
<a href="#">
|
<a href="#">
|
||||||
<span class={styles.sidebarItemName}>
|
<span class={styles.sidebarItemName}>
|
||||||
{item.icon && <Icon name={item.icon} class={styles.icon} />}
|
{item.icon && <Icon name={item.icon} class={styles.icon} />}
|
||||||
|
|
|
@ -5,11 +5,11 @@ import { useLocalize } from '../../context/localize'
|
||||||
|
|
||||||
export type MessageActionType = 'reply' | 'copy' | 'pin' | 'forward' | 'select' | 'delete'
|
export type MessageActionType = 'reply' | 'copy' | 'pin' | 'forward' | 'select' | 'delete'
|
||||||
|
|
||||||
type MessageActionsPopup = {
|
type MessageActionsPopupProps = {
|
||||||
actionSelect?: (selectedAction) => void
|
actionSelect?: (selectedAction) => void
|
||||||
} & Omit<PopupProps, 'children'>
|
} & Omit<PopupProps, 'children'>
|
||||||
|
|
||||||
export const MessageActionsPopup = (props: MessageActionsPopup) => {
|
export const MessageActionsPopup = (props: MessageActionsPopupProps) => {
|
||||||
const [selectedAction, setSelectedAction] = createSignal<MessageActionType | null>(null)
|
const [selectedAction, setSelectedAction] = createSignal<MessageActionType | null>(null)
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const actions: { name: string; action: MessageActionType }[] = [
|
const actions: { name: string; action: MessageActionType }[] = [
|
||||||
|
|
|
@ -33,12 +33,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.authImage {
|
.authImage {
|
||||||
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
background: #141414 url('/auth-page.jpg') center no-repeat;
|
background: #141414 url('/auth-page.jpg') center no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
display: flex;
|
display: flex;
|
||||||
@include font-size(1.5rem);
|
|
||||||
|
|
||||||
padding: 3em;
|
padding: 3em;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
@ -81,8 +81,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.disclaimer {
|
.disclaimer {
|
||||||
color: #9fa1a7;
|
|
||||||
@include font-size(1.2rem);
|
@include font-size(1.2rem);
|
||||||
|
|
||||||
|
color: #9fa1a7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.authActions {
|
.authActions {
|
||||||
|
|
|
@ -130,10 +130,11 @@
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
position: relative;
|
position: relative;
|
||||||
//flex: 1 100% !important;
|
|
||||||
|
// flex: 1 100% !important;
|
||||||
|
|
||||||
// replace row > * selector to remove !important
|
// replace row > * selector to remove !important
|
||||||
//width: auto !important;
|
// width: auto !important;
|
||||||
|
|
||||||
@include media-breakpoint-down(md) {
|
@include media-breakpoint-down(md) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
@ -142,9 +143,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.mainNavigationWrapper {
|
.mainNavigationWrapper {
|
||||||
|
@include font-size(1.7rem);
|
||||||
|
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
@include font-size(1.7rem);
|
|
||||||
|
|
||||||
@include media-breakpoint-down(md) {
|
@include media-breakpoint-down(md) {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -513,8 +515,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.textLabel {
|
.textLabel {
|
||||||
|
// padding: 0 1.2rem;
|
||||||
|
|
||||||
display: inline;
|
display: inline;
|
||||||
//padding: 0 1.2rem;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,10 @@ import { clsx } from 'clsx'
|
||||||
import { router, useRouter } from '../../stores/router'
|
import { router, useRouter } from '../../stores/router'
|
||||||
|
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { createSignal, Show } from 'solid-js'
|
import { createMemo, createSignal, Show } from 'solid-js'
|
||||||
import Notifications from './Notifications'
|
import Notifications from './Notifications'
|
||||||
import { ProfilePopup } from './ProfilePopup'
|
import { ProfilePopup } from './ProfilePopup'
|
||||||
import Userpic from '../Author/Userpic'
|
import Userpic from '../Author/Userpic'
|
||||||
import type { Author } from '../../graphql/types.gen'
|
|
||||||
import { showModal, useWarningsStore } from '../../stores/ui'
|
import { showModal, useWarningsStore } from '../../stores/ui'
|
||||||
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
|
@ -44,12 +43,24 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
|
||||||
toggleWarnings()
|
toggleWarnings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isEditorPage = createMemo(
|
||||||
|
() => page().route === 'create' || page().route === 'edit' || page().route === 'editSettings'
|
||||||
|
)
|
||||||
|
|
||||||
|
const showNotifications = createMemo(() => isAuthenticated() && !isEditorPage())
|
||||||
|
const showSaveButton = createMemo(() => isAuthenticated() && isEditorPage())
|
||||||
|
const showCreatePostButton = createMemo(() => isAuthenticated() && !isEditorPage())
|
||||||
|
|
||||||
|
const handleBurgerButtonClick = () => {
|
||||||
|
toggleEditorPanel()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ShowOnlyOnClient>
|
<ShowOnlyOnClient>
|
||||||
<Show when={isSessionLoaded()} keyed={true}>
|
<Show when={isSessionLoaded()} keyed={true}>
|
||||||
<div class={clsx(styles.usernav, 'col')}>
|
<div class={clsx(styles.usernav, 'col')}>
|
||||||
<div class={clsx(styles.userControl, styles.userControl, 'col')}>
|
<div class={clsx(styles.userControl, styles.userControl, 'col')}>
|
||||||
<Show when={page().route !== 'create'}>
|
<Show when={showCreatePostButton()}>
|
||||||
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
||||||
<a href={getPagePath(router, 'create')}>
|
<a href={getPagePath(router, 'create')}>
|
||||||
<span class={styles.textLabel}>{t('Create post')}</span>
|
<span class={styles.textLabel}>{t('Create post')}</span>
|
||||||
|
@ -58,7 +69,7 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={isAuthenticated() && page().route !== 'create'}>
|
<Show when={showNotifications()}>
|
||||||
<div class={styles.userControlItem}>
|
<div class={styles.userControlItem}>
|
||||||
<a href="#" onClick={handleBellIconClick}>
|
<a href="#" onClick={handleBellIconClick}>
|
||||||
<div>
|
<div>
|
||||||
|
@ -68,7 +79,7 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={isAuthenticated() && page().route === 'create'}>
|
<Show when={showSaveButton()}>
|
||||||
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
||||||
<Button
|
<Button
|
||||||
value={
|
value={
|
||||||
|
@ -97,7 +108,7 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
|
||||||
<Button
|
<Button
|
||||||
value={<Icon name="burger" />}
|
value={<Icon name="burger" />}
|
||||||
variant={'outline'}
|
variant={'outline'}
|
||||||
onClick={() => toggleEditorPanel()}
|
onClick={handleBurgerButtonClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
@ -136,7 +147,7 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
|
||||||
<div class={styles.userControlItem}>
|
<div class={styles.userControlItem}>
|
||||||
<button class={styles.button}>
|
<button class={styles.button}>
|
||||||
<div classList={{ entered: page().path === `/${session().user?.slug}` }}>
|
<div classList={{ entered: page().path === `/${session().user?.slug}` }}>
|
||||||
<Userpic user={session().user as Author} class={styles.userpic} />
|
<Userpic user={session().user} class={styles.userpic} />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 800px) and (max-width: 991px) {
|
@media (width >= 800px) and (width <= 991px) {
|
||||||
// top: 11em;
|
// top: 11em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,8 @@
|
||||||
&.narrow {
|
&.narrow {
|
||||||
max-width: 460px;
|
max-width: 460px;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
@media (min-width: 800px) and (max-width: 991px) {
|
|
||||||
|
@media (width >= 800px) and (width <= 991px) {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AuthorCard } from '../Author/Card'
|
import { AuthorCard } from '../Author/AuthorCard'
|
||||||
import type { Author } from '../../graphql/types.gen'
|
import type { Author } from '../../graphql/types.gen'
|
||||||
|
|
||||||
import { translit } from '../../utils/ru2en'
|
import { translit } from '../../utils/ru2en'
|
||||||
|
|
|
@ -24,9 +24,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.topicTitle {
|
.topicTitle {
|
||||||
font-weight: bold;
|
|
||||||
@include font-size(2.2rem);
|
@include font-size(2.2rem);
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
margin-bottom: 1.2rem;
|
margin-bottom: 1.2rem;
|
||||||
margin-top: 0.5rem !important;
|
margin-top: 0.5rem !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ export const FullTopic = (props: Props) => {
|
||||||
{t('Unfollow the topic')}
|
{t('Unfollow the topic')}
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
<a href={`/create/${props.topic.slug}`}>{t('Write about the topic')}</a>
|
<a href={`/create/?topicId=${props.topic.id}`}>{t('Write about the topic')}</a>
|
||||||
</div>
|
</div>
|
||||||
<Show when={props.topic.pic}>
|
<Show when={props.topic.pic}>
|
||||||
<img src={props.topic.pic} alt={props.topic.title} />
|
<img src={props.topic.pic} alt={props.topic.title} />
|
||||||
|
|
|
@ -3,7 +3,7 @@ import type { Author } from '../../graphql/types.gen'
|
||||||
|
|
||||||
import { setAuthorsSort, useAuthorsStore } from '../../stores/zine/authors'
|
import { setAuthorsSort, useAuthorsStore } from '../../stores/zine/authors'
|
||||||
import { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
import { AuthorCard } from '../Author/Card'
|
import { AuthorCard } from '../Author/AuthorCard'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
import { translit } from '../../utils/ru2en'
|
import { translit } from '../../utils/ru2en'
|
||||||
|
|
|
@ -14,7 +14,7 @@ import stylesArticle from '../Article/Article.module.scss'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import Userpic from '../Author/Userpic'
|
import Userpic from '../Author/Userpic'
|
||||||
import { Popup } from '../_shared/Popup'
|
import { Popup } from '../_shared/Popup'
|
||||||
import { AuthorCard } from '../Author/Card'
|
import { AuthorCard } from '../Author/AuthorCard'
|
||||||
import { apiClient } from '../../utils/apiClient'
|
import { apiClient } from '../../utils/apiClient'
|
||||||
import { Comment } from '../Article/Comment'
|
import { Comment } from '../Article/Comment'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
|
99
src/components/Views/Edit.module.scss
Normal file
99
src/components/Views/Edit.module.scss
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
:global(.main-content) {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
.articlePreview {
|
||||||
|
border: 2px solid #e8e8e8;
|
||||||
|
min-height: 10em;
|
||||||
|
padding: 1rem 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formHolder {
|
||||||
|
padding: 0 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
.titleInput,
|
||||||
|
.subtitleInput {
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 36px;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
opacity: 0.3;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleInput {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editSettings,
|
||||||
|
.edit {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.asidePanel {
|
||||||
|
background: #1f1f1f;
|
||||||
|
color: rgb(255 255 255 / 35%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 1.7rem;
|
||||||
|
justify-content: flex-start;
|
||||||
|
height: 100%;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: $grid-gutter-width;
|
||||||
|
position: fixed;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.close {
|
||||||
|
filter: invert(1);
|
||||||
|
margin: -1.6rem 0 0 -1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
border-bottom: 2px solid rgb(255 255 255 / 10%);
|
||||||
|
|
||||||
|
// margin-bottom: 1.8rem;
|
||||||
|
margin-top: 1.8rem;
|
||||||
|
padding-bottom: 1.8rem;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0.6em 0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
font-weight: normal;
|
||||||
|
margin-left: -1.6rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgb(255 255 255 / 35%);
|
||||||
|
font-weight: normal !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: none;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,14 @@
|
||||||
import { createSignal, onMount, Show } from 'solid-js'
|
import { createSignal, onMount, Show } from 'solid-js'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import styles from './Create.module.scss'
|
import styles from './Edit.module.scss'
|
||||||
import { Title } from '@solidjs/meta'
|
import { Title } from '@solidjs/meta'
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
import type { Topic } from '../../graphql/types.gen'
|
import type { Shout, Topic } from '../../graphql/types.gen'
|
||||||
import { apiClient } from '../../utils/apiClient'
|
import { apiClient } from '../../utils/apiClient'
|
||||||
import { TopicSelect } from '../Editor/TopicSelect/TopicSelect'
|
import { TopicSelect } from '../Editor/TopicSelect/TopicSelect'
|
||||||
import { router, useRouter } from '../../stores/router'
|
import { router, useRouter } from '../../stores/router'
|
||||||
import { getPagePath } from '@nanostores/router'
|
import { openPage } from '@nanostores/router'
|
||||||
import { translit } from '../../utils/ru2en'
|
import { translit } from '../../utils/ru2en'
|
||||||
import { Editor } from '../Editor/Editor'
|
import { Editor } from '../Editor/Editor'
|
||||||
import { Panel } from '../Editor/Panel'
|
import { Panel } from '../Editor/Panel'
|
||||||
|
@ -18,12 +18,16 @@ type ShoutForm = {
|
||||||
title: string
|
title: string
|
||||||
subtitle: string
|
subtitle: string
|
||||||
selectedTopics: Topic[]
|
selectedTopics: Topic[]
|
||||||
mainTopic: Topic
|
mainTopic: string
|
||||||
body: string
|
body: string
|
||||||
coverImageUrl: string
|
coverImageUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreateView = () => {
|
type EditViewProps = {
|
||||||
|
shout: Shout
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditView = (props: EditViewProps) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
|
||||||
const [topics, setTopics] = createSignal<Topic[]>(null)
|
const [topics, setTopics] = createSignal<Topic[]>(null)
|
||||||
|
@ -32,13 +36,13 @@ export const CreateView = () => {
|
||||||
const [isSlugChanged, setIsSlugChanged] = createSignal(false)
|
const [isSlugChanged, setIsSlugChanged] = createSignal(false)
|
||||||
|
|
||||||
const [form, setForm] = createStore<ShoutForm>({
|
const [form, setForm] = createStore<ShoutForm>({
|
||||||
slug: '',
|
slug: props.shout.slug,
|
||||||
title: '',
|
title: props.shout.title,
|
||||||
subtitle: '',
|
subtitle: props.shout.subtitle,
|
||||||
selectedTopics: [],
|
selectedTopics: props.shout.topics,
|
||||||
mainTopic: null,
|
mainTopic: props.shout.mainTopic,
|
||||||
body: '',
|
body: props.shout.body,
|
||||||
coverImageUrl: ''
|
coverImageUrl: props.shout.cover
|
||||||
})
|
})
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
@ -49,18 +53,9 @@ export const CreateView = () => {
|
||||||
const handleFormSubmit = async (e) => {
|
const handleFormSubmit = async (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
const newShout = await apiClient.createArticle({
|
const article = await apiClient.publishDraft()
|
||||||
article: {
|
|
||||||
slug: form.slug,
|
|
||||||
title: form.title,
|
|
||||||
subtitle: form.subtitle,
|
|
||||||
body: form.body,
|
|
||||||
topics: form.selectedTopics.map((topic) => topic.slug),
|
|
||||||
mainTopic: form.selectedTopics[0].slug
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
router.open(getPagePath(router, 'article', { slug: newShout.slug }))
|
openPage(router, 'article', { slug: article.slug })
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTitleInputChange = (e) => {
|
const handleTitleInputChange = (e) => {
|
||||||
|
@ -82,17 +77,36 @@ export const CreateView = () => {
|
||||||
setForm('slug', slug)
|
setForm('slug', slug)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSaveButtonClick = async (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
await apiClient.updateArticle({
|
||||||
|
slug: props.shout.slug,
|
||||||
|
article: {
|
||||||
|
slug: form.slug,
|
||||||
|
title: form.title,
|
||||||
|
subtitle: form.subtitle,
|
||||||
|
body: form.body,
|
||||||
|
topics: form.selectedTopics.map((topic) => topic.slug),
|
||||||
|
mainTopic: form.selectedTopics[0].slug
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
openPage(router, 'drafts')
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class={styles.container}>
|
<div class={styles.container}>
|
||||||
<Title>{t('Write an article')}</Title>
|
<Title>{t('Write an article')}</Title>
|
||||||
|
|
||||||
<form onSubmit={handleFormSubmit}>
|
<form onSubmit={handleFormSubmit}>
|
||||||
<div class="wide-container">
|
<div class="wide-container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-19 col-lg-18 col-xl-16 offset-md-5">
|
<div class="col-md-19 col-lg-18 col-xl-16 offset-md-5">
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.create, {
|
class={clsx(styles.edit, {
|
||||||
[styles.visible]: page().route === 'create'
|
[styles.visible]: page().route === 'edit'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
@ -115,16 +129,15 @@ export const CreateView = () => {
|
||||||
onChange={(e) => setForm('subtitle', e.currentTarget.value)}
|
onChange={(e) => setForm('subtitle', e.currentTarget.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Editor shoutId={42} onChange={(body) => setForm('body', body)} />
|
<Editor
|
||||||
|
shoutSlug={props.shout.slug}
|
||||||
<div class={styles.saveBlock}>
|
initialContent={props.shout.body}
|
||||||
{/*<button class={clsx('button button--outline', styles.button)}>Сохранить</button>*/}
|
onChange={(body) => setForm('body', body)}
|
||||||
<a href={getPagePath(router, 'createSettings')}>Настройки</a>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.createSettings, {
|
class={clsx(styles.editSettings, {
|
||||||
[styles.visible]: page().route === 'createSettings'
|
[styles.visible]: page().route === 'editSettings'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<h1>Настройки публикации</h1>
|
<h1>Настройки публикации</h1>
|
||||||
|
@ -199,27 +212,15 @@ export const CreateView = () => {
|
||||||
выглядеть на главной странице
|
выглядеть на главной странице
|
||||||
</p>
|
</p>
|
||||||
<div class={styles.articlePreview} />
|
<div class={styles.articlePreview} />
|
||||||
|
|
||||||
<div class={styles.saveBlock}>
|
|
||||||
<p>
|
|
||||||
Проверьте ещё раз введённые данные, если всё верно, вы можете сохранить или
|
|
||||||
опубликовать ваш текст
|
|
||||||
</p>
|
|
||||||
{/*<button class={clsx('button button--outline', styles.button)}>Сохранить</button>*/}
|
|
||||||
<a href={getPagePath(router, 'create')}>Назад</a>
|
|
||||||
<button type="submit" class={clsx('button button--submit', styles.button)}>
|
|
||||||
Опубликовать
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<Panel />
|
<Panel shoutSlug={props.shout.slug} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CreateView
|
export default EditView
|
|
@ -1,5 +1,6 @@
|
||||||
.feedNavigation {
|
.feedNavigation {
|
||||||
@include font-size(1.5rem);
|
@include font-size(1.5rem);
|
||||||
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { createEffect, createMemo, createSignal, For, onMount, Show } from 'solid-js'
|
import { createEffect, createSignal, For, onMount, Show } from 'solid-js'
|
||||||
import '../../styles/Feed.scss'
|
import '../../styles/Feed.scss'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { ArticleCard } from '../Feed/Card'
|
import { ArticleCard } from '../Feed/ArticleCard'
|
||||||
import { AuthorCard } from '../Author/Card'
|
import { AuthorCard } from '../Author/AuthorCard'
|
||||||
import { Sidebar } from '../Feed/Sidebar'
|
import { Sidebar } from '../Feed/Sidebar'
|
||||||
import { loadShouts, useArticlesStore } from '../../stores/zine/articles'
|
import { loadShouts, useArticlesStore } from '../../stores/zine/articles'
|
||||||
import { useAuthorsStore } from '../../stores/zine/authors'
|
import { useAuthorsStore } from '../../stores/zine/authors'
|
||||||
|
|
|
@ -22,7 +22,7 @@ import {
|
||||||
import { useTopAuthorsStore } from '../../stores/zine/topAuthors'
|
import { useTopAuthorsStore } from '../../stores/zine/topAuthors'
|
||||||
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
||||||
import { splitToPages } from '../../utils/splitToPages'
|
import { splitToPages } from '../../utils/splitToPages'
|
||||||
import { ArticleCard } from '../Feed/Card'
|
import { ArticleCard } from '../Feed/ArticleCard'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
|
||||||
type HomeProps = {
|
type HomeProps = {
|
||||||
|
|
|
@ -23,12 +23,14 @@ type InboxSearchParams = {
|
||||||
initChat: string
|
initChat: string
|
||||||
chat: string
|
chat: string
|
||||||
}
|
}
|
||||||
// const userSearch = (array: Author[], keyword: string) => {
|
|
||||||
// const searchTerm = keyword.toLowerCase()
|
const userSearch = (array: Author[], keyword: string) => {
|
||||||
// return array.filter((value) => {
|
return array.filter((value) => new RegExp(keyword.trim(), 'gi').test(value.name))
|
||||||
// return value.name.toLowerCase().match(new RegExp(searchTerm, 'g'))
|
}
|
||||||
// })
|
|
||||||
// }
|
const handleOpenInviteModal = () => {
|
||||||
|
showModal('inviteToChat')
|
||||||
|
}
|
||||||
|
|
||||||
export const InboxView = () => {
|
export const InboxView = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
@ -46,14 +48,15 @@ export const InboxView = () => {
|
||||||
const [messageToReply, setMessageToReply] = createSignal<MessageType | null>(null)
|
const [messageToReply, setMessageToReply] = createSignal<MessageType | null>(null)
|
||||||
const { session } = useSession()
|
const { session } = useSession()
|
||||||
const currentUserId = createMemo(() => session()?.user.id)
|
const currentUserId = createMemo(() => session()?.user.id)
|
||||||
|
|
||||||
// Поиск по диалогам
|
// Поиск по диалогам
|
||||||
const getQuery = (query) => {
|
const getQuery = (query) => {
|
||||||
// if (query().length >= 2) {
|
if (query().length >= 2) {
|
||||||
// const match = userSearch(recipients(), query())
|
const match = userSearch(recipients(), query())
|
||||||
// setRecipients(match)
|
setRecipients(match)
|
||||||
// } else {
|
} else {
|
||||||
// setRecipients(cashedRecipients())
|
// setRecipients(cashedRecipients())
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let chatWindow
|
let chatWindow
|
||||||
|
@ -136,10 +139,6 @@ export const InboxView = () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleOpenInviteModal = () => {
|
|
||||||
showModal('inviteToChat')
|
|
||||||
}
|
|
||||||
|
|
||||||
const chatsToShow = () => {
|
const chatsToShow = () => {
|
||||||
const sorted = chats().sort((a, b) => {
|
const sorted = chats().sort((a, b) => {
|
||||||
return b.updatedAt - a.updatedAt
|
return b.updatedAt - a.updatedAt
|
||||||
|
@ -158,7 +157,10 @@ export const InboxView = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyDown = async (event) => {
|
const handleKeyDown = async (event) => {
|
||||||
if (event.keyCode === 13 && event.shiftKey) return
|
if (event.keyCode === 13 && event.shiftKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (event.keyCode === 13 && !event.shiftKey && postMessageText()?.trim().length > 0) {
|
if (event.keyCode === 13 && !event.shiftKey && postMessageText()?.trim().length > 0) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
handleSubmit()
|
handleSubmit()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Show, For, createSignal } from 'solid-js'
|
import { Show, For, createSignal } from 'solid-js'
|
||||||
import '../../styles/Search.scss'
|
import '../../styles/Search.scss'
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
import { ArticleCard } from '../Feed/Card'
|
import { ArticleCard } from '../Feed/ArticleCard'
|
||||||
|
|
||||||
import { loadShouts, useArticlesStore } from '../../stores/zine/articles'
|
import { loadShouts, useArticlesStore } from '../../stores/zine/articles'
|
||||||
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
import { restoreScrollPosition, saveScrollPosition } from '../../utils/scroll'
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { splitToPages } from '../../utils/splitToPages'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import Slider from '../_shared/Slider'
|
import Slider from '../_shared/Slider'
|
||||||
import { Row1 } from '../Feed/Row1'
|
import { Row1 } from '../Feed/Row1'
|
||||||
import { ArticleCard } from '../Feed/Card'
|
import { ArticleCard } from '../Feed/ArticleCard'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
|
|
||||||
type TopicsPageSearchParams = {
|
type TopicsPageSearchParams = {
|
||||||
|
|
|
@ -5,16 +5,15 @@
|
||||||
max-height: 360px;
|
max-height: 360px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-flow: row nowrap;
|
||||||
flex-wrap: nowrap;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.user {
|
.user {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-flow: row nowrap;
|
||||||
flex-wrap: nowrap;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-right: 1.2rem;
|
margin-right: 1.2rem;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { Accessor, createContext, createSignal, useContext } from 'solid-js'
|
||||||
type WordCounter = {
|
type WordCounter = {
|
||||||
characters: number
|
characters: number
|
||||||
words: number
|
words: number
|
||||||
paragraphs?: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type EditorContextType = {
|
type EditorContextType = {
|
||||||
|
@ -23,12 +22,12 @@ export function useEditorContext() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditorProvider = (props: { children: JSX.Element }) => {
|
export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
const [isEditorPanelVisible, setEditorPanelVisible] = createSignal<boolean>(false)
|
const [isEditorPanelVisible, setIsEditorPanelVisible] = createSignal<boolean>(false)
|
||||||
const [wordCounter, setWordCounter] = createSignal<WordCounter>({
|
const [wordCounter, setWordCounter] = createSignal<WordCounter>({
|
||||||
characters: 0,
|
characters: 0,
|
||||||
words: 0
|
words: 0
|
||||||
})
|
})
|
||||||
const toggleEditorPanel = () => setEditorPanelVisible(!isEditorPanelVisible())
|
const toggleEditorPanel = () => setIsEditorPanelVisible((value) => !value)
|
||||||
const countWords = (value) => setWordCounter(value)
|
const countWords = (value) => setWordCounter(value)
|
||||||
const actions = {
|
const actions = {
|
||||||
toggleEditorPanel,
|
toggleEditorPanel,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { i18n } from 'i18next'
|
||||||
import type { Accessor, JSX } from 'solid-js'
|
import type { Accessor, JSX } from 'solid-js'
|
||||||
import { createContext, createEffect, createSignal, Show, useContext } from 'solid-js'
|
import { createContext, createEffect, createSignal, Show, useContext } from 'solid-js'
|
||||||
import { useRouter } from '../stores/router'
|
import { useRouter } from '../stores/router'
|
||||||
import i18next from 'i18next'
|
import i18next, { changeLanguage, t } from 'i18next'
|
||||||
import Cookie from 'js-cookie'
|
import Cookie from 'js-cookie'
|
||||||
|
|
||||||
type LocalizeContextType = {
|
type LocalizeContextType = {
|
||||||
|
@ -30,13 +30,13 @@ export const LocalizeProvider = (props: { children: JSX.Element }) => {
|
||||||
|
|
||||||
const lng: Language = searchParams().lng === 'en' ? 'en' : 'ru'
|
const lng: Language = searchParams().lng === 'en' ? 'en' : 'ru'
|
||||||
|
|
||||||
i18next.changeLanguage(lng)
|
changeLanguage(lng)
|
||||||
setLang(lng)
|
setLang(lng)
|
||||||
Cookie.set('lng', lng)
|
Cookie.set('lng', lng)
|
||||||
changeSearchParam('lng', null)
|
changeSearchParam('lng', null)
|
||||||
})
|
})
|
||||||
|
|
||||||
const value: LocalizeContextType = { t: i18next.t, lang, setLang }
|
const value: LocalizeContextType = { t, lang, setLang }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocalizeContext.Provider value={value}>
|
<LocalizeContext.Provider value={value}>
|
||||||
|
|
|
@ -54,7 +54,6 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
|
||||||
initialValue: null
|
initialValue: null
|
||||||
})
|
})
|
||||||
|
|
||||||
const userSlug = createMemo(() => session()?.user?.slug)
|
|
||||||
const user = createMemo(() => session()?.user)
|
const user = createMemo(() => session()?.user)
|
||||||
|
|
||||||
const isAuthenticated = createMemo(() => Boolean(session()?.user?.slug))
|
const isAuthenticated = createMemo(() => Boolean(session()?.user?.slug))
|
||||||
|
|
|
@ -5,23 +5,11 @@ export default gql`
|
||||||
createShout(inp: $shout) {
|
createShout(inp: $shout) {
|
||||||
error
|
error
|
||||||
shout {
|
shout {
|
||||||
id
|
_id: slug
|
||||||
slug
|
slug
|
||||||
title
|
title
|
||||||
subtitle
|
subtitle
|
||||||
body
|
body
|
||||||
topics {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
authors {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
userpic
|
|
||||||
caption
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
16
src/graphql/mutation/article-publish.ts
Normal file
16
src/graphql/mutation/article-publish.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { gql } from '@urql/core'
|
||||||
|
|
||||||
|
export default gql`
|
||||||
|
mutation UpdateShoutMutation($slug: String!) {
|
||||||
|
publishShout(slug: $slug) {
|
||||||
|
error
|
||||||
|
shout {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
title
|
||||||
|
subtitle
|
||||||
|
body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
|
@ -1,29 +1,15 @@
|
||||||
import { gql } from '@urql/core'
|
import { gql } from '@urql/core'
|
||||||
|
|
||||||
export default gql`
|
export default gql`
|
||||||
mutation UpdateShoutMutation($shout: Shout!) {
|
mutation UpdateShoutMutation($slug: String!, $shout: ShoutInput!) {
|
||||||
updateShout(input: $shout) {
|
updateShout(slug: $slug, inp: $shout) {
|
||||||
error
|
error
|
||||||
shout {
|
shout {
|
||||||
_id: slug
|
id
|
||||||
slug
|
slug
|
||||||
title
|
title
|
||||||
subtitle
|
subtitle
|
||||||
image
|
|
||||||
body
|
body
|
||||||
topics {
|
|
||||||
# id
|
|
||||||
title
|
|
||||||
slug
|
|
||||||
image
|
|
||||||
}
|
|
||||||
authors {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
userpic
|
|
||||||
caption
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
mutation DraftCreateMutation($draft: DraftInput!) {
|
|
||||||
createDraft(draft: $draft) {
|
|
||||||
error
|
|
||||||
draft {
|
|
||||||
id
|
|
||||||
slug
|
|
||||||
title
|
|
||||||
subtitle
|
|
||||||
body
|
|
||||||
topics {
|
|
||||||
# id
|
|
||||||
title
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
authors {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
userpic
|
|
||||||
caption
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
mutation DraftDestroyMutation($draft: Int!) {
|
|
||||||
deleteDraft(draft: $draft) {
|
|
||||||
error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
mutation ShoutFromDraftMutation($draft: Int!) {
|
|
||||||
draftToShout(draft: $draft) {
|
|
||||||
error
|
|
||||||
shout {
|
|
||||||
_id: slug
|
|
||||||
slug
|
|
||||||
title
|
|
||||||
subtitle
|
|
||||||
body
|
|
||||||
topics {
|
|
||||||
# id
|
|
||||||
title
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
authors {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
userpic
|
|
||||||
caption
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
mutation DraftUpdateMutation($draft: DraftInput!) {
|
|
||||||
updateDraft(draft: $draft) {
|
|
||||||
error
|
|
||||||
draft {
|
|
||||||
id
|
|
||||||
slug
|
|
||||||
title
|
|
||||||
subtitle
|
|
||||||
body
|
|
||||||
topics {
|
|
||||||
# id
|
|
||||||
title
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
authors {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
userpic
|
|
||||||
caption
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
36
src/graphql/query/article-drafts-load-by.ts
Normal file
36
src/graphql/query/article-drafts-load-by.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { gql } from '@urql/core'
|
||||||
|
|
||||||
|
export default gql`
|
||||||
|
query LoadDraftsQuery($options: LoadShoutsOptions) {
|
||||||
|
loadDrafts(options: $options) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
subtitle
|
||||||
|
slug
|
||||||
|
layout
|
||||||
|
cover
|
||||||
|
# community
|
||||||
|
mainTopic
|
||||||
|
topics {
|
||||||
|
# id
|
||||||
|
title
|
||||||
|
body
|
||||||
|
slug
|
||||||
|
stat {
|
||||||
|
_id: shouts
|
||||||
|
shouts
|
||||||
|
authors
|
||||||
|
followers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authors {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
userpic
|
||||||
|
}
|
||||||
|
createdAt
|
||||||
|
publishedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
|
@ -1,17 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
query MyDraftsQuery {
|
|
||||||
loadDrafts {
|
|
||||||
authors {
|
|
||||||
id
|
|
||||||
slug
|
|
||||||
name
|
|
||||||
pic
|
|
||||||
}
|
|
||||||
createdAt
|
|
||||||
body
|
|
||||||
title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -105,30 +105,6 @@ export type Community = {
|
||||||
slug: Scalars['String']
|
slug: Scalars['String']
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DraftCollab = {
|
|
||||||
authors: Array<Maybe<Scalars['Int']>>
|
|
||||||
body?: Maybe<Scalars['String']>
|
|
||||||
chat?: Maybe<Chat>
|
|
||||||
cover?: Maybe<Scalars['String']>
|
|
||||||
createdAt: Scalars['Int']
|
|
||||||
layout?: Maybe<Scalars['String']>
|
|
||||||
slug?: Maybe<Scalars['String']>
|
|
||||||
subtitle?: Maybe<Scalars['String']>
|
|
||||||
title?: Maybe<Scalars['String']>
|
|
||||||
topics?: Maybe<Array<Maybe<Scalars['String']>>>
|
|
||||||
updatedAt?: Maybe<Scalars['Int']>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DraftInput = {
|
|
||||||
authors?: InputMaybe<Array<InputMaybe<Scalars['Int']>>>
|
|
||||||
body?: InputMaybe<Scalars['String']>
|
|
||||||
cover?: InputMaybe<Scalars['String']>
|
|
||||||
slug?: InputMaybe<Scalars['String']>
|
|
||||||
subtitle?: InputMaybe<Scalars['String']>
|
|
||||||
title?: InputMaybe<Scalars['String']>
|
|
||||||
topics?: InputMaybe<Array<InputMaybe<Scalars['Int']>>>
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum FollowingEntity {
|
export enum FollowingEntity {
|
||||||
Author = 'AUTHOR',
|
Author = 'AUTHOR',
|
||||||
Community = 'COMMUNITY',
|
Community = 'COMMUNITY',
|
||||||
|
@ -185,29 +161,24 @@ export type MessagesBy = {
|
||||||
export type Mutation = {
|
export type Mutation = {
|
||||||
confirmEmail: AuthResult
|
confirmEmail: AuthResult
|
||||||
createChat: Result
|
createChat: Result
|
||||||
createDraft: Result
|
|
||||||
createMessage: Result
|
createMessage: Result
|
||||||
createReaction: Result
|
createReaction: Result
|
||||||
createShout: Result
|
createShout: Result
|
||||||
createTopic: Result
|
createTopic: Result
|
||||||
deleteChat: Result
|
deleteChat: Result
|
||||||
deleteDraft: Result
|
|
||||||
deleteMessage: Result
|
deleteMessage: Result
|
||||||
deleteReaction: Result
|
deleteReaction: Result
|
||||||
deleteShout: Result
|
deleteShout: Result
|
||||||
destroyTopic: Result
|
destroyTopic: Result
|
||||||
draftToShout: Result
|
|
||||||
follow: Result
|
follow: Result
|
||||||
getSession: AuthResult
|
getSession: AuthResult
|
||||||
inviteAccept: Result
|
|
||||||
inviteAuthor: Result
|
|
||||||
markAsRead: Result
|
markAsRead: Result
|
||||||
|
publishShout: Result
|
||||||
rateUser: Result
|
rateUser: Result
|
||||||
registerUser: AuthResult
|
registerUser: AuthResult
|
||||||
sendLink: Result
|
sendLink: Result
|
||||||
unfollow: Result
|
unfollow: Result
|
||||||
updateChat: Result
|
updateChat: Result
|
||||||
updateDraft: Result
|
|
||||||
updateMessage: Result
|
updateMessage: Result
|
||||||
updateOnlineStatus: Result
|
updateOnlineStatus: Result
|
||||||
updateProfile: Result
|
updateProfile: Result
|
||||||
|
@ -225,10 +196,6 @@ export type MutationCreateChatArgs = {
|
||||||
title?: InputMaybe<Scalars['String']>
|
title?: InputMaybe<Scalars['String']>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MutationCreateDraftArgs = {
|
|
||||||
draft: DraftInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MutationCreateMessageArgs = {
|
export type MutationCreateMessageArgs = {
|
||||||
body: Scalars['String']
|
body: Scalars['String']
|
||||||
chat: Scalars['String']
|
chat: Scalars['String']
|
||||||
|
@ -251,10 +218,6 @@ export type MutationDeleteChatArgs = {
|
||||||
chatId: Scalars['String']
|
chatId: Scalars['String']
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MutationDeleteDraftArgs = {
|
|
||||||
draft: Scalars['Int']
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MutationDeleteMessageArgs = {
|
export type MutationDeleteMessageArgs = {
|
||||||
chatId: Scalars['String']
|
chatId: Scalars['String']
|
||||||
id: Scalars['Int']
|
id: Scalars['Int']
|
||||||
|
@ -272,29 +235,21 @@ export type MutationDestroyTopicArgs = {
|
||||||
slug: Scalars['String']
|
slug: Scalars['String']
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MutationDraftToShoutArgs = {
|
|
||||||
draft: Scalars['Int']
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MutationFollowArgs = {
|
export type MutationFollowArgs = {
|
||||||
slug: Scalars['String']
|
slug: Scalars['String']
|
||||||
what: FollowingEntity
|
what: FollowingEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MutationInviteAcceptArgs = {
|
|
||||||
draft: Scalars['Int']
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MutationInviteAuthorArgs = {
|
|
||||||
author: Scalars['Int']
|
|
||||||
draft: Scalars['Int']
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MutationMarkAsReadArgs = {
|
export type MutationMarkAsReadArgs = {
|
||||||
chatId: Scalars['String']
|
chatId: Scalars['String']
|
||||||
ids: Array<InputMaybe<Scalars['Int']>>
|
ids: Array<InputMaybe<Scalars['Int']>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MutationPublishShoutArgs = {
|
||||||
|
inp: ShoutInput
|
||||||
|
slug: Scalars['String']
|
||||||
|
}
|
||||||
|
|
||||||
export type MutationRateUserArgs = {
|
export type MutationRateUserArgs = {
|
||||||
slug: Scalars['String']
|
slug: Scalars['String']
|
||||||
value: Scalars['Int']
|
value: Scalars['Int']
|
||||||
|
@ -321,10 +276,6 @@ export type MutationUpdateChatArgs = {
|
||||||
chat: ChatInput
|
chat: ChatInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MutationUpdateDraftArgs = {
|
|
||||||
draft: DraftInput
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MutationUpdateMessageArgs = {
|
export type MutationUpdateMessageArgs = {
|
||||||
body: Scalars['String']
|
body: Scalars['String']
|
||||||
chatId: Scalars['String']
|
chatId: Scalars['String']
|
||||||
|
@ -342,6 +293,7 @@ export type MutationUpdateReactionArgs = {
|
||||||
|
|
||||||
export type MutationUpdateShoutArgs = {
|
export type MutationUpdateShoutArgs = {
|
||||||
inp: ShoutInput
|
inp: ShoutInput
|
||||||
|
slug: Scalars['String']
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MutationUpdateTopicArgs = {
|
export type MutationUpdateTopicArgs = {
|
||||||
|
@ -380,7 +332,7 @@ export type Query = {
|
||||||
isEmailUsed: Scalars['Boolean']
|
isEmailUsed: Scalars['Boolean']
|
||||||
loadAuthorsBy: Array<Maybe<Author>>
|
loadAuthorsBy: Array<Maybe<Author>>
|
||||||
loadChats: Result
|
loadChats: Result
|
||||||
loadDrafts: Array<Maybe<DraftCollab>>
|
loadDrafts: Array<Maybe<Shout>>
|
||||||
loadMessagesBy: Result
|
loadMessagesBy: Result
|
||||||
loadReactionsBy: Array<Maybe<Reaction>>
|
loadReactionsBy: Array<Maybe<Reaction>>
|
||||||
loadRecipients: Result
|
loadRecipients: Result
|
||||||
|
@ -388,6 +340,7 @@ export type Query = {
|
||||||
loadShouts: Array<Maybe<Shout>>
|
loadShouts: Array<Maybe<Shout>>
|
||||||
markdownBody: Scalars['String']
|
markdownBody: Scalars['String']
|
||||||
myFeed?: Maybe<Array<Maybe<Shout>>>
|
myFeed?: Maybe<Array<Maybe<Shout>>>
|
||||||
|
publishShout: Array<Maybe<Shout>>
|
||||||
searchMessages: Result
|
searchMessages: Result
|
||||||
searchRecipients: Result
|
searchRecipients: Result
|
||||||
signIn: AuthResult
|
signIn: AuthResult
|
||||||
|
@ -424,6 +377,10 @@ export type QueryLoadChatsArgs = {
|
||||||
offset?: InputMaybe<Scalars['Int']>
|
offset?: InputMaybe<Scalars['Int']>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type QueryLoadDraftsArgs = {
|
||||||
|
options?: InputMaybe<LoadShoutsOptions>
|
||||||
|
}
|
||||||
|
|
||||||
export type QueryLoadMessagesByArgs = {
|
export type QueryLoadMessagesByArgs = {
|
||||||
by: MessagesBy
|
by: MessagesBy
|
||||||
limit?: InputMaybe<Scalars['Int']>
|
limit?: InputMaybe<Scalars['Int']>
|
||||||
|
@ -457,6 +414,10 @@ export type QueryMyFeedArgs = {
|
||||||
options?: InputMaybe<LoadShoutsOptions>
|
options?: InputMaybe<LoadShoutsOptions>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type QueryPublishShoutArgs = {
|
||||||
|
slug: Scalars['String']
|
||||||
|
}
|
||||||
|
|
||||||
export type QuerySearchMessagesArgs = {
|
export type QuerySearchMessagesArgs = {
|
||||||
by: MessagesBy
|
by: MessagesBy
|
||||||
limit?: InputMaybe<Scalars['Int']>
|
limit?: InputMaybe<Scalars['Int']>
|
||||||
|
@ -583,8 +544,6 @@ export type Result = {
|
||||||
chats?: Maybe<Array<Maybe<Chat>>>
|
chats?: Maybe<Array<Maybe<Chat>>>
|
||||||
communities?: Maybe<Array<Maybe<Community>>>
|
communities?: Maybe<Array<Maybe<Community>>>
|
||||||
community?: Maybe<Community>
|
community?: Maybe<Community>
|
||||||
draft?: Maybe<DraftCollab>
|
|
||||||
drafts?: Maybe<Array<Maybe<DraftCollab>>>
|
|
||||||
error?: Maybe<Scalars['String']>
|
error?: Maybe<Scalars['String']>
|
||||||
members?: Maybe<Array<Maybe<ChatMember>>>
|
members?: Maybe<Array<Maybe<ChatMember>>>
|
||||||
message?: Maybe<Message>
|
message?: Maybe<Message>
|
||||||
|
@ -596,7 +555,6 @@ export type Result = {
|
||||||
slugs?: Maybe<Array<Maybe<Scalars['String']>>>
|
slugs?: Maybe<Array<Maybe<Scalars['String']>>>
|
||||||
topic?: Maybe<Topic>
|
topic?: Maybe<Topic>
|
||||||
topics?: Maybe<Array<Maybe<Topic>>>
|
topics?: Maybe<Array<Maybe<Topic>>>
|
||||||
uids?: Maybe<Array<Maybe<Scalars['String']>>>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Role = {
|
export type Role = {
|
||||||
|
@ -634,7 +592,7 @@ export type Shout = {
|
||||||
|
|
||||||
export type ShoutInput = {
|
export type ShoutInput = {
|
||||||
authors?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
|
authors?: InputMaybe<Array<InputMaybe<Scalars['String']>>>
|
||||||
body: Scalars['String']
|
body?: InputMaybe<Scalars['String']>
|
||||||
community?: InputMaybe<Scalars['Int']>
|
community?: InputMaybe<Scalars['Int']>
|
||||||
mainTopic?: InputMaybe<Scalars['String']>
|
mainTopic?: InputMaybe<Scalars['String']>
|
||||||
slug?: InputMaybe<Scalars['String']>
|
slug?: InputMaybe<Scalars['String']>
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
import { lazy, Show, Suspense } from 'solid-js'
|
|
||||||
import { PageLayout } from '../components/_shared/PageLayout'
|
import { PageLayout } from '../components/_shared/PageLayout'
|
||||||
import { Loading } from '../components/_shared/Loading'
|
import { Loading } from '../components/_shared/Loading'
|
||||||
import { useSession } from '../context/session'
|
import { onMount } from 'solid-js'
|
||||||
|
import { apiClient } from '../utils/apiClient'
|
||||||
const CreateView = lazy(() => import('../components/Views/Create'))
|
import { router } from '../stores/router'
|
||||||
|
import { redirectPage } from '@nanostores/router'
|
||||||
|
|
||||||
export const CreatePage = () => {
|
export const CreatePage = () => {
|
||||||
const { isAuthenticated, isSessionLoaded } = useSession()
|
onMount(async () => {
|
||||||
|
const shout = await apiClient.createArticle({ article: {} })
|
||||||
|
redirectPage(router, 'edit', { shoutSlug: shout.slug })
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageLayout>
|
<PageLayout>
|
||||||
<Show when={isSessionLoaded()}>
|
<Loading />
|
||||||
<Show when={isAuthenticated()} fallback="Давайте авторизуемся">
|
|
||||||
<Suspense fallback={<Loading />}>
|
|
||||||
<CreateView />
|
|
||||||
</Suspense>
|
|
||||||
</Show>
|
|
||||||
</Show>
|
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ROUTES } from '../stores/router'
|
import { ROUTES } from '../stores/router'
|
||||||
import { getServerRoute } from '../utils/getServerRoute'
|
import { getServerRoute } from '../utils/getServerRoute'
|
||||||
|
|
||||||
export default getServerRoute(ROUTES.createSettings)
|
export default getServerRoute(ROUTES.drafts)
|
34
src/pages/drafts.page.tsx
Normal file
34
src/pages/drafts.page.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { createSignal, For, onMount, Show } from 'solid-js'
|
||||||
|
import { PageLayout } from '../components/_shared/PageLayout'
|
||||||
|
import { useSession } from '../context/session'
|
||||||
|
import { Shout } from '../graphql/types.gen'
|
||||||
|
import { apiClient } from '../utils/apiClient'
|
||||||
|
|
||||||
|
export const DraftsPage = () => {
|
||||||
|
const { isAuthenticated, isSessionLoaded, user } = useSession()
|
||||||
|
|
||||||
|
const [drafts, setDrafts] = createSignal<Shout[]>([])
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const loadedDrafts = await apiClient.getShouts({
|
||||||
|
filters: {
|
||||||
|
author: user().slug,
|
||||||
|
visibility: 'owner'
|
||||||
|
},
|
||||||
|
limit: 9999
|
||||||
|
})
|
||||||
|
setDrafts(loadedDrafts)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageLayout>
|
||||||
|
<Show when={isSessionLoaded()}>
|
||||||
|
<Show when={isAuthenticated()} fallback="Давайте авторизуемся">
|
||||||
|
<For each={drafts()}>{(draft) => <div>{draft.title}</div>}</For>
|
||||||
|
</Show>
|
||||||
|
</Show>
|
||||||
|
</PageLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Page = DraftsPage
|
4
src/pages/edit.page.route.ts
Normal file
4
src/pages/edit.page.route.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { ROUTES } from '../stores/router'
|
||||||
|
import { getServerRoute } from '../utils/getServerRoute'
|
||||||
|
|
||||||
|
export default getServerRoute(ROUTES.edit)
|
40
src/pages/edit.page.tsx
Normal file
40
src/pages/edit.page.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { createMemo, createSignal, lazy, onMount, Show, Suspense } from 'solid-js'
|
||||||
|
import { PageLayout } from '../components/_shared/PageLayout'
|
||||||
|
import { Loading } from '../components/_shared/Loading'
|
||||||
|
import { useSession } from '../context/session'
|
||||||
|
import { Shout } from '../graphql/types.gen'
|
||||||
|
import { useRouter } from '../stores/router'
|
||||||
|
import { apiClient } from '../utils/apiClient'
|
||||||
|
|
||||||
|
const EditView = lazy(() => import('../components/Views/Edit'))
|
||||||
|
|
||||||
|
export const EditPage = () => {
|
||||||
|
const { isAuthenticated, isSessionLoaded } = useSession()
|
||||||
|
|
||||||
|
const { page } = useRouter()
|
||||||
|
|
||||||
|
const shoutSlug = createMemo(() => (page().params as Record<'shoutSlug', string>).shoutSlug)
|
||||||
|
|
||||||
|
const [shout, setShout] = createSignal<Shout>(null)
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const loadedShout = await apiClient.getShout(shoutSlug())
|
||||||
|
setShout(loadedShout)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageLayout>
|
||||||
|
<Show when={isSessionLoaded()}>
|
||||||
|
<Show when={isAuthenticated()} fallback="Давайте авторизуемся">
|
||||||
|
<Show when={shout()}>
|
||||||
|
<Suspense fallback={<Loading />}>
|
||||||
|
<EditView shout={shout()} />
|
||||||
|
</Suspense>
|
||||||
|
</Show>
|
||||||
|
</Show>
|
||||||
|
</Show>
|
||||||
|
</PageLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Page = EditPage
|
4
src/pages/editSettings.page.route.ts
Normal file
4
src/pages/editSettings.page.route.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { ROUTES } from '../stores/router'
|
||||||
|
import { getServerRoute } from '../utils/getServerRoute'
|
||||||
|
|
||||||
|
export default getServerRoute(ROUTES.editSettings)
|
|
@ -16,7 +16,7 @@ import { Beside } from '../components/Feed/Beside'
|
||||||
import Slider from '../components/_shared/Slider'
|
import Slider from '../components/_shared/Slider'
|
||||||
import { Row1 } from '../components/Feed/Row1'
|
import { Row1 } from '../components/Feed/Row1'
|
||||||
import styles from '../styles/Topic.module.scss'
|
import styles from '../styles/Topic.module.scss'
|
||||||
import { ArticleCard } from '../components/Feed/Card'
|
import { ArticleCard } from '../components/Feed/ArticleCard'
|
||||||
import { useLocalize } from '../context/localize'
|
import { useLocalize } from '../context/localize'
|
||||||
|
|
||||||
export const PRERENDERED_ARTICLES_COUNT = 21
|
export const PRERENDERED_ARTICLES_COUNT = 21
|
||||||
|
@ -27,14 +27,16 @@ export const LayoutShoutsPage = (props: PageProps) => {
|
||||||
const getLayout = createMemo<LayoutType>(() => {
|
const getLayout = createMemo<LayoutType>(() => {
|
||||||
const { page: getPage } = useRouter()
|
const { page: getPage } = useRouter()
|
||||||
const page = getPage()
|
const page = getPage()
|
||||||
if (page.route !== 'expo') throw new Error('ts guard')
|
if (page.route !== 'expo') {
|
||||||
|
throw new Error('ts guard')
|
||||||
|
}
|
||||||
const { layout } = page.params
|
const { layout } = page.params
|
||||||
return layout as LayoutType
|
return layout as LayoutType
|
||||||
})
|
})
|
||||||
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
|
||||||
const { sortedLayoutShouts, loadLayoutShoutsBy } = useLayoutsStore(getLayout(), props.layoutShouts)
|
const { sortedLayoutShouts, loadLayoutShoutsBy } = useLayoutsStore(getLayout(), props.layoutShouts)
|
||||||
const sortedArticles = createMemo<Shout[]>(() => sortedLayoutShouts().get(getLayout()) || [])
|
const sortedArticles = createMemo<Shout[]>(() => sortedLayoutShouts().get(getLayout()) || [])
|
||||||
const loadMoreLayout = async (kind: LayoutType) => {
|
const loadMoreLayout = async (_kind: LayoutType) => {
|
||||||
saveScrollPosition()
|
saveScrollPosition()
|
||||||
const { hasMore } = await loadLayoutShoutsBy({
|
const { hasMore } = await loadLayoutShoutsBy({
|
||||||
// filters: { layout: kind },
|
// filters: { layout: kind },
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { hydrate } from 'solid-js/web'
|
||||||
import type { PageContextBuiltInClient } from 'vite-plugin-ssr/client/router'
|
import type { PageContextBuiltInClient } from 'vite-plugin-ssr/client/router'
|
||||||
import type { PageContext } from './types'
|
import type { PageContext } from './types'
|
||||||
import { MetaProvider } from '@solidjs/meta'
|
import { MetaProvider } from '@solidjs/meta'
|
||||||
import i18next from 'i18next'
|
import { use as useI18next, init as initI18next } from 'i18next'
|
||||||
import HttpApi from 'i18next-http-backend'
|
import HttpApi from 'i18next-http-backend'
|
||||||
|
|
||||||
let layoutReady = false
|
let layoutReady = false
|
||||||
|
@ -12,8 +12,8 @@ let layoutReady = false
|
||||||
export const render = async (pageContext: PageContextBuiltInClient & PageContext) => {
|
export const render = async (pageContext: PageContextBuiltInClient & PageContext) => {
|
||||||
const { lng, pageProps } = pageContext
|
const { lng, pageProps } = pageContext
|
||||||
|
|
||||||
i18next.use(HttpApi)
|
useI18next(HttpApi)
|
||||||
await i18next.init({
|
await initI18next({
|
||||||
// debug: true,
|
// debug: true,
|
||||||
supportedLngs: ['ru', 'en'],
|
supportedLngs: ['ru', 'en'],
|
||||||
fallbackLng: lng,
|
fallbackLng: lng,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { App } from '../components/App'
|
||||||
import { initRouter } from '../stores/router'
|
import { initRouter } from '../stores/router'
|
||||||
import type { PageContext } from './types'
|
import type { PageContext } from './types'
|
||||||
import { MetaProvider, renderTags } from '@solidjs/meta'
|
import { MetaProvider, renderTags } from '@solidjs/meta'
|
||||||
import i18next from 'i18next'
|
import i18next, { changeLanguage, init as initI18next } from 'i18next'
|
||||||
import ru from '../../public/locales/ru/translation.json'
|
import ru from '../../public/locales/ru/translation.json'
|
||||||
import en from '../../public/locales/en/translation.json'
|
import en from '../../public/locales/en/translation.json'
|
||||||
import type { Language } from '../context/localize'
|
import type { Language } from '../context/localize'
|
||||||
|
@ -31,7 +31,8 @@ export const render = async (pageContext: PageContext) => {
|
||||||
const lng = getLng(pageContext)
|
const lng = getLng(pageContext)
|
||||||
|
|
||||||
if (!i18next.isInitialized) {
|
if (!i18next.isInitialized) {
|
||||||
i18next.init({
|
// eslint-disable-next-line import/no-named-as-default-member
|
||||||
|
await initI18next({
|
||||||
// debug: true,
|
// debug: true,
|
||||||
supportedLngs: ['ru', 'en'],
|
supportedLngs: ['ru', 'en'],
|
||||||
fallbackLng: lng,
|
fallbackLng: lng,
|
||||||
|
@ -43,7 +44,7 @@ export const render = async (pageContext: PageContext) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else if (i18next.language !== lng) {
|
} else if (i18next.language !== lng) {
|
||||||
await i18next.changeLanguage(lng)
|
await changeLanguage(lng)
|
||||||
}
|
}
|
||||||
|
|
||||||
initRouter(pageContext.urlParsed.pathname, pageContext.urlParsed.search)
|
initRouter(pageContext.urlParsed.pathname, pageContext.urlParsed.search)
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { createStorageSignal } from '@solid-primitives/storage'
|
|
||||||
import type { Reaction } from '../graphql/types.gen'
|
|
||||||
import { createSignal } from 'solid-js'
|
|
||||||
|
|
||||||
// TODO: store drafts
|
|
||||||
// import type { Draft } from '../components/EditorExample/store/context'
|
|
||||||
|
|
||||||
interface Collab {
|
|
||||||
authors: string[] // slugs
|
|
||||||
invites?: string[]
|
|
||||||
createdAt: Date
|
|
||||||
body?: string
|
|
||||||
title?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const drafts = createStorageSignal<{ [key: string]: string }>('drafts', {}) // save drafts on device
|
|
||||||
export const [collabs, setCollabs] = createSignal<Collab[]>([]) // save collabs in backend or in p2p network
|
|
||||||
export const [editorReactions, setReactions] = createSignal<Reaction[]>([])
|
|
||||||
/*
|
|
||||||
|
|
||||||
TODO: approvals and proposals derived stores
|
|
||||||
|
|
||||||
const approvals = computed(
|
|
||||||
reactions,
|
|
||||||
(rdict) => Object.values(rdict)
|
|
||||||
.filter((r: Reaction) => r.kind === ReactionKind.Accept)
|
|
||||||
)
|
|
||||||
const proposals = computed<Reaction[], typeof reactions>(
|
|
||||||
reactions,
|
|
||||||
(rdict) => Object.values(rdict)
|
|
||||||
.filter((r: Reaction) => r.kind === ReactionKind.Propose)
|
|
||||||
)
|
|
||||||
*/
|
|
|
@ -8,7 +8,9 @@ export const ROUTES = {
|
||||||
inbox: '/inbox',
|
inbox: '/inbox',
|
||||||
connect: '/connect',
|
connect: '/connect',
|
||||||
create: '/create',
|
create: '/create',
|
||||||
createSettings: '/create/settings',
|
edit: '/edit/:shoutSlug',
|
||||||
|
editSettings: '/edit/:shoutSlug/settings',
|
||||||
|
drafts: '/drafts',
|
||||||
topics: '/topics',
|
topics: '/topics',
|
||||||
topic: '/topic/:slug',
|
topic: '/topic/:slug',
|
||||||
authors: '/authors',
|
authors: '/authors',
|
||||||
|
|
|
@ -25,9 +25,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment__details {
|
.comment__details {
|
||||||
display: flex;
|
|
||||||
@include font-size(1.2rem);
|
@include font-size(1.2rem);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -76,10 +76,7 @@ main {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
display: flex;
|
display: flex;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
inset: 0;
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user