diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 97824a78..6808b96c 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -200,6 +200,7 @@ "Manifest": "Manifest", "Manifesto": "Manifesto", "Many files, choose only one": "Many files, choose only one", + "Mark as read": "Mark as read", "Material card": "Material card", "Message": "Message", "More": "More", diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index 87576d99..9f7333f8 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -209,6 +209,7 @@ "Manifest": "Манифест", "Manifesto": "Манифест", "Many files, choose only one": "Много файлов, выберете один", + "Mark as read": "Отметить прочитанным", "Material card": "Карточка материала", "Message": "Написать", "More": "Ещё", diff --git a/src/components/Author/Userpic/Userpic.module.scss b/src/components/Author/Userpic/Userpic.module.scss index cfa5ef80..f72c1c1d 100644 --- a/src/components/Author/Userpic/Userpic.module.scss +++ b/src/components/Author/Userpic/Userpic.module.scss @@ -75,6 +75,15 @@ } } + &.L { + height: 40px; + width: 40px; + min-width: 40px; + .letters { + font-size: 1.2rem; + } + } + &.XL { aspect-ratio: 1/1; margin: 0 auto 1rem; diff --git a/src/components/Editor/SimplifiedEditor.tsx b/src/components/Editor/SimplifiedEditor.tsx index 534a28ad..2e29f7d4 100644 --- a/src/components/Editor/SimplifiedEditor.tsx +++ b/src/components/Editor/SimplifiedEditor.tsx @@ -53,7 +53,7 @@ type Props = { onlyBubbleControls?: boolean controlsAlwaysVisible?: boolean autoFocus?: boolean - isCancelButtonVisible: boolean + isCancelButtonVisible?: boolean } export const MAX_DESCRIPTION_LIMIT = 400 diff --git a/src/components/NotificationsPanel/EmptyMessage/EmptyMessage.module.scss b/src/components/NotificationsPanel/EmptyMessage/EmptyMessage.module.scss index b1484a0d..947b9ada 100644 --- a/src/components/NotificationsPanel/EmptyMessage/EmptyMessage.module.scss +++ b/src/components/NotificationsPanel/EmptyMessage/EmptyMessage.module.scss @@ -5,6 +5,7 @@ font-size: 15px; line-height: 24px; white-space: pre-line; + padding: 4rem 0; } .title { diff --git a/src/components/NotificationsPanel/NotificationView/NotificationView.module.scss b/src/components/NotificationsPanel/NotificationView/NotificationView.module.scss index 3c3aaf77..3a326439 100644 --- a/src/components/NotificationsPanel/NotificationView/NotificationView.module.scss +++ b/src/components/NotificationsPanel/NotificationView/NotificationView.module.scss @@ -1,17 +1,19 @@ .NotificationView { + @include font-size(1.5rem); + display: flex; - align-items: center; - height: 72px; + align-items: flex-start; + min-height: 72px; margin-left: -16px; border-radius: 16px; padding: 16px; background-color: var(--yellow-50); // TODO: check markup - font-size: 15px; // font-weight: 700; line-height: 20px; cursor: pointer; transition: background-color 100ms; + max-width: 700px; &.seen { background-color: transparent; diff --git a/src/components/NotificationsPanel/NotificationsPanel.module.scss b/src/components/NotificationsPanel/NotificationsPanel.module.scss index ee997569..26be7838 100644 --- a/src/components/NotificationsPanel/NotificationsPanel.module.scss +++ b/src/components/NotificationsPanel/NotificationsPanel.module.scss @@ -10,7 +10,6 @@ $transition-duration: 200ms; bottom: 0; width: 0; z-index: 10000; - background-color: rgb(0 0 0 / 0%); overflow: hidden; transition: background-color $transition-duration, @@ -18,12 +17,49 @@ $transition-duration: 200ms; .panel { position: relative; - background-color: #fff; - width: 700px; - padding: 48px 96px 96px 48px; + background-color: var(--background-color); + width: 50%; + height: 100%; transform: translateX(100%); transition: transform $transition-duration; - overflow-y: auto; + display: flex; + flex-direction: column; + + .title { + @include font-size(2rem); + + color: var(--black-500); + font-style: normal; + font-weight: 700; + line-height: 36px; + position: sticky; + top: 0; + padding: 16px 38px; + border-bottom: 1px solid var(--black-100); + } + + .content { + overflow-y: auto; + flex: 1; + padding: 0 38px 1rem; + + .loading { + @include font-size(1.2rem); + + text-align: center; + padding: 1rem; + color: var(--black-300); + } + } + + .actions { + padding: 24px 38px; + width: 100%; + bottom: 0; + left: 0; + background: var(--background-color); + border-top: 1px solid var(--black-100); + } } &.isOpened { @@ -39,16 +75,6 @@ $transition-duration: 200ms; } } -.title { - // TODO: check markup - color: var(--black-500, #141414); - font-size: 32px; - font-style: normal; - font-weight: 700; - line-height: 36px; - margin-bottom: 32px; -} - .closeButton { position: absolute; top: 0; diff --git a/src/components/NotificationsPanel/NotificationsPanel.tsx b/src/components/NotificationsPanel/NotificationsPanel.tsx index 3c7c09fa..3c772a93 100644 --- a/src/components/NotificationsPanel/NotificationsPanel.tsx +++ b/src/components/NotificationsPanel/NotificationsPanel.tsx @@ -4,10 +4,13 @@ import { useEscKeyDownHandler } from '../../utils/useEscKeyDownHandler' import { useOutsideClickHandler } from '../../utils/useOutsideClickHandler' import { useLocalize } from '../../context/localize' import { Icon } from '../_shared/Icon' -import { createEffect, createMemo, For, Show } from 'solid-js' -import { useNotifications } from '../../context/notifications' +import { createEffect, createMemo, createSignal, For, on, onCleanup, onMount, Show } from 'solid-js' +import { PAGE_SIZE, useNotifications } from '../../context/notifications' import { NotificationView } from './NotificationView' import { EmptyMessage } from './EmptyMessage' +import { Button } from '../_shared/Button' +import throttle from 'just-throttle' +import { useSession } from '../../context/session' type Props = { isOpen: boolean @@ -39,8 +42,17 @@ const isEarlier = (date: Date) => { } export const NotificationsPanel = (props: Props) => { + const [isLoading, setIsLoading] = createSignal(false) + + const { isAuthenticated } = useSession() const { t } = useLocalize() - const { sortedNotifications } = useNotifications() + const { + sortedNotifications, + unreadNotificationsCount, + loadedNotificationsCount, + totalNotificationsCount, + actions: { loadNotifications, markAllNotificationsAsRead } + } = useNotifications() const handleHide = () => { props.onClose() } @@ -91,6 +103,57 @@ export const NotificationsPanel = (props: Props) => { return sortedNotifications().filter((notification) => isEarlier(new Date(notification.createdAt))) }) + const scrollContainerRef: { current: HTMLDivElement } = { current: null } + const loadNextPage = async () => { + await loadNotifications({ limit: PAGE_SIZE, offset: loadedNotificationsCount() }) + if (loadedNotificationsCount() < totalNotificationsCount()) { + const hasMore = scrollContainerRef.current.scrollHeight <= scrollContainerRef.current.offsetHeight + + if (hasMore) { + await loadNextPage() + } + } + } + const handleScroll = async () => { + if (!scrollContainerRef.current || isLoading()) { + return + } + if (totalNotificationsCount() === loadedNotificationsCount()) { + return + } + + const isNearBottom = + scrollContainerRef.current.scrollHeight - scrollContainerRef.current.scrollTop <= + scrollContainerRef.current.clientHeight * 1.5 + + if (isNearBottom) { + setIsLoading(true) + await loadNextPage() + setIsLoading(false) + } + } + const handleScrollThrottled = throttle(handleScroll, 50) + + onMount(() => { + scrollContainerRef.current.addEventListener('scroll', handleScrollThrottled) + onCleanup(() => { + scrollContainerRef.current.removeEventListener('scroll', handleScrollThrottled) + }) + }) + + createEffect( + on( + () => isAuthenticated(), + async () => { + if (isAuthenticated()) { + setIsLoading(true) + await loadNextPage() + setIsLoading(false) + } + } + ) + ) + return (