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..a263456e 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.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..705fb083 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -28,11 +28,24 @@ 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) => { + window.scrollTo({ + top: el.offsetTop - 96, + left: 0, + behavior: 'smooth' + }) +} + export const FullArticle = (props: Props) => { const { t } = useLocalize() const { @@ -78,15 +91,12 @@ export const FullArticle = (props: Props) => { }) 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 +115,12 @@ 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/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/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.tsx b/src/components/Nav/Header/Header.tsx index 4ce186bd..855086ac 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 diff --git a/src/components/Nav/Modal/Modal.module.scss b/src/components/Nav/Modal/Modal.module.scss index ebc49a08..f69f99c2 100644 --- a/src/components/Nav/Modal/Modal.module.scss +++ b/src/components/Nav/Modal/Modal.module.scss @@ -71,8 +71,8 @@ } .close { - right: 3.6rem; - top: 12px; + right: 1.6rem; + top: 1.6rem; } } } diff --git a/src/components/Nav/Modal/Modal.tsx b/src/components/Nav/Modal/Modal.tsx index 929c7ab1..0b6e1415 100644 --- a/src/components/Nav/Modal/Modal.tsx +++ b/src/components/Nav/Modal/Modal.tsx @@ -1,4 +1,4 @@ -import { createEffect, createMemo, createSignal, on, Show } from 'solid-js' +import { createEffect, createMemo, createSignal, Show } from 'solid-js' import type { JSX } from 'solid-js' import { clsx } from 'clsx' import { hideModal, useModalStore } from '../../../stores/ui' @@ -8,7 +8,6 @@ import styles from './Modal.module.scss' import { redirectPage } from '@nanostores/router' import { router } from '../../../stores/router' import { Icon } from '../../_shared/Icon' -import { resetSortedArticles } from '../../../stores/zine/articles' interface Props { name: string diff --git a/src/components/NotificationsPanel/EmptyMessage/EmptyMessage.module.scss b/src/components/NotificationsPanel/EmptyMessage/EmptyMessage.module.scss new file mode 100644 index 00000000..b1484a0d --- /dev/null +++ b/src/components/NotificationsPanel/EmptyMessage/EmptyMessage.module.scss @@ -0,0 +1,12 @@ +.EmptyMessage { + // TODO: check markup + color: var(--black-500); + text-align: center; + font-size: 15px; + line-height: 24px; + white-space: pre-line; +} + +.title { + font-weight: 500; +} diff --git a/src/components/NotificationsPanel/EmptyMessage/EmptyMessage.tsx b/src/components/NotificationsPanel/EmptyMessage/EmptyMessage.tsx new file mode 100644 index 00000000..855009ff --- /dev/null +++ b/src/components/NotificationsPanel/EmptyMessage/EmptyMessage.tsx @@ -0,0 +1,14 @@ +import { clsx } from 'clsx' +import styles from './EmptyMessage.module.scss' +import { useLocalize } from '../../../context/localize' + +export const EmptyMessage = () => { + const { t } = useLocalize() + + return ( +
+
{t('No notifications yet')}
+
{t("Write good articles, comment\nand it won't be so empty here")}
+
+ ) +} diff --git a/src/components/NotificationsPanel/EmptyMessage/index.ts b/src/components/NotificationsPanel/EmptyMessage/index.ts new file mode 100644 index 00000000..f26b9a9c --- /dev/null +++ b/src/components/NotificationsPanel/EmptyMessage/index.ts @@ -0,0 +1 @@ +export { EmptyMessage } from './EmptyMessage' diff --git a/src/components/NotificationsPanel/NotificationView/NotificationView.module.scss b/src/components/NotificationsPanel/NotificationView/NotificationView.module.scss index 284af187..26401acd 100644 --- a/src/components/NotificationsPanel/NotificationView/NotificationView.module.scss +++ b/src/components/NotificationsPanel/NotificationView/NotificationView.module.scss @@ -20,6 +20,13 @@ &:hover { background-color: var(--gray-100); } + + a, + a:visited { + padding-bottom: 0 !important; + border-bottom: none !important; + font-weight: 700; + } } .userpic { diff --git a/src/components/NotificationsPanel/NotificationsPanel.tsx b/src/components/NotificationsPanel/NotificationsPanel.tsx index 4041dbed..c47e770f 100644 --- a/src/components/NotificationsPanel/NotificationsPanel.tsx +++ b/src/components/NotificationsPanel/NotificationsPanel.tsx @@ -7,6 +7,7 @@ import { Icon } from '../_shared/Icon' import { createEffect, For } from 'solid-js' import { useNotifications } from '../../context/notifications' import { NotificationView } from './NotificationView' +import { EmptyMessage } from './EmptyMessage' type Props = { isOpen: boolean @@ -30,8 +31,22 @@ export const NotificationsPanel = (props: Props) => { handler: () => handleHide() }) + let windowScrollTop = 0 + createEffect(() => { + const mainContent = document.querySelector('.main-content') + + if (props.isOpen) { + windowScrollTop = window.scrollY + mainContent.style.marginTop = `-${windowScrollTop}px` + } + document.body.classList.toggle('fixed', props.isOpen) + + if (!props.isOpen) { + mainContent.style.marginTop = '' + window.scrollTo(0, windowScrollTop) + } }) useEscKeyDownHandler(handleHide) @@ -52,10 +67,7 @@ export const NotificationsPanel = (props: Props) => {
{t('Notifications')}
- {t('No notifications, yet')}
} - > + }> {(notification) => ( Promise resolveConfirm: (value: boolean) => void } @@ -36,13 +41,17 @@ export const ConfirmProvider = (props: { children: JSX.Element }) => { message: { confirmBody?: ConfirmMessage['confirmBody'] confirmButtonLabel?: ConfirmMessage['confirmButtonLabel'] + confirmButtonVariant?: ConfirmMessage['confirmButtonVariant'] declineButtonLabel?: ConfirmMessage['declineButtonLabel'] + declineButtonVariant?: ConfirmMessage['declineButtonVariant'] } = {} ): Promise => { const messageToShow = { confirmBody: message.confirmBody, confirmButtonLabel: message.confirmButtonLabel, - declineButtonLabel: message.declineButtonLabel + confirmButtonVariant: message.confirmButtonVariant, + declineButtonLabel: message.declineButtonLabel, + declineButtonVariant: message.declineButtonVariant } setConfirmMessage(messageToShow) diff --git a/src/context/notifications.tsx b/src/context/notifications.tsx index 87342f48..f7b99573 100644 --- a/src/context/notifications.tsx +++ b/src/context/notifications.tsx @@ -24,6 +24,7 @@ type NotificationsContextType = { sortedNotifications: Accessor actions: { showNotificationsPanel: () => void + hideNotificationsPanel: () => void markNotificationAsRead: (notification: ServerNotification) => Promise setMessageHandler: (MessageHandler) => void } @@ -129,7 +130,16 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => { setIsNotificationsPanelOpen(true) } - const actions = { showNotificationsPanel, markNotificationAsRead, setMessageHandler } + const hideNotificationsPanel = () => { + setIsNotificationsPanelOpen(false) + } + + const actions = { + setMessageHandler, + showNotificationsPanel, + hideNotificationsPanel, + markNotificationAsRead + } const value: NotificationsContextType = { notificationEntities, diff --git a/src/pages/article.page.tsx b/src/pages/article.page.tsx index c522b76d..c5ed9aac 100644 --- a/src/pages/article.page.tsx +++ b/src/pages/article.page.tsx @@ -11,18 +11,9 @@ import { setPageLoadManagerPromise } from '../utils/pageLoadManager' export const ArticlePage = (props: PageProps) => { const shouts = props.article ? [props.article] : [] + const { page } = useRouter() - const slug = createMemo(() => { - const { page: getPage } = useRouter() - - const page = getPage() - - if (page.route !== 'article') { - throw new Error('ts guard') - } - - return page.params.slug - }) + const slug = createMemo(() => page().params['slug'] as string) const { articleEntities } = useArticlesStore({ shouts