feat: notification improvements (#396)

This commit is contained in:
patak 2022-12-11 23:40:40 +01:00 committed by GitHub
parent a26cedbdd4
commit 33b0f295f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 146 additions and 51 deletions

View file

@ -9,47 +9,55 @@ const { notification } = defineProps<{
<template>
<article flex flex-col relative>
<template v-if="notification.type === 'follow'">
<div flex ml-4 items-center absolute class="-top-2.5" right-2 px-2>
<div flex items-center absolute px-3 py-3 bg-base rounded-br-3 top-0 left-0>
<div i-ri:user-follow-fill mr-1 color-primary />
<AccountInlineInfo :account="notification.account" mr1 />
<ContentRich
text-primary mr-1 font-bold line-clamp-1 ws-pre-wrap break-all
:content="getDisplayName(notification.account, { rich: true })"
:emojis="notification.account.emojis"
/>
<span ws-nowrap>
{{ $t('notification.followed_you') }}
</span>
</div>
<AccountCard :account="notification.account" />
<AccountBigCard :account="notification.account" />
</template>
<template v-if="notification.type === 'admin.sign_up'">
<div flex p2 items-center gap-2>
<template v-else-if="notification.type === 'admin.sign_up'">
<div flex p3 items-center bg-shaded>
<div i-ri:admin-fill mr-1 color-purple />
<span>New Sign Up</span>
<ContentRich
text-purple mr-1 font-bold line-clamp-1 ws-pre-wrap break-all
:content="getDisplayName(notification.account, { rich: true })"
:emojis="notification.account.emojis"
/>
<span>signed up</span>
</div>
<AccountCard :account="notification.account" px2 pb2 />
</template>
<template v-else-if="notification.type === 'follow_request'">
<div flex ml-4 items-center class="-top-2.5" absolute right-2 px-2>
<div i-ri:user-follow-fill mr-1 />
<div i-ri:user-follow-fill text-xl mr-1 />
<AccountInlineInfo :account="notification.account" mr1 />
</div>
<!-- TODO: accept request -->
<AccountCard :account="notification.account" />
</template>
<template v-else-if="notification.type === 'favourite'">
<CommonMetaWrapper>
<div i-ri:heart-fill mr-1 color-red />
<AccountInlineInfo :account="notification.account" mr1 />
<CommonMetaWrapper z-1>
<div i-ri:heart-fill text-xl mr-1 color-red />
<AccountInlineInfo text-primary font-bold :account="notification.account" mr1 />
</CommonMetaWrapper>
<StatusCard :status="notification.status!" :decorated="true" />
<StatusCard op50 hover:op100 :status="notification.status!" :decorated="true" />
</template>
<template v-else-if="notification.type === 'reblog'">
<CommonMetaWrapper>
<div i-ri:repeat-fill mr-1 color-green />
<AccountInlineInfo :account="notification.account" mr1 />
<CommonMetaWrapper z-1>
<div i-ri:repeat-fill text-xl mr-1 color-green />
<AccountInlineInfo text-primary font-bold :account="notification.account" mr1 />
</CommonMetaWrapper>
<StatusCard :status="notification.status!" :decorated="true" />
<StatusCard op50 hover:op100 :status="notification.status!" :decorated="true" />
</template>
<template v-else-if="notification.type === 'update'">
<CommonMetaWrapper>
<div i-ri:edit-2-fill mr-1 text-secondary />
<CommonMetaWrapper z-1>
<div i-ri:edit-2-fill text-xl mr-1 text-secondary />
<AccountInlineInfo :account="notification.account" mr1 />
<span ws-nowrap>
{{ $t('notification.update_status') }}

View file

@ -0,0 +1,21 @@
<script setup lang="ts">
import type { GroupedLikeNotifications } from '~/types'
const { group } = defineProps<{
group: GroupedLikeNotifications
}>()
</script>
<template>
<article flex flex-col relative>
<div flex flex-col class="-mb-12" py-3>
<div v-for="like of group.likes" :key="like.account.id" flex px-3 py-1>
<div v-if="like.reblog" i-ri:repeat-fill text-xl mr-2 color-green />
<div v-if="like.favourite && !like.reblog" i-ri:heart-fill text-xl mr-2 color-red />
<AccountInlineInfo text-primary font-bold :account="like.account" mr2 />
<div v-if="like.favourite && like.reblog" i-ri:heart-fill text-xl mr-2 color-red />
</div>
</div>
<StatusCard op50 hover:op100 :status="group.status!" :decorated="true" />
</article>
</template>

View file

@ -1,45 +1,90 @@
<script setup lang="ts">
import type { Notification, Paginator, WsEvents } from 'masto'
import type { GroupedNotifications } from '~/types'
import type { GroupedAccountLike, NotificationSlot } from '~/types'
const { paginator, stream } = defineProps<{
paginator: Paginator<any, Notification[]>
stream?: WsEvents
}>()
function groupItems(items: Notification[]): (Notification | GroupedNotifications)[] {
const results: (Notification | GroupedNotifications)[] = []
const groupCapacity = Number.MAX_VALUE // No limit
const minFollowGroupSize = 5 // Below this limit, show a profile card for each follow
// Group by type (and status when applicable)
const groupId = (item: Notification): string => {
// If the update is related to an status, group notifications from the same account (boost + favorite the same status)
const id = item.status
? {
status: item.status?.id,
type: (item.type === 'reblog' || item.type === 'favourite') ? 'like' : item.type,
}
: {
type: item.type,
}
return JSON.stringify(id)
}
function groupItems(items: Notification[]): NotificationSlot[] {
const results: NotificationSlot[] = []
let id = 0
let followGroup: Notification[] = []
let currentGroupId = ''
let currentGroup: Notification[] = []
const processGroup = () => {
if (currentGroup.length === 0)
return
const bump = () => {
const alwaysGroup = true
if (!alwaysGroup && followGroup.length === 1) {
results.push(followGroup[0])
followGroup = []
}
else if (followGroup.length > 0) {
const group = currentGroup
currentGroup = []
// Only group follow notifications when there are too many in a row
// This normally happens when you transfer an account, if not, show
// a big profile card for each follow
if (group[0].type === 'follow' && group.length > minFollowGroupSize) {
results.push({
id: `grouped-${id++}`,
type: 'grouped-follow',
items: followGroup,
type: `grouped-${group[0].type}`,
items: group,
})
followGroup = []
return
}
const { status } = group[0]
if (status && group.length > 1 && (group[0].type === 'reblog' || group[0].type === 'favourite')) {
// All notifications in these group are reblogs or favourites of the same status
const likes: GroupedAccountLike[] = []
for (const notification of group) {
let like = likes.find(like => like.account.id === notification.account.id)
if (!like) {
like = { account: notification.account }
likes.push(like)
}
like[notification.type === 'reblog' ? 'reblog' : 'favourite'] = notification
}
likes.sort((a, b) => b.reblog && !a.reblog ? 1 : -1)
results.push({
id: `grouped-${id++}`,
type: 'grouped-reblogs-and-favourites',
status,
likes,
})
return
}
results.push(...group)
}
for (const item of items) {
if (item.type === 'follow') {
followGroup.push(item)
}
else {
bump()
results.push(item)
}
}
const itemId = groupId(item)
// Finalize group if it already has too many notifications
if (currentGroupId !== itemId || currentGroup.length >= groupCapacity)
processGroup()
bump()
currentGroup.push(item)
currentGroupId = itemId
}
// Finalize remaining groups
processGroup()
return results
}
@ -48,7 +93,7 @@ const { clearNotifications } = useNotifications()
</script>
<template>
<CommonPaginator :paginator="paginator" :stream="stream" event-type="notification">
<CommonPaginator :paginator="paginator" :stream="stream" :eager="3" event-type="notification">
<template #updater="{ number, update }">
<button py-4 border="b base" flex="~ col" p-3 w-full text-primary font-bold @click="() => { update(); clearNotifications() }">
{{ $t('timeline.show_new_items', [number]) }}
@ -61,6 +106,11 @@ const { clearNotifications } = useNotifications()
:items="item"
border="b base"
/>
<NotificationGroupedLikes
v-else-if="item.type === 'grouped-reblogs-and-favourites'"
:group="item"
border="b base"
/>
<NotificationCard
v-else
:notification="item"