diff --git a/package-lock.json b/package-lock.json index 06509ac4..4553ab3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "i18next": "22.4.15", "i18next-icu": "2.3.0", "intl-messageformat": "10.5.3", + "just-throttle": "4.2.0", "mailgun.js": "8.2.1", "node-fetch": "3.3.1" }, @@ -13168,6 +13169,11 @@ "node": ">=4.0" } }, + "node_modules/just-throttle": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/just-throttle/-/just-throttle-4.2.0.tgz", + "integrity": "sha512-/iAZv1953JcExpvsywaPKjSzfTiCLqeguUTE6+VmK15mOcwxBx7/FHrVvS4WEErMR03TRazH8kcBSHqMagYIYg==" + }, "node_modules/kebab-case": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/kebab-case/-/kebab-case-1.0.2.tgz", @@ -28106,6 +28112,11 @@ "object.values": "^1.1.6" } }, + "just-throttle": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/just-throttle/-/just-throttle-4.2.0.tgz", + "integrity": "sha512-/iAZv1953JcExpvsywaPKjSzfTiCLqeguUTE6+VmK15mOcwxBx7/FHrVvS4WEErMR03TRazH8kcBSHqMagYIYg==" + }, "kebab-case": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/kebab-case/-/kebab-case-1.0.2.tgz", diff --git a/package.json b/package.json index e6d3f2bb..7de87825 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "i18next": "22.4.15", "i18next-icu": "2.3.0", "intl-messageformat": "10.5.3", + "just-throttle": "4.2.0", "mailgun.js": "8.2.1", "node-fetch": "3.3.1" }, diff --git a/public/icons/key.svg b/public/icons/key.svg new file mode 100644 index 00000000..11d788ca --- /dev/null +++ b/public/icons/key.svg @@ -0,0 +1,4 @@ + + + diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index d277ae8c..577ddfcf 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -28,6 +28,8 @@ "All posts": "All posts", "All topics": "All topics", "Almost done! Check your email.": "Almost done! Just checking your email.", + "Are you sure you want to delete this comment?": "Are you sure you want to delete this comment?", + "Are you sure you want to delete this draft?": "Are you sure you want to delete this draft?", "Are you sure you want to to proceed the action?": "Are you sure you want to to proceed the action?", "Art": "Art", "Artist": "Artist", @@ -100,6 +102,7 @@ "Discussion rules": "Discussion rules", "Discussions": "Discussions", "Dogma": "Dogma", + "Draft successfully deleted": "Draft successfully deleted", "Drafts": "Drafts", "Drag the image to this area": "Drag the image to this area", "Each image must be no larger than 5 MB.": "Each image must be no larger than 5 MB.", @@ -193,6 +196,7 @@ "Manifesto": "Manifesto", "Many files, choose only one": "Many files, choose only one", "Material card": "Material card", + "Message": "Message", "More": "More", "Most commented": "Commented", "Most read": "Readable", @@ -206,12 +210,19 @@ "New only": "New only", "New password": "New password", "New stories every day and even more!": "New stories and more are waiting for you every day!", - "NewCommentNotificationText": "{commentsCount, plural, one {New comment} other {{commentsCount} comments}} to your publication {shoutTitle} from {lastCommenterName}{restUsersCount, plural, =0 {} one { one more user} other { and more {restUsersCount} users}}", - "NewReplyNotificationText": "{commentsCount, plural, one {New reply} other {{commentsCount} replays} other {{commentsCount} новых ответов}} to your publication {shoutTitle} от {lastCommenterName}{restUsersCount, plural, =0 {} one { and one more user} other { and more {restUsersCount} users}}", + + "NotificationNewCommentText1": "{commentsCount, plural, one {New comment} other {{commentsCount} comments}} to your publication", + "NotificationNewCommentText2": "from", + "NotificationNewCommentText3": "{restUsersCount, plural, =0 {} one { one more user} other { and more {restUsersCount} users}}", + + "NotificationNewReplyText1": "{commentsCount, plural, one {New reply} other {{commentsCount} replays}} to your publication", + "NotificationNewReplyText2": "from", + "NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { and one more user} other { and more {restUsersCount} users}}", + "Newsletter": "Newsletter", "Night mode": "Night mode", - "No notifications, yet": "No notifications, yet", - "No such account, please try to register": "No such account found, please try to register", + "No notifications yet": "No notifications yet", + "Write good articles, comment\nand it won't be so empty here": "Write good articles, comment\nand it won't be so empty here", "Nothing here yet": "There's nothing here yet", "Nothing is here": "There is nothing here", "Notifications": "Notifications", @@ -353,7 +364,6 @@ "Where": "From", "Words": "Слов", "Work with us": "Cooperate with Discourse", - "Message": "Message", "Write a comment...": "Write a comment...", "Write a short introduction": "Write a short introduction", "Write about the topic": "Write about the topic", diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index 2a983fa3..b8f5d551 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -31,6 +31,8 @@ "All posts": "Все публикации", "All topics": "Все темы", "Almost done! Check your email.": "Почти готово! Осталось подтвердить вашу почту.", + "Are you sure you want to delete this comment?": "Уверены, что хотите удалить этот комментарий?", + "Are you sure you want to delete this draft?": "Уверены, что хотите удалить этот черновик?", "Are you sure you want to to proceed the action?": "Вы уверены, что хотите продолжить?", "Art": "Искусство", "Artist": "Исполнитель", @@ -104,6 +106,7 @@ "Discussion rules": "Правила сообществ самиздата в соцсетях", "Discussions": "Дискуссии", "Dogma": "Догма", + "Draft successfully deleted": "Черновик успешно удален", "Drafts": "Черновики", "Drag the image to this area": "Перетащите изображение в эту область", "Each image must be no larger than 5 MB.": "Каждое изображение должно быть размером не больше 5 мб.", @@ -217,11 +220,19 @@ "New only": "Только новые", "New password": "Новый пароль", "New stories every day and even more!": "Каждый день вас ждут новые истории и ещё много всего интересного!", - "NewCommentNotificationText": "{commentsCount, plural, one {Новый комментарий} few {{commentsCount} новых комментария} other {{commentsCount} новых комментариев}} к вашей публикации {shoutTitle} от {lastCommenterName}{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}", - "NewReplyNotificationText": "{commentsCount, plural, one {Новый ответ} few {{commentsCount} новых ответа} other {{commentsCount} новых ответов}} к вашему комментарию к публикации {shoutTitle} от {lastCommenterName}{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}", + + "NotificationNewCommentText1": "{commentsCount, plural, one {Новый комментарий} few {{commentsCount} новых комментария} other {{commentsCount} новых комментариев}} к вашей публикации", + "NotificationNewCommentText2": "от", + "NotificationNewCommentText3": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}", + + "NotificationNewReplyText1": "{commentsCount, plural, one {Новый ответ} few {{commentsCount} новых ответа} other {{commentsCount} новых ответов}} на ваш комментарий к публикации", + "NotificationNewReplyText2": "от", + "NotificationNewReplyText3": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}", + "Newsletter": "Рассылка", "Night mode": "Ночная тема", - "No notifications, yet": "Тут пока пусто", + "No notifications yet": "Уведомлений пока нет", + "Write good articles, comment\nand it won't be so empty here": "Пишите хорошие статьи, комментируйте,\nи здесь станет не так пусто", "No such account, please try to register": "Такой адрес не найден, попробуйте зарегистрироваться", "Nothing here yet": "Здесь пока ничего нет", "Nothing is here": "Здесь ничего нет", diff --git a/src/components/Article/Comment.module.scss b/src/components/Article/Comment.module.scss index 58f775ab..eb8eea1b 100644 --- a/src/components/Article/Comment.module.scss +++ b/src/components/Article/Comment.module.scss @@ -1,5 +1,5 @@ .comment { - margin: 0.5em 0; + margin: 0 0 0.5em; padding: 1rem; transition: background-color 0.3s; position: relative; diff --git a/src/components/Article/Comment.tsx b/src/components/Article/Comment.tsx index 35788445..3405b49b 100644 --- a/src/components/Article/Comment.tsx +++ b/src/components/Article/Comment.tsx @@ -62,7 +62,12 @@ export const Comment = (props: Props) => { const remove = async () => { if (comment()?.id) { try { - const isConfirmed = await showConfirm() + const isConfirmed = await showConfirm({ + confirmBody: t('Are you sure you want to delete this comment?'), + confirmButtonLabel: t('Delete'), + confirmButtonVariant: 'danger', + declineButtonVariant: 'primary' + }) if (isConfirmed) { await deleteReaction(comment().id) @@ -136,7 +141,7 @@ export const Comment = (props: Props) => { })} /> - {comment()?.shout.title || ''} + {comment()?.shout.title || ''} } @@ -174,7 +179,7 @@ export const Comment = (props: Props) => { -
+
}> {t('Loading')}

}> { handleSubmitComment(value)} diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index 7d68604b..7647b2b1 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -28,11 +28,26 @@ import styles from './Article.module.scss' import { CardTopic } from '../Feed/CardTopic' import { createPopper } from '@popperjs/core' -interface Props { +type Props = { article: Shout scrollToComments?: boolean } +export type ArticlePageSearchParams = { + scrollTo: 'comments' + commentId: string +} + +const scrollTo = (el: HTMLElement) => { + const { top } = el.getBoundingClientRect() + + window.scrollTo({ + top: top + window.scrollY - 96, + left: 0, + behavior: 'smooth' + }) +} + export const FullArticle = (props: Props) => { const { t } = useLocalize() const { @@ -77,16 +92,15 @@ export const FullArticle = (props: Props) => { return JSON.parse(props.article.media || '[]') }) - const commentsRef: { current: HTMLDivElement } = { current: null } + const commentsRef: { + current: HTMLDivElement + } = { current: null } + const scrollToComments = () => { - window.scrollTo({ - top: commentsRef.current.offsetTop - 96, - left: 0, - behavior: 'smooth' - }) + scrollTo(commentsRef.current) } - const { searchParams, changeSearchParam } = useRouter() + const { searchParams, changeSearchParam } = useRouter() createEffect(() => { if (props.scrollToComments) { @@ -105,9 +119,14 @@ export const FullArticle = (props: Props) => { createEffect(() => { if (searchParams().commentId && isReactionsLoaded()) { - const commentElement = document.querySelector(`[id='comment_${searchParams().commentId}']`) + const commentElement = document.querySelector( + `[id='comment_${searchParams().commentId}']` + ) + + changeSearchParam({ commentId: null }) + if (commentElement) { - commentElement.scrollIntoView({ behavior: 'smooth' }) + scrollTo(commentElement) } } }) diff --git a/src/components/Author/AuthorBadge/AuthorBadge.module.scss b/src/components/Author/AuthorBadge/AuthorBadge.module.scss index 5aa176e4..da653817 100644 --- a/src/components/Author/AuthorBadge/AuthorBadge.module.scss +++ b/src/components/Author/AuthorBadge/AuthorBadge.module.scss @@ -9,6 +9,10 @@ margin-bottom: 3rem; } + @include media-breakpoint-down(md) { + text-align: left; + } + .info { @include font-size(1.4rem); border: none; diff --git a/src/components/Author/AuthorCard/AuthorCard.module.scss b/src/components/Author/AuthorCard/AuthorCard.module.scss index 240e4be2..52b0d3d1 100644 --- a/src/components/Author/AuthorCard/AuthorCard.module.scss +++ b/src/components/Author/AuthorCard/AuthorCard.module.scss @@ -42,7 +42,7 @@ flex: 1; @include media-breakpoint-up(sm) { - align-items: baseline; + align-items: center; display: flex; } @@ -426,6 +426,10 @@ } } +.shareControl { + display: inline-block; +} + .buttonSubscribe { align-items: center; aspect-ratio: 1/1; @@ -447,8 +451,10 @@ } .buttonWrite { + background: #ccc; color: #000; display: inline-flex; + font-weight: 500; transition: background-color 0.3s, color 0.3s; @@ -490,6 +496,7 @@ color: #696969; @include font-size(2rem); font-weight: 500; + margin-top: 1.5rem; } .authorSubscribe { diff --git a/src/components/Author/AuthorCard/AuthorCard.tsx b/src/components/Author/AuthorCard/AuthorCard.tsx index 9e5433c4..75e6b2f2 100644 --- a/src/components/Author/AuthorCard/AuthorCard.tsx +++ b/src/components/Author/AuthorCard/AuthorCard.tsx @@ -208,76 +208,57 @@ export const AuthorCard = (props: Props) => { } >
- - + + 0}> + +
{
+ + +
+
+
diff --git a/src/components/Draft/Draft.tsx b/src/components/Draft/Draft.tsx index f5095a65..7ee25154 100644 --- a/src/components/Draft/Draft.tsx +++ b/src/components/Draft/Draft.tsx @@ -35,11 +35,16 @@ export const Draft = (props: Props) => { const handleDeleteLinkClick = async (e) => { e.preventDefault() - const isConfirmed = await showConfirm() + const isConfirmed = await showConfirm({ + confirmBody: t('Are you sure you want to delete this draft?'), + confirmButtonLabel: t('Delete'), + confirmButtonVariant: 'danger', + declineButtonVariant: 'primary' + }) if (isConfirmed) { props.onDelete(props.shout) - await showSnackbar({ type: 'success', body: t('Success') }) + await showSnackbar({ body: t('Draft successfully deleted') }) } } diff --git a/src/components/Feed/ArticleCard.module.scss b/src/components/Feed/ArticleCard.module.scss index c0ca557a..020e60f1 100644 --- a/src/components/Feed/ArticleCard.module.scss +++ b/src/components/Feed/ArticleCard.module.scss @@ -115,12 +115,8 @@ } } -.shoutAuthor, -.shoutDate { - @include font-size(1.4rem); -} - .shoutAuthor { + @include font-size(1.4rem); font-weight: 500; margin-right: 1.6rem; @@ -139,9 +135,11 @@ .shoutDate { color: #9fa1a7; font-weight: 500; + @include font-size(1.2rem); } .shoutDetails { + align-items: end; display: flex; margin-bottom: 1rem; } @@ -149,8 +147,7 @@ .shoutDetailsFeedMode { justify-content: space-between; - .shoutAuthor, - .shoutDate { + .shoutAuthor { @include font-size(1.2rem); } } @@ -194,19 +191,27 @@ font-weight: 400; line-height: 1.3; margin-bottom: 1.4rem; - transition: color 0.2s, background-color 0.2s, box-shadow 0.2s; + transition: + color 0.2s, + background-color 0.2s, + box-shadow 0.2s; } .shoutCardLinkContainer { position: relative; - transition: color 0.2s, background-color 0.2s, box-shadow 0.2s; + transition: + color 0.2s, + background-color 0.2s, + box-shadow 0.2s; } .shoutCardEditControl { border-radius: 2em; min-height: 2.6em; padding: 0 1.4em; - transition: background-color 0.2s, color 0.2s; + transition: + background-color 0.2s, + color 0.2s; &:hover { background: var(--background-color-invert); diff --git a/src/components/Feed/Sidebar/Sidebar.module.scss b/src/components/Feed/Sidebar/Sidebar.module.scss index c09de070..5735b053 100644 --- a/src/components/Feed/Sidebar/Sidebar.module.scss +++ b/src/components/Feed/Sidebar/Sidebar.module.scss @@ -1,9 +1,11 @@ .sidebar { + margin-top: -0.7rem; max-height: calc(100vh - 120px); overflow: auto; top: 120px; @include media-breakpoint-up(md) { + margin-top: 0; position: sticky; ul > li { diff --git a/src/components/Inbox/Message.tsx b/src/components/Inbox/Message.tsx index bdcf0b9d..9a345cf4 100644 --- a/src/components/Inbox/Message.tsx +++ b/src/components/Inbox/Message.tsx @@ -7,7 +7,6 @@ import formattedTime from '../../utils/formatDateTime' import { Icon } from '../_shared/Icon' import { MessageActionsPopup } from './MessageActionsPopup' import QuotedMessage from './QuotedMessage' -import MD from '../Article/MD' type Props = { content: MessageType diff --git a/src/components/Nav/ConfirmModal/ConfirmModal.module.scss b/src/components/Nav/ConfirmModal/ConfirmModal.module.scss index deb8f946..b955ec59 100644 --- a/src/components/Nav/ConfirmModal/ConfirmModal.module.scss +++ b/src/components/Nav/ConfirmModal/ConfirmModal.module.scss @@ -1,48 +1,22 @@ .confirmModal { - background: #fff; - min-height: 550px; position: relative; - @include media-breakpoint-up(md) { - min-height: 710px; - } -} - -.confirmModalTitle { - font-size: 26px; - line-height: 32px; - font-weight: 700; - color: #141414; - text-align: left; -} - -.confirmModalActions { - display: flex; - justify-content: space-between; - margin-top: 16px; -} - -.confirmModalButton { - display: block; - width: 100%; - margin-right: 12px; - font-weight: 700; - margin-top: 32px; - padding: 1.6rem !important; - border: 1px solid black; - - &:hover { - background-color: rgb(0 0 0 / 8%); - } -} - -.confirmModalButtonPrimary { - margin-right: 0; - background-color: black; - color: white; - border: none; - - &:hover { - background-color: rgb(0 0 0 / 60%); + .confirmModalTitle { + @include font-size(2rem); + + font-weight: 700; + color: var(--default-color); + text-align: center; + } + + .confirmModalActions { + display: flex; + justify-content: space-between; + margin-top: 4rem; + gap: 2rem; + + .confirmAction { + flex: 1; + } } } diff --git a/src/components/Nav/ConfirmModal/ConfirmModal.tsx b/src/components/Nav/ConfirmModal/ConfirmModal.tsx index b224f0f0..63f6a767 100644 --- a/src/components/Nav/ConfirmModal/ConfirmModal.tsx +++ b/src/components/Nav/ConfirmModal/ConfirmModal.tsx @@ -1,7 +1,7 @@ -import { clsx } from 'clsx' import { useConfirm } from '../../../context/confirm' -import styles from './ConfirmModal.module.scss' import { useLocalize } from '../../../context/localize' +import { Button } from '../../_shared/Button' +import styles from './ConfirmModal.module.scss' export const ConfirmModal = () => { const { t } = useLocalize() @@ -12,21 +12,26 @@ export const ConfirmModal = () => { } = useConfirm() return ( -
+

{confirmMessage().confirmBody ?? t('Are you sure you want to to proceed the action?')}

- - + value={confirmMessage().confirmButtonLabel ?? t('Confirm')} + size="L" + variant={confirmMessage().confirmButtonVariant ?? 'primary'} + class={styles.confirmAction} + />
) diff --git a/src/components/Nav/Header/Header.module.scss b/src/components/Nav/Header/Header.module.scss index 9d6fc432..a991f1a9 100644 --- a/src/components/Nav/Header/Header.module.scss +++ b/src/components/Nav/Header/Header.module.scss @@ -104,9 +104,9 @@ position: relative; @include media-breakpoint-down(lg) { - flex: 1 !important; max-width: 100% !important; - padding: 0 !important; + position: absolute; + right: 0; } } @@ -139,7 +139,7 @@ overflow: auto; padding: $container-padding-x !important; position: fixed; - top: 64px; + top: 58px; width: 100%; z-index: 1; @@ -191,8 +191,9 @@ } } - ul { + :global(.view-switcher) { margin-top: 0; + overflow: hidden; } li { @@ -217,6 +218,10 @@ .fixed & { display: block; } + + a { + padding-top: 0.1em; + } } .mainNavigationSocial a { @@ -246,6 +251,30 @@ background: #f7f7f8; border: none; border-radius: 1.6rem; + padding-right: 5.6rem; + + &:not(:placeholder-shown) { + & ~ .mobileSubscriptionSubmit { + display: block; + } + } + } +} + +.mobileSubscriptionSubmit { + aspect-ratio: 1/1; + display: none; + height: 100%; + position: absolute; + right: 0; + top: 0; + + img { + aspect-ratio: 1/1; + left: 50%; + position: relative; + transform: translateX(-50%); + width: 16px !important; } } @@ -387,15 +416,11 @@ display: flex; justify-content: flex-end; position: absolute; - right: 5rem; + right: 0; top: 50%; transform: translateY(-50%); width: 100%; - @include media-breakpoint-up(lg) { - right: 0; - } - @include media-breakpoint-up(xl) { right: 2rem; } @@ -446,10 +471,6 @@ z-index: -1; } - @include media-breakpoint-down(md) { - padding: divide($container-padding-x, 2); - } - .userpic { align-items: center; margin-right: 0; @@ -491,6 +512,7 @@ a, a:link { border: none; + cursor: pointer; height: auto; margin: 0; padding: 0; @@ -534,6 +556,12 @@ } &:global(.loginbtn) { + background: #e9e9ee; + + @include media-breakpoint-up(xl) { + background: none; + } + .icon { height: 2.4rem; width: 2.4rem; diff --git a/src/components/Nav/Header/Header.tsx b/src/components/Nav/Header/Header.tsx index 4ce186bd..b5743b6d 100644 --- a/src/components/Nav/Header/Header.tsx +++ b/src/components/Nav/Header/Header.tsx @@ -67,7 +67,7 @@ export const Header = (props: Props) => { let windowScrollTop = 0 createEffect(() => { - const mainContent = document.querySelector('.main-content') as HTMLDivElement + const mainContent = document.querySelector('.main-content') if (fixed() || modal() !== null) { windowScrollTop = window.scrollY @@ -173,6 +173,11 @@ export const Header = (props: Props) => {