diff --git a/api/image.mjs b/api/image.mjs new file mode 100644 index 00000000..6311d618 --- /dev/null +++ b/api/image.mjs @@ -0,0 +1,24 @@ +import fetch from 'node-fetch' + +export default async function handler(req, res) { + const imageUrl = req.query.url + + if (!imageUrl) { + return res.status(400).send('Missing URL parameter') + } + + try { + const imageRes = await fetch(imageUrl) + + if (!imageRes.ok) { + return res.status(404).send('Image not found') + } + + res.setHeader('Content-Type', imageRes.headers.get('content-type')) + + imageRes.body.pipe(res) + } catch (err) { + console.error(err) + return res.status(404).send('Error') + } +} diff --git a/package-lock.json b/package-lock.json index 0d87ef0c..b2be3043 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,8 @@ "@aws-sdk/lib-storage": "^3.282.0", "formidable": "^2.1.1", "i18next": "^22.4.10", - "mailgun.js": "^8.2.0" + "mailgun.js": "^8.2.0", + "node-fetch": "^3.3.1" }, "devDependencies": { "@babel/core": "^7.21.0", @@ -417,6 +418,26 @@ "node": ">=14" } }, + "node_modules/@ardatan/sync-fetch/node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/@aws-crypto/crc32": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", @@ -7628,6 +7649,14 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, "node_modules/dataloader": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.2.tgz", @@ -9614,6 +9643,28 @@ "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", "dev": true }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -9760,6 +9811,17 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/formidable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", @@ -11970,6 +12032,26 @@ "whatwg-fetch": "^3.4.1" } }, + "node_modules/isomorphic-fetch/node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/isomorphic-ws": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", @@ -15392,24 +15474,39 @@ "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "dev": true }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "dev": true, + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", + "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", "dependencies": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": "4.x || >=6.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "node_modules/node-gyp-build": { @@ -19134,7 +19231,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "dev": true, "engines": { "node": ">= 8" } @@ -19758,6 +19854,17 @@ "dev": true, "requires": { "node-fetch": "^2.6.1" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + } } }, "@aws-crypto/crc32": { @@ -25219,6 +25326,11 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, + "data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==" + }, "dataloader": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.2.tgz", @@ -26599,6 +26711,15 @@ "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", "dev": true }, + "fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -26706,6 +26827,14 @@ "mime-types": "^2.1.12" } }, + "formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "requires": { + "fetch-blob": "^3.1.2" + } + }, "formidable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz", @@ -28332,6 +28461,17 @@ "requires": { "node-fetch": "^2.6.1", "whatwg-fetch": "^3.4.1" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + } } }, "isomorphic-ws": { @@ -30878,13 +31018,19 @@ "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "dev": true }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, "node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "dev": true, + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", + "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", "requires": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" } }, "node-gyp-build": { @@ -33631,8 +33777,7 @@ "web-streams-polyfill": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "dev": true + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==" }, "webcrypto-core": { "version": "1.7.6", diff --git a/package.json b/package.json index 9b008ae3..c5e908a2 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "@aws-sdk/lib-storage": "^3.282.0", "formidable": "^2.1.1", "i18next": "^22.4.10", - "mailgun.js": "^8.2.0" + "mailgun.js": "^8.2.0", + "node-fetch": "^3.3.1" }, "devDependencies": { "@babel/core": "^7.21.0", diff --git a/public/icons/bookmark-x.svg b/public/icons/bookmark-x.svg index ab24ac7c..c5670137 100644 --- a/public/icons/bookmark-x.svg +++ b/public/icons/bookmark-x.svg @@ -1,4 +1,3 @@ - - + + diff --git a/public/icons/bookmark.svg b/public/icons/bookmark.svg index ab24ac7c..c5670137 100644 --- a/public/icons/bookmark.svg +++ b/public/icons/bookmark.svg @@ -1,4 +1,3 @@ - - + + diff --git a/public/icons/comment.svg b/public/icons/comment.svg index 9ea76718..54fdc046 100644 --- a/public/icons/comment.svg +++ b/public/icons/comment.svg @@ -1,4 +1,3 @@ - - + + diff --git a/public/icons/feed-all.svg b/public/icons/feed-all.svg index 91e4730a..bf43645b 100644 --- a/public/icons/feed-all.svg +++ b/public/icons/feed-all.svg @@ -1,3 +1,3 @@ - - + + diff --git a/public/icons/feed-collaborate.svg b/public/icons/feed-collaborate.svg index 900af627..624212bc 100644 --- a/public/icons/feed-collaborate.svg +++ b/public/icons/feed-collaborate.svg @@ -1,11 +1,3 @@ - - - - - - - - - - + + diff --git a/public/icons/feed-discussion.svg b/public/icons/feed-discussion.svg index b551ac5e..33487b34 100644 --- a/public/icons/feed-discussion.svg +++ b/public/icons/feed-discussion.svg @@ -1,3 +1,3 @@ - - + + diff --git a/public/icons/feed-drafts.svg b/public/icons/feed-drafts.svg index 77aa04bc..09f21898 100644 --- a/public/icons/feed-drafts.svg +++ b/public/icons/feed-drafts.svg @@ -1,3 +1,3 @@ - - + + diff --git a/public/icons/feed-my.svg b/public/icons/feed-my.svg index 51a98763..0f3b6ab3 100644 --- a/public/icons/feed-my.svg +++ b/public/icons/feed-my.svg @@ -1,3 +1,3 @@ - - + + diff --git a/public/icons/feed-notifications.svg b/public/icons/feed-notifications.svg index ad127830..18f1f252 100644 --- a/public/icons/feed-notifications.svg +++ b/public/icons/feed-notifications.svg @@ -1,3 +1,3 @@ - - + + diff --git a/public/icons/pencil-outline.svg b/public/icons/pencil-outline.svg index 95eda09a..b03787a4 100644 --- a/public/icons/pencil-outline.svg +++ b/public/icons/pencil-outline.svg @@ -1,4 +1,3 @@ - - + + diff --git a/public/icons/share-outline.svg b/public/icons/share-outline.svg index a12b4c26..75d67fea 100644 --- a/public/icons/share-outline.svg +++ b/public/icons/share-outline.svg @@ -1,3 +1,3 @@ - - + + diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 4d903442..d690a0a0 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -231,5 +231,9 @@ "Your name will appear on your profile page and as your signature in publications, comments and responses.": "Your name will appear on your profile page and as your signature in publications, comments and responses", "zine": "zine", "By time": "By time", - "New only": "New only" + "New only": "New only", + "Bookmarks": "Bookmarks", + "Logout": "Logout", + "This comment has not yet been rated": "This comment has not yet been rated", + "This post has not been rated yet": "This post has not been rated yet" } diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index edbae3ef..cb056a2b 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -249,5 +249,9 @@ "view": "просмотр", "zine": "журнал", "By time": "По порядку", - "New only": "Только новые" + "New only": "Только новые", + "Bookmarks": "Закладки", + "Logout": "Выход", + "This comment has not yet been rated": "Этот комментарий еще пока никто не оценил", + "This post has not been rated yet": "Эту публикацию еще пока никто не оценил" } diff --git a/src/components/Article/Comment.module.scss b/src/components/Article/Comment.module.scss index 96cdf800..1ea32cec 100644 --- a/src/components/Article/Comment.module.scss +++ b/src/components/Article/Comment.module.scss @@ -1,16 +1,17 @@ .comment { - margin: 0 -2.4rem 0.5em; - padding: 0.8rem 2.4rem; + margin: 0.5em 0; + padding: 1rem; transition: background-color 0.3s; position: relative; list-style: none; - @include media-breakpoint-down(sm) { - margin-right: -1.2rem; + &.isNew { + border-radius: 6px; + background: rgba(38, 56, 217, 0.05); } - &:last-child { - margin-bottom: 0; + @include media-breakpoint-down(sm) { + margin-right: -1.2rem; } .comment { @@ -196,42 +197,10 @@ .commentDetails { display: flex; + padding: 1rem 0.2rem 0; margin-bottom: 1.2rem; } -.commentRating { - align-items: center; - display: flex; - font-weight: bold; -} - -.commentRatingValue { - padding: 0 0.3em; -} - -.commentRatingPositive { - color: #2bb452; -} - -.commentRatingNegative { - color: #d00820; -} - -.commentRatingControl { - border-left: 6px solid transparent; - border-right: 6px solid transparent; - height: 0; - width: 0; -} - -.commentRatingControlUp { - border-bottom: 8px solid rgb(0 0 0 / 40%); -} - -.commentRatingControlDown { - border-top: 8px solid rgb(0 0 0 / 40%); -} - .compactUserpic { height: 28px; width: 28px; diff --git a/src/components/Article/Comment.tsx b/src/components/Article/Comment.tsx index 268710c9..28f91655 100644 --- a/src/components/Article/Comment.tsx +++ b/src/components/Article/Comment.tsx @@ -13,6 +13,8 @@ import { useReactions } from '../../context/reactions' import { useSnackbar } from '../../context/snackbar' import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated' import { useLocalize } from '../../context/localize' +import { CommentRatingControl } from './CommentRatingControl' + const CommentEditor = lazy(() => import('../_shared/CommentEditor')) type Props = { @@ -20,6 +22,7 @@ type Props = { compact?: boolean isArticleAuthor?: boolean sortedComments?: Reaction[] + lastSeen?: Date } export const Comment = (props: Props) => { @@ -37,7 +40,7 @@ export const Comment = (props: Props) => { actions: { showSnackbar } } = useSnackbar() - const canEdit = createMemo(() => props.comment.createdBy?.slug === session()?.user?.slug) + const isCommentAuthor = createMemo(() => props.comment.createdBy?.slug === session()?.user?.slug) const comment = createMemo(() => props.comment) const body = createMemo(() => (comment().body || '').trim()) @@ -90,8 +93,9 @@ export const Comment = (props: Props) => { } } + const createdAt = new Date(comment()?.createdAt) return ( -
  • +
  • props.lastSeen })}>
    {
    -
    0, - [styles.commentRatingNegative]: comment().stat.rating < 0 - }} - > -
    +
    @@ -168,7 +162,7 @@ export const Comment = (props: Props) => { {loading() ? t('Loading') : t('Reply')} - +
    + } + variant="tiny" + > + + + - - - - - +
    + + + + + + +
    -
    - - {(topic) => ( - - )} - -
    +
    + + {(topic) => ( + + )} + +
    -
    - 1}> -

    {t('Authors')}

    -
    - - {(a) => ( -
    - -
    - )} -
    +
    + 1}> +

    {t('Authors')}

    +
    + + {(a) => ( +
    + +
    + )} +
    +
    +
    + + + +
    - - - diff --git a/src/components/Article/ShoutRatingControl.tsx b/src/components/Article/ShoutRatingControl.tsx index f867d851..c4e75aad 100644 --- a/src/components/Article/ShoutRatingControl.tsx +++ b/src/components/Article/ShoutRatingControl.tsx @@ -6,6 +6,8 @@ import { loadShout } from '../../stores/zine/articles' import { useSession } from '../../context/session' import { useReactions } from '../../context/reactions' import { Popup } from '../_shared/Popup' +import { VotersList } from '../_shared/VotersList' +import { useLocalize } from '../../context/localize' interface ShoutRatingControlProps { shout: Shout @@ -13,6 +15,7 @@ interface ShoutRatingControlProps { } export const ShoutRatingControl = (props: ShoutRatingControlProps) => { + const { t } = useLocalize() const { userSlug } = useSession() const { @@ -83,15 +86,10 @@ export const ShoutRatingControl = (props: ShoutRatingControlProps) => { {props.shout.stat.rating}} variant="tiny"> -
      - - {(reaction) => ( -
    • - {reaction.kind === ReactionKind.Like ? <>+1 : <>−1} {reaction.createdBy.name} -
    • - )} -
      -
    +
  • - {t('Comments')} + + {t('Comments')} +
  • {t('Bookmarks')} diff --git a/src/components/Topic/Card.tsx b/src/components/Topic/Card.tsx index 409d487c..98e7dcdc 100644 --- a/src/components/Topic/Card.tsx +++ b/src/components/Topic/Card.tsx @@ -61,7 +61,7 @@ export const TopicCard = (props: TopicProps) => { [styles.topicInRow]: props.isTopicInRow }} > -
    +

    {capitalize(props.topic.title || '')} @@ -86,7 +86,7 @@ export const TopicCard = (props: TopicProps) => {

    diff --git a/src/components/Topic/FloorHeader.tsx b/src/components/Topic/FloorHeader.tsx index 596f7a80..6f8557de 100644 --- a/src/components/Topic/FloorHeader.tsx +++ b/src/components/Topic/FloorHeader.tsx @@ -7,8 +7,8 @@ export default (props: { topic: Topic; color: string }) => { const { t } = useLocalize() return ( <> -

    {props.topic.title}

    -
    +

    {props.topic.title}

    +
    {t('All posts')} diff --git a/src/components/Topic/Full.tsx b/src/components/Topic/Full.tsx index 893731d9..ccb1637c 100644 --- a/src/components/Topic/Full.tsx +++ b/src/components/Topic/Full.tsx @@ -17,7 +17,7 @@ export const FullTopic = (props: Props) => { const { t } = useLocalize() const subscribed = createMemo(() => session()?.news?.topics?.includes(props.topic?.slug)) return ( -
    +

    #{props.topic.title}

    {props.topic.body}

    diff --git a/src/components/Views/AllAuthors.tsx b/src/components/Views/AllAuthors.tsx index 3ca9d269..341cc747 100644 --- a/src/components/Views/AllAuthors.tsx +++ b/src/components/Views/AllAuthors.tsx @@ -94,7 +94,7 @@ export const AllAuthorsView = (props: AllAuthorsViewProps) => { const showMore = () => setLimit((oldLimit) => oldLimit + PAGE_SIZE) const AllAuthorsHead = () => (
    -
    +

    {t('Authors')}

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

    @@ -121,12 +121,12 @@ export const AllAuthorsView = (props: AllAuthorsViewProps) => { return (
    0}> -
    +
    -
    +
    @@ -94,13 +90,13 @@ export const SearchView = (props: Props) => {
    {(article) => ( -
    +
    )} -
    +
    {t('Load more')} diff --git a/src/components/Views/Topic.tsx b/src/components/Views/Topic.tsx index 7c741b58..d4a2aed1 100644 --- a/src/components/Views/Topic.tsx +++ b/src/components/Views/Topic.tsx @@ -81,7 +81,7 @@ export const TopicView = (props: TopicProps) => {
    -
    +
    • */}
    -
    +
    {`${t('Show')} `} {t('All posts')} diff --git a/src/components/_shared/Image/Image.tsx b/src/components/_shared/Image/Image.tsx new file mode 100644 index 00000000..a4a6f88b --- /dev/null +++ b/src/components/_shared/Image/Image.tsx @@ -0,0 +1,8 @@ +import { splitProps } from 'solid-js' +import type { JSX } from 'solid-js' + +export const Image = (props: JSX.ImgHTMLAttributes) => { + const [local, others] = splitProps(props, ['src']) + + return +} diff --git a/src/components/_shared/Image/index.ts b/src/components/_shared/Image/index.ts new file mode 100644 index 00000000..8085d7b6 --- /dev/null +++ b/src/components/_shared/Image/index.ts @@ -0,0 +1 @@ +export { Image } from './Image' diff --git a/src/components/_shared/Slider.tsx b/src/components/_shared/Slider.tsx index a520858e..a4db0a32 100644 --- a/src/components/_shared/Slider.tsx +++ b/src/components/_shared/Slider.tsx @@ -101,7 +101,7 @@ export default (props: SliderProps) => {
    -

    {props.title}

    +

    {props.title}

    { + return ( +
    +
      + 0} + fallback={ +
    • + {props.fallbackMessage} +
    • + } + > + + {(reaction) => ( +
    • + + {reaction.kind === ReactionKind.Like ? ( +
      +1
      + ) : ( +
      −1
      + )} +
    • + )} +
      +
      +
    +
    + ) +} diff --git a/src/components/_shared/VotersList/index.ts b/src/components/_shared/VotersList/index.ts new file mode 100644 index 00000000..a94042b9 --- /dev/null +++ b/src/components/_shared/VotersList/index.ts @@ -0,0 +1 @@ +export { VotersList } from './VotersList' diff --git a/src/context/inbox.tsx b/src/context/inbox.tsx index adaa12b3..8cecb8a4 100644 --- a/src/context/inbox.tsx +++ b/src/context/inbox.tsx @@ -1,6 +1,6 @@ import type { Accessor, JSX } from 'solid-js' import { createContext, createSignal, useContext, createMemo } from 'solid-js' -import { createChatClient } from '../graphql/privateGraphQLClient' +import { createSubClient } from '../graphql/privateGraphQLClient' import type { Chat, Message, MutationCreateMessageArgs } from '../graphql/types.gen' import { apiClient } from '../utils/apiClient' import newMessage from '../graphql/subs/new-message' @@ -29,7 +29,7 @@ export function useInbox() { export const InboxProvider = (props: { children: JSX.Element }) => { const [chats, setChats] = createSignal([]) const [messages, setMessages] = createSignal([]) - const subclient = createMemo(() => createChatClient()) + const subclient = createMemo(() => createSubClient()) const loadChats = async () => { try { const newChats = await apiClient.getChats({ limit: 50, offset: 0 }) @@ -71,8 +71,8 @@ export const InboxProvider = (props: { children: JSX.Element }) => { return chat } - const { unsubscribe } = pipe( - () => subclient().subscription(newMessage, {}), + pipe( + subclient().subscription(newMessage, {}), subscribe((result) => { console.info('[subscription]') console.debug(result) @@ -83,8 +83,8 @@ export const InboxProvider = (props: { children: JSX.Element }) => { createChat, loadChats, getMessages, - sendMessage, - unsubscribe // TODO: call unsubscribe some time! + sendMessage + // unsubscribe // TODO: call unsubscribe some time! } const value: InboxContextType = { chats, messages, actions } diff --git a/src/graphql/privateGraphQLClient.ts b/src/graphql/privateGraphQLClient.ts index 5b414a22..e6a4f4aa 100644 --- a/src/graphql/privateGraphQLClient.ts +++ b/src/graphql/privateGraphQLClient.ts @@ -7,7 +7,7 @@ import { createClient } from '@urql/core' // import { createClient as createSubClient } from 'graphql-sse' -import { createClient as createSubClient } from 'graphql-ws' +import { createClient as createWSClient } from 'graphql-ws' import { devtoolsExchange } from '@urql/devtools' import { isDev, apiBaseUrl } from '../utils/config' // import { cache } from './cache' @@ -55,8 +55,8 @@ const options: ClientOptions = { export const privateGraphQLClient = createClient(options) -export const createChatClient = () => { - const subClient = createSubClient({ +export const createSubClient = () => { + const subClient = createWSClient({ url: apiBaseUrl.replace('http', 'ws') // + '/messages' }) diff --git a/src/graphql/query/article-load.ts b/src/graphql/query/article-load.ts index 43b0dd4a..6bec8004 100644 --- a/src/graphql/query/article-load.ts +++ b/src/graphql/query/article-load.ts @@ -39,6 +39,7 @@ export default gql` viewed reacted rating + commented } } } diff --git a/src/graphql/query/articles-load-by.ts b/src/graphql/query/articles-load-by.ts index 4baad70a..b7929c2c 100644 --- a/src/graphql/query/articles-load-by.ts +++ b/src/graphql/query/articles-load-by.ts @@ -37,6 +37,7 @@ export default gql` viewed reacted rating + commented } } } diff --git a/src/graphql/subs/new-reaction.ts b/src/graphql/subs/new-reaction.ts new file mode 100644 index 00000000..8ffd4cf8 --- /dev/null +++ b/src/graphql/subs/new-reaction.ts @@ -0,0 +1,26 @@ +import { gql } from '@urql/core' + +export default gql` + subscription { + newReactions { + id + body + kind + range + createdAt + replyTo + stat { + rating + } + shout { + id + slug + } + createdBy { + name + slug + userpic + } + } + } +` diff --git a/src/graphql/subs/new-shout.ts b/src/graphql/subs/new-shout.ts new file mode 100644 index 00000000..8172fd63 --- /dev/null +++ b/src/graphql/subs/new-shout.ts @@ -0,0 +1,25 @@ +import { gql } from '@urql/core' + +export default gql` + subscription { + newShout { + id + slug + title + subtitle + body + topics { + # id + title + slug + } + authors { + id + name + slug + userpic + caption + } + } + } +` diff --git a/src/pages/about/discussionRules.page.tsx b/src/pages/about/discussionRules.page.tsx index e3bc8cfe..7f03e424 100644 --- a/src/pages/about/discussionRules.page.tsx +++ b/src/pages/about/discussionRules.page.tsx @@ -10,7 +10,7 @@ export const DiscussionRulesPage = () => { {title}
    -
    +

    diff --git a/src/pages/about/dogma.page.tsx b/src/pages/about/dogma.page.tsx index 76edcd14..229cf16a 100644 --- a/src/pages/about/dogma.page.tsx +++ b/src/pages/about/dogma.page.tsx @@ -7,7 +7,7 @@ export const DogmaPage = () => {
    -
    +

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

    Дискурс - журнал с открытой горизонтальной редакцией. Содержание журнала определяется прямым diff --git a/src/pages/about/guide.page.tsx b/src/pages/about/guide.page.tsx index 4479a8f0..6381dc93 100644 --- a/src/pages/about/guide.page.tsx +++ b/src/pages/about/guide.page.tsx @@ -23,7 +23,7 @@ export const GuidePage = () => {

    -
    +
    -
    +

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

    diff --git a/src/pages/about/help.page.tsx b/src/pages/about/help.page.tsx index 9c629d25..cded23cb 100644 --- a/src/pages/about/help.page.tsx +++ b/src/pages/about/help.page.tsx @@ -22,7 +22,7 @@ export const HelpPage = () => {
    -
    +
    -
    +

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

    @@ -78,7 +78,7 @@ export const HelpPage = () => { продолжался, пожалуйста, поддержите проект.

    -
    +
    diff --git a/src/pages/about/manifest.page.tsx b/src/pages/about/manifest.page.tsx index 0a2c2863..6fa36ce6 100644 --- a/src/pages/about/manifest.page.tsx +++ b/src/pages/about/manifest.page.tsx @@ -23,7 +23,7 @@ export const ManifestPage = () => {
    -
    +
    -
    +

    Манифест

    diff --git a/src/pages/about/partners.page.tsx b/src/pages/about/partners.page.tsx index 1dd97be0..e96afbb4 100644 --- a/src/pages/about/partners.page.tsx +++ b/src/pages/about/partners.page.tsx @@ -9,7 +9,7 @@ export const PartnersPage = () => { {t('Partners')}
    -
    +

    {t('Partners')}

    diff --git a/src/pages/about/principles.page.tsx b/src/pages/about/principles.page.tsx index e3f15916..88378c9f 100644 --- a/src/pages/about/principles.page.tsx +++ b/src/pages/about/principles.page.tsx @@ -10,7 +10,7 @@ export const PrinciplesPage = () => { {t('Principles')}
    -
    +

    {t('Principles')}

    diff --git a/src/pages/about/projects.page.tsx b/src/pages/about/projects.page.tsx index d84c02ba..a6c7dffe 100644 --- a/src/pages/about/projects.page.tsx +++ b/src/pages/about/projects.page.tsx @@ -9,7 +9,7 @@ export const ProjectsPage = () => { {t('Projects')}
    -
    +

    {t('Projects')}

    diff --git a/src/pages/about/termsOfUse.page.tsx b/src/pages/about/termsOfUse.page.tsx index 64218598..22dda7f5 100644 --- a/src/pages/about/termsOfUse.page.tsx +++ b/src/pages/about/termsOfUse.page.tsx @@ -20,7 +20,7 @@ export const TermsOfUsePage = () => {
    -
    +
    -
    +

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

    diff --git a/src/pages/about/thanks.page.tsx b/src/pages/about/thanks.page.tsx index 6f57cd10..0569fbe5 100644 --- a/src/pages/about/thanks.page.tsx +++ b/src/pages/about/thanks.page.tsx @@ -14,7 +14,7 @@ export const ThanksPage = () => {
    -
    +

    {title}

    diff --git a/src/pages/article.page.tsx b/src/pages/article.page.tsx index 8378bf37..bece4351 100644 --- a/src/pages/article.page.tsx +++ b/src/pages/article.page.tsx @@ -7,6 +7,7 @@ import { useRouter } from '../stores/router' import { Loading } from '../components/_shared/Loading' import { ReactionsProvider } from '../context/reactions' import { FullArticle } from '../components/Article/FullArticle' +import { setPageLoadManagerPromise } from '../utils/pageLoadManager' export const ArticlePage = (props: PageProps) => { const shouts = props.article ? [props.article] : [] @@ -33,7 +34,9 @@ export const ArticlePage = (props: PageProps) => { const articleValue = articleEntities()[slug()] if (!articleValue || !articleValue.body) { - await loadShout(slug()) + const loadShoutPromise = loadShout(slug()) + setPageLoadManagerPromise(loadShoutPromise) + await loadShoutPromise } }) diff --git a/src/pages/connect.page.tsx b/src/pages/connect.page.tsx index f9882458..361e2d8f 100644 --- a/src/pages/connect.page.tsx +++ b/src/pages/connect.page.tsx @@ -5,7 +5,7 @@ export const ConnectPage = () => {
    -
    +

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

    diff --git a/src/pages/layoutShouts.page.tsx b/src/pages/layoutShouts.page.tsx index fc254427..afda53b3 100644 --- a/src/pages/layoutShouts.page.tsx +++ b/src/pages/layoutShouts.page.tsx @@ -76,7 +76,7 @@ export const LayoutShoutsPage = (props: PageProps) => { const ModeSwitcher = () => (
    -
    +
    • {t('Audio')} @@ -92,7 +92,7 @@ export const LayoutShoutsPage = (props: PageProps) => {
    -
    +
    {`${t('Show')} `} {t('All posts')} diff --git a/src/pages/profile/Settings.module.scss b/src/pages/profile/Settings.module.scss index 056ae465..eed12635 100644 --- a/src/pages/profile/Settings.module.scss +++ b/src/pages/profile/Settings.module.scss @@ -114,6 +114,10 @@ h5 { .leftNavigation { top: 9rem !important; + + h4:first-child { + margin-top: 0; + } } .passwordToggleControl { diff --git a/src/pages/profile/profileSecurity.page.tsx b/src/pages/profile/profileSecurity.page.tsx index e627be83..a32e13ce 100644 --- a/src/pages/profile/profileSecurity.page.tsx +++ b/src/pages/profile/profileSecurity.page.tsx @@ -8,120 +8,122 @@ export const ProfileSecurityPage = () => { return (
    -
    -
    +
    +
    -
    -
    -

    Вход и безопасность

    -

    Настройки аккаунта, почты, пароля и способов входа.

    +
    +
    +
    +

    Вход и безопасность

    +

    Настройки аккаунта, почты, пароля и способов входа.

    - -

    Почта

    -
    - - -
    + +

    Почта

    +
    + + +
    -

    Изменить пароль

    -
    Текущий пароль
    -
    - - -
    +

    Изменить пароль

    +
    Текущий пароль
    +
    + + +
    -
    Новый пароль
    -
    - - -
    +
    Новый пароль
    +
    + + +
    -
    Подтвердите новый пароль
    -
    - - -
    +
    Подтвердите новый пароль
    +
    + + +
    -

    Социальные сети

    -
    Google
    -
    +

    Социальные сети

    +
    Google
    +
    +

    + +

    +
    + +
    VK
    +
    +

    + +

    +
    + +
    Facebook
    +
    +

    + +

    +
    + +
    Apple
    +
    +

    + +

    +
    + +

    -

    -
    - -
    VK
    -
    -

    - -

    -
    - -
    Facebook
    -
    -

    - -

    -
    - -
    Apple
    -
    -

    - -

    -
    - -
    -

    - -

    - + +
    diff --git a/src/pages/profile/profileSettings.page.tsx b/src/pages/profile/profileSettings.page.tsx index fc6cd6f5..dba0460b 100644 --- a/src/pages/profile/profileSettings.page.tsx +++ b/src/pages/profile/profileSettings.page.tsx @@ -6,13 +6,13 @@ import { clsx } from 'clsx' import styles from './Settings.module.scss' import { useProfileForm } from '../../context/profile' import validateUrl from '../../utils/validateUrl' - import { createFileUploader, UploadFile } from '@solid-primitives/upload' import { Loading } from '../../components/_shared/Loading' import { useSession } from '../../context/session' import { Button } from '../../components/_shared/Button' import { useSnackbar } from '../../context/snackbar' import { useLocalize } from '../../context/localize' +import { Image } from '../../components/_shared/Image' const handleFileUpload = async (uploadFile: UploadFile) => { const formData = new FormData() @@ -85,143 +85,147 @@ export const ProfileSettingsPage = () => {
    -
    -
    +
    +
    -
    -
    -

    {t('Profile settings')}

    -

    {t('Here you can customize your profile the way you want.')}

    -
    -

    {t('Userpic')}

    -
    -
    - }> - {form.name} - -
    -
    -

    {t('Name')}

    -

    - {t( - 'Your name will appear on your profile page and as your signature in publications, comments and responses.' - )} -

    -
    - updateFormField('name', event.currentTarget.value)} - value={form.name} - /> - -
    - -

    {t('Address on Discourse')}

    -
    -
    - -
    - updateFormField('slug', event.currentTarget.value)} - value={form.slug} - class="nolabel" - /> -

    {t(`${slugError()}`)}

    +
    +
    +
    +

    {t('Profile settings')}

    +

    {t('Here you can customize your profile the way you want.')}

    + +

    {t('Userpic')}

    +
    +
    + }> + {form.name} +
    -
    - -

    {t('Introduce')}

    -
    - - -
    - -

    {t('About myself')}

    -
    - + +
    + +

    {t('About myself')}

    +
    +