sse-msg-format
This commit is contained in:
parent
121cbfdbdd
commit
294b7da5da
|
@ -3,7 +3,7 @@ import { createMemo, createSignal, onMount, Show } from 'solid-js'
|
||||||
import { Author } from '../../../graphql/types.gen'
|
import { Author } from '../../../graphql/types.gen'
|
||||||
import { openPage } from '@nanostores/router'
|
import { openPage } from '@nanostores/router'
|
||||||
import { router, useRouter } from '../../../stores/router'
|
import { router, useRouter } from '../../../stores/router'
|
||||||
import { ServerNotification, useNotifications } from '../../../context/notifications'
|
import { SSEMessage, useNotifications } from '../../../context/notifications'
|
||||||
import { Userpic } from '../../Author/Userpic'
|
import { Userpic } from '../../Author/Userpic'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import type { ArticlePageSearchParams } from '../../Article/FullArticle'
|
import type { ArticlePageSearchParams } from '../../Article/FullArticle'
|
||||||
|
@ -11,7 +11,7 @@ import { TimeAgo } from '../../_shared/TimeAgo'
|
||||||
import styles from './NotificationView.module.scss'
|
import styles from './NotificationView.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
notification: ServerNotification
|
notification: SSEMessage
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
dateTimeFormat: 'ago' | 'time' | 'date'
|
dateTimeFormat: 'ago' | 'time' | 'date'
|
||||||
class?: string
|
class?: string
|
||||||
|
@ -29,8 +29,9 @@ export enum NotificationType {
|
||||||
|
|
||||||
const TEMPLATES = {
|
const TEMPLATES = {
|
||||||
// FIXME: set proper templates
|
// FIXME: set proper templates
|
||||||
new_follower: 'new follower',
|
'follower:join': 'new follower',
|
||||||
new_shout: 'new shout',
|
'shout:create': 'new shout'
|
||||||
|
/*
|
||||||
new_reaction0: 'new like',
|
new_reaction0: 'new like',
|
||||||
new_reaction1: 'new dislike',
|
new_reaction1: 'new dislike',
|
||||||
new_reaction2: 'new agreement',
|
new_reaction2: 'new agreement',
|
||||||
|
@ -45,13 +46,14 @@ const TEMPLATES = {
|
||||||
//"new_reaction11": "new footnote",
|
//"new_reaction11": "new footnote",
|
||||||
new_reaction12: 'new acception',
|
new_reaction12: 'new acception',
|
||||||
new_reaction13: 'new rejection'
|
new_reaction13: 'new rejection'
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NotificationView = (props: Props) => {
|
export const NotificationView = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
actions: { markNotificationAsRead }
|
actions: { markNotificationAsRead }
|
||||||
} = useNotifications()
|
} = useNotifications()
|
||||||
const [data, setData] = createSignal<ServerNotification>(null)
|
const [data, setData] = createSignal<SSEMessage>(null)
|
||||||
const [kind, setKind] = createSignal<NotificationType>()
|
const [kind, setKind] = createSignal<NotificationType>()
|
||||||
const { changeSearchParam } = useRouter<ArticlePageSearchParams>()
|
const { changeSearchParam } = useRouter<ArticlePageSearchParams>()
|
||||||
const { t, formatDate, formatTime } = useLocalize()
|
const { t, formatDate, formatTime } = useLocalize()
|
||||||
|
@ -60,7 +62,7 @@ export const NotificationView = (props: Props) => {
|
||||||
setTimeout(() => setData(props.notification))
|
setTimeout(() => setData(props.notification))
|
||||||
})
|
})
|
||||||
const lastUser = createMemo(() => {
|
const lastUser = createMemo(() => {
|
||||||
return props.notification.kind === 'new_follower' ? data().payload : data().payload.author
|
return props.notification.entity === 'follower' ? data().payload : data().payload.author
|
||||||
})
|
})
|
||||||
const content = createMemo(() => {
|
const content = createMemo(() => {
|
||||||
if (!data()) {
|
if (!data()) {
|
||||||
|
@ -70,46 +72,39 @@ export const NotificationView = (props: Props) => {
|
||||||
|
|
||||||
// TODO: count occurencies from in-browser notifications-db
|
// TODO: count occurencies from in-browser notifications-db
|
||||||
|
|
||||||
switch (props.notification.kind) {
|
switch (props.notification.entity) {
|
||||||
case 'new_follower': {
|
case 'follower': {
|
||||||
caption = ''
|
caption = ''
|
||||||
author = data().payload
|
author = data().payload
|
||||||
ntype = NotificationType.NewFollower
|
ntype = NotificationType.NewFollower
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'new_shout': {
|
case 'shout': {
|
||||||
caption = data().payload.title
|
caption = data().payload.title
|
||||||
author = data().payload.authors[-1]
|
author = data().payload.authors[-1]
|
||||||
ntype = NotificationType.NewShout
|
ntype = NotificationType.NewShout
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'new_reaction6': {
|
case 'reaction': {
|
||||||
ntype = data().payload.replyTo ? NotificationType.NewReply : NotificationType.NewComment
|
ntype = data().payload.replyTo ? NotificationType.NewReply : NotificationType.NewComment
|
||||||
|
console.log(data().payload.kind)
|
||||||
|
// TODO: handle all needed reaction kinds
|
||||||
}
|
}
|
||||||
case 'new_reaction0': {
|
|
||||||
ntype = NotificationType.NewLike
|
|
||||||
}
|
|
||||||
case 'new_reaction0': {
|
|
||||||
ntype = NotificationType.NewDislike
|
|
||||||
}
|
|
||||||
// TODO: add more reaction types
|
|
||||||
default: {
|
default: {
|
||||||
caption = data().payload.shout.title
|
caption = data().payload.shout.title
|
||||||
author = data().payload.author
|
author = data().payload.author
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setKind(ntype) // FIXME: use it somewhere if needed or remove
|
setKind(ntype) // FIXME: use it somewhere if needed or remove
|
||||||
return t(TEMPLATES[props.notification.kind], { caption, author })
|
return t(TEMPLATES[`${props.notification.entity}:${props.notification.action}`], { caption, author })
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (!props.notification.seen) {
|
if (!props.notification.seen) {
|
||||||
markNotificationAsRead(props.notification)
|
markNotificationAsRead(props.notification)
|
||||||
}
|
}
|
||||||
const subpath = props.notification.kind === 'new_follower' ? 'author' : 'article'
|
const subpath = props.notification.entity === 'follower' ? 'author' : 'article'
|
||||||
const slug = props.notification.kind.startsWith('new_reaction')
|
const slug = props.notification.entity === 'reaction' ? data().payload.shout.slug : data().payload.slug
|
||||||
? data().payload.shout.slug
|
|
||||||
: data().payload.slug
|
|
||||||
openPage(router, subpath, { slug })
|
openPage(router, subpath, { slug })
|
||||||
props.onClick()
|
props.onClick()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,8 @@ import { createContext, createSignal, onMount, useContext } from 'solid-js'
|
||||||
import type { Chat, Message, MutationCreateMessageArgs } from '../graphql/types.gen'
|
import type { Chat, Message, MutationCreateMessageArgs } from '../graphql/types.gen'
|
||||||
import { inboxClient } from '../utils/apiClient'
|
import { inboxClient } from '../utils/apiClient'
|
||||||
import { loadMessages } from '../stores/inbox'
|
import { loadMessages } from '../stores/inbox'
|
||||||
import { ServerNotification, useNotifications } from './notifications'
|
import { SSEMessage, useNotifications } from './notifications'
|
||||||
|
import { M } from '../components/_shared/Button/Button.module.scss'
|
||||||
|
|
||||||
type InboxContextType = {
|
type InboxContextType = {
|
||||||
chats: Accessor<Chat[]>
|
chats: Accessor<Chat[]>
|
||||||
|
@ -29,10 +30,16 @@ export const InboxProvider = (props: { children: JSX.Element }) => {
|
||||||
actions: { setMessageHandler }
|
actions: { setMessageHandler }
|
||||||
} = useNotifications()
|
} = useNotifications()
|
||||||
|
|
||||||
const handleMessage = (n: ServerNotification) => {
|
const handleMessage = (m: SSEMessage) => {
|
||||||
// TODO: handle notification types here: new_message edit_message del_message
|
console.log('[context.inbox] ', m)
|
||||||
const msg = n.payload as Message
|
// TODO: handle all action types: create update delete join left
|
||||||
console.log(msg)
|
if (m.action in ['create', 'update', 'delete']) {
|
||||||
|
const msg = m.payload
|
||||||
|
setMessages((mmm) => [msg, ...mmm])
|
||||||
|
} else if (m.action in ['left', 'join']) {
|
||||||
|
// TODO: set chat members
|
||||||
|
console.debug(m)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|
|
@ -10,22 +10,23 @@ import { fetchEventSource } from '@microsoft/fetch-event-source'
|
||||||
import { getToken } from '../graphql/privateGraphQLClient'
|
import { getToken } from '../graphql/privateGraphQLClient'
|
||||||
import { Author, Message, Reaction, Shout } from '../graphql/types.gen'
|
import { Author, Message, Reaction, Shout } from '../graphql/types.gen'
|
||||||
|
|
||||||
export interface ServerNotification {
|
export interface SSEMessage {
|
||||||
kind: string
|
entity: string
|
||||||
|
action: string
|
||||||
payload: any // Author | Shout | Reaction | Message
|
payload: any // Author | Shout | Reaction | Message
|
||||||
timestamp: number
|
timestamp?: number
|
||||||
seen: boolean
|
seen?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type MessageHandler = (m: Message) => void
|
type MessageHandler = (m: Message) => void
|
||||||
type NotificationsContextType = {
|
type NotificationsContextType = {
|
||||||
notificationEntities: Record<number, ServerNotification>
|
notificationEntities: Record<number, SSEMessage>
|
||||||
unreadNotificationsCount: Accessor<number>
|
unreadNotificationsCount: Accessor<number>
|
||||||
sortedNotifications: Accessor<ServerNotification[]>
|
sortedNotifications: Accessor<SSEMessage[]>
|
||||||
actions: {
|
actions: {
|
||||||
showNotificationsPanel: () => void
|
showNotificationsPanel: () => void
|
||||||
hideNotificationsPanel: () => void
|
hideNotificationsPanel: () => void
|
||||||
markNotificationAsRead: (notification: ServerNotification) => Promise<void>
|
markNotificationAsRead: (notification: SSEMessage) => Promise<void>
|
||||||
setMessageHandler: (h: MessageHandler) => void
|
setMessageHandler: (h: MessageHandler) => void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,9 +41,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
const [isNotificationsPanelOpen, setIsNotificationsPanelOpen] = createSignal(false)
|
const [isNotificationsPanelOpen, setIsNotificationsPanelOpen] = createSignal(false)
|
||||||
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
|
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
|
||||||
const { isAuthenticated, user } = useSession()
|
const { isAuthenticated, user } = useSession()
|
||||||
const [notificationEntities, setNotificationEntities] = createStore<Record<number, ServerNotification>>(
|
const [notificationEntities, setNotificationEntities] = createStore<Record<number, SSEMessage>>({})
|
||||||
{}
|
|
||||||
)
|
|
||||||
const [db, setDb] = createSignal<Promise<IDBPDatabase<unknown>>>()
|
const [db, setDb] = createSignal<Promise<IDBPDatabase<unknown>>>()
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const dbx = openDB('notifications-db', 1, {
|
const dbx = openDB('notifications-db', 1, {
|
||||||
|
@ -76,7 +75,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
return Object.values(notificationEntities).sort((a, b) => b.timestamp - a.timestamp)
|
return Object.values(notificationEntities).sort((a, b) => b.timestamp - a.timestamp)
|
||||||
})
|
})
|
||||||
|
|
||||||
const storeNotification = async (notification: ServerNotification) => {
|
const storeNotification = async (notification: SSEMessage) => {
|
||||||
console.log('[context.notifications] Storing notification:', notification)
|
console.log('[context.notifications] Storing notification:', notification)
|
||||||
|
|
||||||
const storage = await db()
|
const storage = await db()
|
||||||
|
@ -88,28 +87,27 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
loadNotifications()
|
loadNotifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
const [messageHandler, setMessageHandler] = createSignal<(m: Message) => void>()
|
const [messageHandler, setMessageHandler] = createSignal<(m: SSEMessage) => void>()
|
||||||
|
|
||||||
createEffect(async () => {
|
createEffect(async () => {
|
||||||
if (isAuthenticated()) {
|
if (isAuthenticated()) {
|
||||||
loadNotifications()
|
loadNotifications()
|
||||||
|
|
||||||
await fetchEventSource('https://chat.discours.io/connect', {
|
await fetchEventSource('https://chat.discours.io/', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: 'Bearer ' + getToken()
|
Authorization: 'Bearer ' + getToken()
|
||||||
},
|
},
|
||||||
onmessage(event) {
|
onmessage(event) {
|
||||||
const n: { kind: string; payload: any } = JSON.parse(event.data)
|
const m: SSEMessage = JSON.parse(event.data)
|
||||||
if (n.kind === 'new_message') {
|
if (m.entity === 'chat') {
|
||||||
console.log('[context.notifications] Received message:', n)
|
console.log('[context.notifications] Received message:', m)
|
||||||
messageHandler()(n.payload)
|
messageHandler()(m)
|
||||||
} else {
|
} else {
|
||||||
console.log('[context.notifications] Received notification:', n)
|
console.log('[context.notifications] Received notification:', m)
|
||||||
storeNotification({
|
storeNotification({
|
||||||
kind: n.kind,
|
...m,
|
||||||
payload: n.payload,
|
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
seen: false
|
seen: false
|
||||||
})
|
})
|
||||||
|
@ -126,7 +124,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const markNotificationAsRead = async (notification: ServerNotification) => {
|
const markNotificationAsRead = async (notification: SSEMessage) => {
|
||||||
console.log('[context.notifications] Marking notification as read:', notification)
|
console.log('[context.notifications] Marking notification as read:', notification)
|
||||||
|
|
||||||
const storage = await db()
|
const storage = await db()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user