From 43b3de572b6013fdccd080d08c4121bf7e4d4bde Mon Sep 17 00:00:00 2001 From: Untone Date: Wed, 20 Dec 2023 19:54:20 +0300 Subject: [PATCH] notification-group-view --- public/locales/en/translation.json | 10 +- public/locales/ru/translation.json | 10 +- src/components/Article/FullArticle.tsx | 2 +- .../Author/AuthorBadge/AuthorBadge.tsx | 1 - src/components/Inbox/CreateModalContent.tsx | 2 +- .../Nav/SearchModal/SearchModal.tsx | 6 +- .../NotificationView/NotificationGroup.tsx | 194 ++++++++++++++++++ .../NotificationView/NotificationView.tsx | 164 --------------- .../NotificationView/index.ts | 2 +- .../NotificationsPanel/NotificationsPanel.tsx | 52 ++--- .../ProfileSettings/ProfileSettings.tsx | 10 +- src/components/Views/Feed/Feed.tsx | 4 +- src/components/Views/Inbox.tsx | 1 - src/components/Views/Search.tsx | 6 +- src/components/Views/Topic.tsx | 2 +- src/context/notifications.tsx | 9 +- src/context/profile.tsx | 5 +- src/context/reactions.tsx | 7 +- src/context/session.tsx | 20 +- src/pages/expo/expo.page.server.ts | 2 +- src/stores/zine/articles.ts | 1 - 21 files changed, 256 insertions(+), 254 deletions(-) create mode 100644 src/components/NotificationsPanel/NotificationView/NotificationGroup.tsx delete mode 100644 src/components/NotificationsPanel/NotificationView/NotificationView.tsx diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 660fb909..33bf7fa8 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -236,12 +236,6 @@ "No notifications yet": "No notifications yet", "Nothing here yet": "There's nothing here yet", "Nothing is here": "There is nothing here", - "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}}", "Notifications": "Notifications", "Or paste a link to an image": "Or paste a link to an image", "Ordered list": "Ordered list", @@ -318,6 +312,8 @@ "Slug": "Slug", "Social networks": "Social networks", "Society": "Society", + "Some new comments to your publication": "{commentsCount, plural, one {New comment} other {{commentsCount} comments}} to your publication", + "Some new replies to your comment": "{commentsCount, plural, one {New reply} other {{commentsCount} replays}} to your publication", "Something went wrong, check email and password": "Something went wrong. Check your email and password", "Something went wrong, please try again": "Something went wrong, please try again", "Song lyrics": "Song lyrics...", @@ -421,6 +417,7 @@ "actions": "actions", "add link": "add link", "all topics": "all topics", + "and some more authors": "{restUsersCount, plural, =0 {} one { and one more user} other { and more {restUsersCount} users}}", "article": "article", "author": "author", "authors": "authors", @@ -442,6 +439,7 @@ "feed": "feed", "follower": "follower", "followersWithCount": "{count} {count, plural, one {follower} other {followers}}", + "from": "from", "header 1": "header 1", "header 2": "header 2", "header 3": "header 3", diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index 5a111947..25cd394c 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -248,12 +248,6 @@ "No such account, please try to register": "Такой адрес не найден, попробуйте зарегистрироваться", "Nothing here yet": "Здесь пока ничего нет", "Nothing is here": "Здесь ничего нет", - "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} пользователей}}", "Notifications": "Уведомления", "Or paste a link to an image": "Или вставьте ссылку на изображение", "Ordered list": "Нумерованный список", @@ -338,6 +332,8 @@ "Slug": "Постоянная ссылка", "Social networks": "Социальные сети", "Society": "Общество", + "Some new comments to your publication": "{commentsCount, plural, one {Новый комментарий} few {{commentsCount} новых комментария} other {{commentsCount} новых комментариев}} к вашей публикации", + "Some new replies to your comment": "{commentsCount, plural, one {Новый ответ} few {{commentsCount} новых ответа} other {{commentsCount} новых ответов}} на ваш комментарий к публикации", "Something went wrong, check email and password": "Что-то пошло не так. Проверьте адрес электронной почты и пароль", "Something went wrong, please try again": "Что-то пошло не так, попробуйте еще раз", "Song lyrics": "Текст песни...", @@ -444,6 +440,7 @@ "actions": "действия", "add link": "добавить ссылку", "all topics": "все темы", + "and some more authors": "{restUsersCount, plural, =0 {} one { и ещё 1 пользователя} few { и ещё {restUsersCount} пользователей} other { и ещё {restUsersCount} пользователей}}", "article": "статья", "author": "автор", "authors": "авторы", @@ -467,6 +464,7 @@ "feed": "лента", "follower": "подписчик", "followersWithCount": "{count} {count, plural, one {подписчик} few {подписчика} other {подписчиков}}", + "from": "от", "header 1": "заголовок 1", "header 2": "заголовок 2", "header 3": "заголовок 3", diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index 0d6e2877..daa35ccc 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -299,7 +299,7 @@ export const FullArticle = (props: Props) => { const ogTitle = props.article.title const keywords = getKeywords(props.article) const getAuthorName = (a: Author) => { - return lang() == 'en' && isCyrillic(a.name) ? capitalize(a.slug.replace(/-/, ' ')) : a.name + return lang() === 'en' && isCyrillic(a.name) ? capitalize(a.slug.replace(/-/, ' ')) : a.name } return ( <> diff --git a/src/components/Author/AuthorBadge/AuthorBadge.tsx b/src/components/Author/AuthorBadge/AuthorBadge.tsx index 6c1400e5..9f648ff0 100644 --- a/src/components/Author/AuthorBadge/AuthorBadge.tsx +++ b/src/components/Author/AuthorBadge/AuthorBadge.tsx @@ -4,7 +4,6 @@ import { createMemo, createSignal, Match, Show, Switch } from 'solid-js' import { useLocalize } from '../../../context/localize' import { useSession } from '../../../context/session' -import { ChatMember } from '../../../graphql/schema/chat.gen' import { Author, FollowingEntity } from '../../../graphql/schema/core.gen' import { router, useRouter } from '../../../stores/router' import { follow, unfollow } from '../../../stores/zine/common' diff --git a/src/components/Inbox/CreateModalContent.tsx b/src/components/Inbox/CreateModalContent.tsx index 973cb195..26c8dfaf 100644 --- a/src/components/Inbox/CreateModalContent.tsx +++ b/src/components/Inbox/CreateModalContent.tsx @@ -59,7 +59,7 @@ const CreateModalContent = (props: Props) => { const handleCreate = async () => { try { const initChat = await actions.createChat(usersId(), chatTitle()) - console.debug('[components.Inbox] create chat result: ', initChat) + console.debug('[components.Inbox] create chat result:', initChat) hideModal() await actions.loadChats() } catch (error) { diff --git a/src/components/Nav/SearchModal/SearchModal.tsx b/src/components/Nav/SearchModal/SearchModal.tsx index 6b87942e..9dad8cdb 100644 --- a/src/components/Nav/SearchModal/SearchModal.tsx +++ b/src/components/Nav/SearchModal/SearchModal.tsx @@ -1,12 +1,12 @@ import { clsx } from 'clsx' +import { createEffect, createSignal } from 'solid-js' import { useLocalize } from '../../../context/localize' +import { apiClient } from '../../../graphql/client/core' +import { SearchResult } from '../../../graphql/schema/core.gen' import { Icon } from '../../_shared/Icon' import styles from './SearchModal.module.scss' -import { apiClient } from '../../../graphql/client/core' -import { createEffect, createSignal } from 'solid-js' -import { SearchResult } from '../../../graphql/schema/core.gen' export const SearchModal = () => { const { t } = useLocalize() diff --git a/src/components/NotificationsPanel/NotificationView/NotificationGroup.tsx b/src/components/NotificationsPanel/NotificationView/NotificationGroup.tsx new file mode 100644 index 00000000..20a180d4 --- /dev/null +++ b/src/components/NotificationsPanel/NotificationView/NotificationGroup.tsx @@ -0,0 +1,194 @@ +import { getPagePath, openPage } from '@nanostores/router' +import { clsx } from 'clsx' +import { createEffect, For, Show } from 'solid-js' + +import { useLocalize } from '../../../context/localize' +import { useNotifications } from '../../../context/notifications' +import { Reaction } from '../../../graphql/schema/core.gen' +import { Notification } from '../../../graphql/schema/notifier.gen' +import { useRouter, router } from '../../../stores/router' +import { GroupAvatar } from '../../_shared/GroupAvatar' +import { TimeAgo } from '../../_shared/TimeAgo' +import { ArticlePageSearchParams } from '../../Article/FullArticle' + +import styles from './NotificationView.module.scss' + +type NotificationGroupProps = { + notifications: Notification[] + onClick: () => void + dateTimeFormat: 'ago' | 'time' | 'date' + class?: string +} + +const getTitle = (title: string) => { + let shoutTitle = '' + let i = 0 + const shoutTitleWords = title.split(' ') + + while (shoutTitle.length <= 30 && i < shoutTitleWords.length) { + shoutTitle += shoutTitleWords[i] + ' ' + i++ + } + + if (shoutTitle.length < title.length) { + shoutTitle = `${shoutTitle.trim()}...` + + if (shoutTitle[0] === '«') { + shoutTitle += '»' + } + } + return shoutTitle +} + +const reactionsCaption = (threadId: string) => + threadId.includes('__') ? 'Some new replies to your comment' : 'Some new comments to your publication' + +export const NotificationGroup = (props: NotificationGroupProps) => { + const { t, formatTime, formatDate } = useLocalize() + const { changeSearchParam } = useRouter() + const { + actions: { hideNotificationsPanel, markNotificationAsRead }, + } = useNotifications() + const threads = new Map() + const notificationsByThread = new Map() + + const handleClick = (threadId: string) => { + props.onClick() + + notificationsByThread.get(threadId).forEach((n: Notification) => { + if (!n.seen) markNotificationAsRead(n) + }) + + const threadParts = threadId.replace('::seen', '').split('__') + openPage(router, 'article', { slug: threadParts[0] }) + if (threadParts.length > 1) { + changeSearchParam({ commentId: threadParts[1] }) + } + } + + createEffect(() => { + const threadsLatest = {} + + // count occurencies and sort reactions by threads + props.notifications.forEach((n: Notification) => { + const reaction = JSON.parse(n.payload) + const slug = reaction.shout.slug + const timestamp = reaction.created_at + // threadId never shows up and looks like - + const threadId = slug + (reaction.reply_to ? `__${reaction.reply_to}` : '') + (n.seen ? `::seen` : '') + const rrr = threads.get(threadId) || [] + const nnn = notificationsByThread.get(threadId) || [] + switch (n.entity) { + case 'reaction': { + switch (n.action) { + case 'create': { + rrr.push(reaction) + threads.set(threadId, rrr) + nnn.push(n) + notificationsByThread.set(threadId, nnn) + if (!(threadId in threadsLatest)) threadsLatest[threadId] = timestamp + else if (timestamp > threadsLatest) threadsLatest[threadId] = timestamp + + break + } + case 'delete': { + // TODO: remove reaction from thread, update storage + + break + } + case 'update': { + // TODO: ignore for thread, update in storage + + break + } + // No default + } + + break + } + case 'shout': { + // TODO: handle notification about the + // new shout from subscribed author, topic + // or community (means all for one community) + + break + } + case 'follower': { + // TODO: handle new follower notification + + break + } + default: + // bypass chat and messages SSE + } + }) + + // sort reactions and threads by created_at + Object.entries(threads) + .sort(([ak, _av], [bk, _bv]) => threadsLatest[bk] - threadsLatest[ak]) + .forEach(([threadId, reaction], _idx, _arr) => { + const rrr = threads.get(threadId) || [] + if (!rrr.includes(reaction)) { + const updatedReactions: Reaction[] = [...rrr, reaction].sort( + (a, b) => b.created_at - a.created_at, + ) + threads.set(threadId, updatedReactions) + } + }) + }) + const handleLinkClick = (event: MouseEvent | TouchEvent) => { + event.stopPropagation() + hideNotificationsPanel() + } + + return ( + <> + + {([threadId, reactions], _index) => ( + <> + {t(reactionsCaption(threadId), { commentsCount: reactions.length })}{' '} +
handleClick(threadId)} + > +
+ r.created_by)} /> +
+ + +
+ + + + + + {formatTime(new Date(reactions[-1].created_at))} + + + + {formatDate(new Date(reactions[-1].created_at), { month: 'numeric', year: '2-digit' })} + +
+
+ + )} +
+ + ) +} diff --git a/src/components/NotificationsPanel/NotificationView/NotificationView.tsx b/src/components/NotificationsPanel/NotificationView/NotificationView.tsx deleted file mode 100644 index f355344b..00000000 --- a/src/components/NotificationsPanel/NotificationView/NotificationView.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import type { ArticlePageSearchParams } from '../../Article/FullArticle' - -import { getPagePath, openPage } from '@nanostores/router' -import { clsx } from 'clsx' -import { createMemo, createSignal, onMount, Show } from 'solid-js' - -import { useLocalize } from '../../../context/localize' -import { useNotifications } from '../../../context/notifications' -import { Reaction } from '../../../graphql/schema/core.gen' -import { Notification } from '../../../graphql/schema/notifier.gen' -import { router, useRouter } from '../../../stores/router' -import { GroupAvatar } from '../../_shared/GroupAvatar' -import { TimeAgo } from '../../_shared/TimeAgo' - -import styles from './NotificationView.module.scss' - -type Props = { - notification: Notification - onClick: () => void - dateTimeFormat: 'ago' | 'time' | 'date' - class?: string -} - -export const NotificationView = (props: Props) => { - const { - actions: { markNotificationAsRead, hideNotificationsPanel }, - } = useNotifications() - - const { changeSearchParam } = useRouter() // TODO: use search params - - const { t, formatDate, formatTime } = useLocalize() - - const [data, setData] = createSignal(null) // NOTE: supports only SSMessage.entity == "reaction" - - onMount(() => { - setTimeout(() => setData(JSON.parse(props.notification.payload))) - }) - - const lastUser = createMemo(() => data().created_by) - - const handleLinkClick = (event: MouseEvent) => { - event.stopPropagation() - hideNotificationsPanel() - } - - const content = createMemo(() => { - if (!data()) { - return null - } - - let shoutTitle = '' - let i = 0 - const shoutTitleWords = data().shout.title.split(' ') - - while (shoutTitle.length <= 30 && i < shoutTitleWords.length) { - shoutTitle += shoutTitleWords[i] + ' ' - i++ - } - - if (shoutTitle.length < data().shout.title.length) { - shoutTitle = `${shoutTitle.trim()}...` - - if (shoutTitle[0] === '«') { - shoutTitle += '»' - } - } - - switch (props.notification.action) { - case 'create': { - if (data()?.reply_to) { - return ( - <> - {t('NotificationNewReplyText1', { - commentsCount: 0, // FIXME: props.notification.occurrences, - })}{' '} - - {shoutTitle} - {' '} - {t('NotificationNewReplyText2')}{' '} - - {lastUser().name} - {' '} - {t('NotificationNewReplyText3', { - restUsersCount: 0, // FIXME: data().users.length - 1, - })} - - ) - } else { - return ( - <> - {t('NotificationNewCommentText1', { - commentsCount: 0, // FIXME: props.notification.occurrences, - })}{' '} - - {shoutTitle} - {' '} - {t('NotificationNewCommentText2')}{' '} - - {lastUser().name} - {' '} - {t('NotificationNewCommentText3', { - restUsersCount: 0, // FIXME: data().users.length - 1, - })} - - ) - } - } - default: { - return <> - } - } - }) - - const handleClick = () => { - props.onClick() - - if (!props.notification.seen) { - markNotificationAsRead(props.notification) - } - - openPage(router, 'article', { slug: data().shout.slug }) - // FIXME: - // if (data().reactionIds) { - // changeSearchParam({ commentId: data().reactionIds[0].toString() }) - // } - } - - const formattedDateTime = createMemo(() => { - switch (props.dateTimeFormat) { - case 'ago': { - return - } - case 'time': { - return formatTime(new Date(props.notification.created_at)) - } - case 'date': { - return formatDate(new Date(props.notification.created_at), { month: 'numeric', year: '2-digit' }) - } - } - }) - - return ( - -
-
- -
-
{content()}
-
{formattedDateTime()}
-
-
- ) -} diff --git a/src/components/NotificationsPanel/NotificationView/index.ts b/src/components/NotificationsPanel/NotificationView/index.ts index 59dcafc6..7688c7bf 100644 --- a/src/components/NotificationsPanel/NotificationView/index.ts +++ b/src/components/NotificationsPanel/NotificationView/index.ts @@ -1 +1 @@ -export { NotificationView } from './NotificationView' +export { NotificationGroup } from './NotificationGroup' diff --git a/src/components/NotificationsPanel/NotificationsPanel.tsx b/src/components/NotificationsPanel/NotificationsPanel.tsx index 10ab22fd..44d1edb3 100644 --- a/src/components/NotificationsPanel/NotificationsPanel.tsx +++ b/src/components/NotificationsPanel/NotificationsPanel.tsx @@ -1,5 +1,5 @@ import { clsx } from 'clsx' -import { createEffect, createMemo, createSignal, For, on, onCleanup, onMount, Show } from 'solid-js' +import { createEffect, createMemo, createSignal, on, onCleanup, onMount, Show } from 'solid-js' import { throttle } from 'throttle-debounce' import { useLocalize } from '../../context/localize' @@ -11,7 +11,7 @@ import { Button } from '../_shared/Button' import { Icon } from '../_shared/Icon' import { EmptyMessage } from './EmptyMessage' -import { NotificationView } from './NotificationView' +import { NotificationGroup } from './NotificationView/NotificationGroup' import styles from './NotificationsPanel.module.scss' @@ -186,42 +186,30 @@ export const NotificationsPanel = (props: Props) => {
0}>
{t('today')}
- - {(notification) => ( - - )} - +
0}>
{t('yesterday')}
- - {(notification) => ( - - )} - +
0}>
{t('earlier')}
- - {(notification) => ( - - )} - +
diff --git a/src/components/ProfileSettings/ProfileSettings.tsx b/src/components/ProfileSettings/ProfileSettings.tsx index 6d3dd593..fc3fab13 100644 --- a/src/components/ProfileSettings/ProfileSettings.tsx +++ b/src/components/ProfileSettings/ProfileSettings.tsx @@ -48,11 +48,9 @@ export const ProfileSettings = () => { const { actions: { showSnackbar }, } = useSnackbar() - const { - actions: { setUser, authorizer }, + actions: { loadAuthor }, } = useSession() - const { actions: { showConfirm }, } = useConfirm() @@ -101,10 +99,8 @@ export const ProfileSettings = () => { } showSnackbar({ type: 'error', body: t('Error') }) } - const profile = await authorizer().getProfile() - if (profile) { - setUser(profile) - } + + await loadAuthor() // renews author's profile } const handleCancel = async () => { diff --git a/src/components/Views/Feed/Feed.tsx b/src/components/Views/Feed/Feed.tsx index 88ceb650..075a6e33 100644 --- a/src/components/Views/Feed/Feed.tsx +++ b/src/components/Views/Feed/Feed.tsx @@ -52,7 +52,7 @@ type Props = { } export const FeedView = (props: Props) => { - const { t, lang } = useLocalize() + const { t } = useLocalize() const { page, searchParams } = useRouter() const [isLoading, setIsLoading] = createSignal(false) const [isRightColumnLoaded, setIsRightColumnLoaded] = createSignal(false) @@ -238,7 +238,7 @@ export const FeedView = (props: Props) => { />
- +
diff --git a/src/components/Views/Inbox.tsx b/src/components/Views/Inbox.tsx index 3816ddda..4871d4c5 100644 --- a/src/components/Views/Inbox.tsx +++ b/src/components/Views/Inbox.tsx @@ -17,7 +17,6 @@ import DialogCard from '../Inbox/DialogCard' import DialogHeader from '../Inbox/DialogHeader' import { Message } from '../Inbox/Message' import MessagesFallback from '../Inbox/MessagesFallback' -import QuotedMessage from '../Inbox/QuotedMessage' import Search from '../Inbox/Search' import { Modal } from '../Nav/Modal' diff --git a/src/components/Views/Search.tsx b/src/components/Views/Search.tsx index f10f1920..83af2114 100644 --- a/src/components/Views/Search.tsx +++ b/src/components/Views/Search.tsx @@ -1,4 +1,4 @@ -import type { SearchResult, Shout } from '../../graphql/schema/core.gen' +import type { SearchResult } from '../../graphql/schema/core.gen' import { Show, For, createSignal } from 'solid-js' @@ -22,7 +22,7 @@ const LOAD_MORE_PAGE_SIZE = 50 export const SearchView = (props: Props) => { const { t } = useLocalize() - const { articleEntities, sortedArticles } = useArticlesStore() + const { sortedArticles } = useArticlesStore() const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false) const [query, setQuery] = createSignal(props.query) const [offset, setOffset] = createSignal(0) @@ -54,7 +54,7 @@ export const SearchView = (props: Props) => { name="q" ref={searchEl} onInput={handleQueryChange} - placeholder={t('Enter text') + '...'} + placeholder={query() || t('Enter text') + '...'} />
diff --git a/src/components/Views/Topic.tsx b/src/components/Views/Topic.tsx index 7e377690..3ea7a27c 100644 --- a/src/components/Views/Topic.tsx +++ b/src/components/Views/Topic.tsx @@ -55,7 +55,7 @@ export const TopicView = (props: Props) => { ) const title = () => `#${capitalize( - lang() == 'en' ? topic()?.slug.replace(/-/, ' ') : topic()?.title || topic()?.slug.replace(/-/, ' '), + lang() === 'en' ? topic()?.slug.replace(/-/, ' ') : topic()?.title || topic()?.slug.replace(/-/, ' '), true, )}` onMount(() => (document.title = title())) diff --git a/src/context/notifications.tsx b/src/context/notifications.tsx index 382a32de..1093a60a 100644 --- a/src/context/notifications.tsx +++ b/src/context/notifications.tsx @@ -1,7 +1,7 @@ import type { Accessor, JSX } from 'solid-js' import { createStorageSignal } from '@solid-primitives/storage' -import { createContext, createEffect, createMemo, createSignal, onMount, useContext } from 'solid-js' +import { createContext, createMemo, createSignal, onMount, useContext } from 'solid-js' import { createStore } from 'solid-js/store' import { Portal } from 'solid-js/web' @@ -41,7 +41,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => { const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0) const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0) const [notificationEntities, setNotificationEntities] = createStore>({}) - const { isAuthenticated } = useSession() + const { isAuthenticated, author } = useSession() const { addHandler } = useConnect() const loadNotifications = async (options: { after: number; limit?: number; offset?: number }) => { @@ -81,10 +81,11 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => { const markNotificationAsRead = async (notification: Notification) => { if (notifierClient.private) await notifierClient.markNotificationAsRead(notification.id) - const nnn = new Set([...notification.seen, notification.id]) - setNotificationEntities(notification.id, 'seen', [...nnn]) + notification.seen.push(author().id) + setNotificationEntities((nnn: Notification) => ({ ...nnn, [notification.id]: notification })) setUnreadNotificationsCount((oldCount) => oldCount - 1) } + const markAllNotificationsAsRead = async () => { if (isAuthenticated() && notifierClient.private) { await notifierClient.markAllNotificationsAsRead() diff --git a/src/context/profile.tsx b/src/context/profile.tsx index 3688f60b..55363636 100644 --- a/src/context/profile.tsx +++ b/src/context/profile.tsx @@ -30,10 +30,7 @@ const userpicUrl = (userpic: string) => { return userpic } export const ProfileFormProvider = (props: { children: JSX.Element }) => { - const { - author, - actions: { getToken }, - } = useSession() + const { author } = useSession() const [form, setForm] = createStore({}) const currentSlug = createMemo(() => author()?.slug) diff --git a/src/context/reactions.tsx b/src/context/reactions.tsx index c9893b6c..8588e413 100644 --- a/src/context/reactions.tsx +++ b/src/context/reactions.tsx @@ -1,13 +1,11 @@ import type { JSX } from 'solid-js' -import { createContext, createMemo, onCleanup, useContext } from 'solid-js' +import { createContext, onCleanup, useContext } from 'solid-js' import { createStore, reconcile } from 'solid-js/store' import { apiClient } from '../graphql/client/core' import { Reaction, ReactionBy, ReactionInput, ReactionKind } from '../graphql/schema/core.gen' -import { useSession } from './session' - type ReactionsContextType = { reactionEntities: Record actions: { @@ -34,9 +32,6 @@ export function useReactions() { export const ReactionsProvider = (props: { children: JSX.Element }) => { const [reactionEntities, setReactionEntities] = createStore>({}) - const { - actions: { getToken }, - } = useSession() const loadReactionsBy = async ({ by, diff --git a/src/context/session.tsx b/src/context/session.tsx index a8314bad..1af9fb7c 100644 --- a/src/context/session.tsx +++ b/src/context/session.tsx @@ -48,6 +48,7 @@ export type SessionContextType = { loadSession: () => AuthToken | Promise setSession: (token: AuthToken | null) => void // setSession loadAuthor: (info?: unknown) => Author | Promise + setAuthor: (a: Author) => void loadSubscriptions: () => Promise requireAuthentication: ( callback: (() => Promise) | (() => void), @@ -92,14 +93,14 @@ export const SessionProvider = (props: { Authorization: tkn, }) if (authResult?.access_token) { - mutate(authResult) + setSession(authResult) // console.debug('[context.session] token after:', authResult.access_token) await loadSubscriptions() return authResult } } catch (error) { console.error('[context.session] getSession error:', error) - mutate(null) + setSession(null) return null } finally { setTimeout(() => { @@ -108,7 +109,7 @@ export const SessionProvider = (props: { } } - const [session, { refetch: loadSession, mutate }] = createResource(getSession, { + const [session, { refetch: loadSession, mutate: setSession }] = createResource(getSession, { ssrLoadFrom: 'initial', initialValue: null, }) @@ -142,7 +143,7 @@ export const SessionProvider = (props: { } } - const [author, { refetch: loadAuthor }] = createResource( + const [author, { refetch: loadAuthor, mutate: setAuthor }] = createResource( async () => { const u = session()?.user if (u) { @@ -162,7 +163,7 @@ export const SessionProvider = (props: { const authResult: AuthToken | void = await authorizer().login(params) if (authResult && authResult.access_token) { - mutate(authResult) + setSession(authResult) await loadSubscriptions() console.debug('[context.session] signed in') } else { @@ -211,7 +212,7 @@ export const SessionProvider = (props: { setIsAuthWithCallback(() => callback) const userdata = await authorizer().getProfile() - if (userdata) mutate({ ...session(), user: userdata }) + if (userdata) setSession({ ...session(), user: userdata }) if (!isAuthenticated()) { showModal('auth', modalSource) @@ -220,7 +221,7 @@ export const SessionProvider = (props: { const signOut = async () => { await authorizer().logout() - mutate(null) + setSession(null) setSubscriptions(EMPTY_SUBSCRIPTIONS) showSnackbar({ body: t("You've successfully logged out") }) } @@ -228,7 +229,7 @@ export const SessionProvider = (props: { const confirmEmail = async (input: VerifyEmailInput) => { console.debug(`[context.session] calling authorizer's verify email with`, input) const at: void | AuthToken = await authorizer().verifyEmail(input) - if (at) mutate(at) + if (at) setSession(at) console.log(`[context.session] confirmEmail got result ${at}`) } @@ -243,7 +244,8 @@ export const SessionProvider = (props: { signOut, confirmEmail, setIsSessionLoaded, - setSession: mutate, + setSession, + setAuthor, authorizer, loadAuthor, } diff --git a/src/pages/expo/expo.page.server.ts b/src/pages/expo/expo.page.server.ts index db36b2ce..49970f3a 100644 --- a/src/pages/expo/expo.page.server.ts +++ b/src/pages/expo/expo.page.server.ts @@ -8,7 +8,7 @@ export const onBeforeRender = async (pageContext: PageContext) => { const { layout } = pageContext.routeParams const expoShouts = await apiClient.getShouts({ - filters: { layouts: ['audio', 'video', 'literature', 'image'] }, + filters: { layouts: layout ? [layout] : ['audio', 'video', 'literature', 'image'] }, limit: PRERENDERED_ARTICLES_COUNT, }) diff --git a/src/stores/zine/articles.ts b/src/stores/zine/articles.ts index d42cf3ee..bb9b393b 100644 --- a/src/stores/zine/articles.ts +++ b/src/stores/zine/articles.ts @@ -1,7 +1,6 @@ import type { Author, Shout, - ShoutInput, LoadShoutsOptions, QueryLoad_Shouts_SearchArgs, } from '../../graphql/schema/core.gen'