forked from Mirrors/elk
feat: simplify notifications (#1905)
This commit is contained in:
parent
a25376b60d
commit
5dd3f4bfa3
6 changed files with 113 additions and 53 deletions
|
@ -47,28 +47,8 @@ const { notification } = defineProps<{
|
||||||
<!-- TODO: accept request -->
|
<!-- TODO: accept request -->
|
||||||
<AccountCard :account="notification.account" />
|
<AccountCard :account="notification.account" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="notification.type === 'favourite'">
|
|
||||||
<StatusCard :status="notification.status!" :faded="true">
|
|
||||||
<template #meta>
|
|
||||||
<div flex="~" gap-1 items-center mt1>
|
|
||||||
<div i-ri:heart-fill text-xl me-1 color-red />
|
|
||||||
<AccountInlineInfo text-primary font-bold :account="notification.account" me1 />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</StatusCard>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="notification.type === 'reblog'">
|
|
||||||
<StatusCard :status="notification.status!" :faded="true">
|
|
||||||
<template #meta>
|
|
||||||
<div flex="~" gap-1 items-center mt1>
|
|
||||||
<div i-ri:repeat-fill text-xl me-1 color-green />
|
|
||||||
<AccountInlineInfo text-primary font-bold :account="notification.account" me1 />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</StatusCard>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="notification.type === 'update'">
|
<template v-else-if="notification.type === 'update'">
|
||||||
<StatusCard :status="notification.status!" :faded="true">
|
<StatusCard :status="notification.status!" :in-notification="true" :actions="false">
|
||||||
<template #meta>
|
<template #meta>
|
||||||
<div flex="~" gap-1 items-center mt1>
|
<div flex="~" gap-1 items-center mt1>
|
||||||
<div i-ri:edit-2-fill text-xl me-1 text-secondary />
|
<div i-ri:edit-2-fill text-xl me-1 text-secondary />
|
||||||
|
@ -84,6 +64,7 @@ const { notification } = defineProps<{
|
||||||
<StatusCard :status="notification.status!" />
|
<StatusCard :status="notification.status!" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
<!-- type 'favourite' and 'reblog' should always rendered by NotificationGroupedLikes -->
|
||||||
<div text-red font-bold>
|
<div text-red font-bold>
|
||||||
[DEV] {{ $t('notification.missing_type') }} '{{ notification.type }}'
|
[DEV] {{ $t('notification.missing_type') }} '{{ notification.type }}'
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,21 +4,58 @@ import type { GroupedLikeNotifications } from '~/types'
|
||||||
const { group } = defineProps<{
|
const { group } = defineProps<{
|
||||||
group: GroupedLikeNotifications
|
group: GroupedLikeNotifications
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const reblogs = $computed(() => group.likes.filter(i => i.reblog))
|
||||||
|
const likes = $computed(() => group.likes.filter(i => i.favourite && !i.reblog))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<article flex flex-col relative>
|
<article flex flex-col relative>
|
||||||
<StatusCard :status="group.status!" :faded="true">
|
<StatusLink :status="group.status!" pb2 pt3>
|
||||||
<template #meta>
|
<div flex flex-col gap-2>
|
||||||
<div flex flex-col gap-1 mt-1>
|
<div v-if="reblogs.length" flex="~ gap-1">
|
||||||
<div v-for="like of group.likes" :key="like.account.id" flex>
|
<div i-ri:repeat-fill text-xl me-1 color-green />
|
||||||
<div v-if="like.reblog" i-ri:repeat-fill text-xl me-2 color-green />
|
<template v-for="i, idx of reblogs" :key="idx">
|
||||||
<div v-if="like.favourite && !like.reblog" i-ri:heart-fill text-xl me-2 color-red />
|
<AccountHoverWrapper :account="i.account">
|
||||||
<AccountInlineInfo text-primary font-bold :account="like.account" me2 />
|
<NuxtLink :to="getAccountRoute(i.account)">
|
||||||
<div v-if="like.favourite && like.reblog" i-ri:heart-fill text-xl me-2 color-red />
|
<AccountAvatar text-primary font-bold :account="i.account" class="h-1.5em w-1.5em" />
|
||||||
</div>
|
</NuxtLink>
|
||||||
</div>
|
</AccountHoverWrapper>
|
||||||
</template>
|
</template>
|
||||||
</StatusCard>
|
<div ml1>
|
||||||
|
{{ $t('notification.reblogged_post') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="likes.length" flex="~ gap-1">
|
||||||
|
<div i-ri:heart-fill text-xl me-1 color-red />
|
||||||
|
<template v-for="i, idx of likes" :key="idx">
|
||||||
|
<AccountHoverWrapper :account="i.account">
|
||||||
|
<NuxtLink :to="getAccountRoute(i.account)">
|
||||||
|
<AccountAvatar text-primary font-bold :account="i.account" class="h-1.5em w-1.5em" />
|
||||||
|
</NuxtLink>
|
||||||
|
</AccountHoverWrapper>
|
||||||
|
</template>
|
||||||
|
<div ml1>
|
||||||
|
{{ $t('notification.favourited_post') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div pl8 mt-1>
|
||||||
|
<StatusBody :status="group.status!" text-secondary />
|
||||||
|
<!-- When no text content is presented, we show media instead -->
|
||||||
|
<template v-if="!group.status!.content">
|
||||||
|
<StatusMedia
|
||||||
|
v-if="group.status!.mediaAttachments?.length"
|
||||||
|
:status="group.status!"
|
||||||
|
:is-preview="false"
|
||||||
|
pointer-events-none
|
||||||
|
/>
|
||||||
|
<StatusPoll
|
||||||
|
v-else-if="group.status!.poll"
|
||||||
|
:status="group.status!"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</StatusLink>
|
||||||
</article>
|
</article>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -75,9 +75,7 @@ function groupItems(items: mastodon.v1.Notification[]): NotificationSlot[] {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
else if (group.length && group[0].status && (group[0].type === 'reblog' || group[0].type === 'favourite')) {
|
||||||
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
|
// All notifications in these group are reblogs or favourites of the same status
|
||||||
const likes: GroupedAccountLike[] = []
|
const likes: GroupedAccountLike[] = []
|
||||||
for (const notification of group) {
|
for (const notification of group) {
|
||||||
|
@ -96,7 +94,7 @@ function groupItems(items: mastodon.v1.Notification[]): NotificationSlot[] {
|
||||||
results.push({
|
results.push({
|
||||||
id: `grouped-${id++}`,
|
id: `grouped-${id++}`,
|
||||||
type: 'grouped-reblogs-and-favourites',
|
type: 'grouped-reblogs-and-favourites',
|
||||||
status,
|
status: group[0].status,
|
||||||
likes,
|
likes,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|
|
@ -7,7 +7,7 @@ const props = withDefaults(
|
||||||
actions?: boolean
|
actions?: boolean
|
||||||
context?: mastodon.v2.FilterContext
|
context?: mastodon.v2.FilterContext
|
||||||
hover?: boolean
|
hover?: boolean
|
||||||
faded?: boolean
|
inNotification?: boolean
|
||||||
isPreview?: boolean
|
isPreview?: boolean
|
||||||
|
|
||||||
// If we know the prev and next status in the timeline, we can simplify the card
|
// If we know the prev and next status in the timeline, we can simplify the card
|
||||||
|
@ -77,19 +77,7 @@ const showReplyTo = $computed(() => !replyToMain && !directReply)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<StatusLink :status="status" :hover="hover">
|
||||||
:id="`status-${status.id}`"
|
|
||||||
ref="el"
|
|
||||||
relative flex="~ col gap1"
|
|
||||||
p="b-2 is-3 ie-4"
|
|
||||||
:class="{ 'hover:bg-active': hover }"
|
|
||||||
tabindex="0"
|
|
||||||
focus:outline-none focus-visible:ring="2 primary"
|
|
||||||
aria-roledescription="status-card"
|
|
||||||
:lang="status.language ?? undefined"
|
|
||||||
@click="onclick"
|
|
||||||
@keydown.enter="onclick"
|
|
||||||
>
|
|
||||||
<!-- Upper border -->
|
<!-- Upper border -->
|
||||||
<div :h="showUpperBorder ? '1px' : '0'" w-auto bg-border mb-1 />
|
<div :h="showUpperBorder ? '1px' : '0'" w-auto bg-border mb-1 />
|
||||||
|
|
||||||
|
@ -101,7 +89,7 @@ const showReplyTo = $computed(() => !replyToMain && !directReply)
|
||||||
m="is-5" p="t-1 is-5"
|
m="is-5" p="t-1 is-5"
|
||||||
:status="status"
|
:status="status"
|
||||||
:is-self-reply="isSelfReply"
|
:is-self-reply="isSelfReply"
|
||||||
:class="faded ? 'text-secondary-light' : ''"
|
:class="inNotification ? 'text-secondary-light' : ''"
|
||||||
/>
|
/>
|
||||||
<div flex="~ col gap-1" items-center pos="absolute top-0 inset-is-0" w="77px" z--1>
|
<div flex="~ col gap-1" items-center pos="absolute top-0 inset-is-0" w="77px" z--1>
|
||||||
<template v-if="showReplyTo">
|
<template v-if="showReplyTo">
|
||||||
|
@ -134,7 +122,7 @@ const showReplyTo = $computed(() => !replyToMain && !directReply)
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<div flex gap-3 :class="{ 'text-secondary': faded }">
|
<div flex gap-3 :class="{ 'text-secondary': inNotification }">
|
||||||
<!-- Avatar -->
|
<!-- Avatar -->
|
||||||
<div relative>
|
<div relative>
|
||||||
<div v-if="collapseRebloggedBy" absolute flex items-center justify-center top--6px px-2px py-3px rounded-full bg-base>
|
<div v-if="collapseRebloggedBy" absolute flex items-center justify-center top--6px px-2px py-3px rounded-full bg-base>
|
||||||
|
@ -179,9 +167,16 @@ const showReplyTo = $computed(() => !replyToMain && !directReply)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<StatusContent :status="status" :newer="newer" :context="context" :is-preview="isPreview" mb2 :class="{ 'mt-2 mb1': isDM }" />
|
<StatusContent
|
||||||
|
:status="status"
|
||||||
|
:newer="newer"
|
||||||
|
:context="context"
|
||||||
|
:is-preview="isPreview"
|
||||||
|
:in-notification="inNotification"
|
||||||
|
mb2 :class="{ 'mt-2 mb1': isDM }"
|
||||||
|
/>
|
||||||
<StatusActions v-if="actions !== false" v-show="!userSettings.zenMode" :status="status" />
|
<StatusActions v-if="actions !== false" v-show="!userSettings.zenMode" :status="status" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</StatusLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -6,6 +6,7 @@ const { status, context } = defineProps<{
|
||||||
newer?: mastodon.v1.Status
|
newer?: mastodon.v1.Status
|
||||||
context?: mastodon.v2.FilterContext | 'details'
|
context?: mastodon.v2.FilterContext | 'details'
|
||||||
isPreview?: boolean
|
isPreview?: boolean
|
||||||
|
inNotification?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const isDM = $computed(() => status.visibility === 'direct')
|
const isDM = $computed(() => status.visibility === 'direct')
|
||||||
|
|
48
components/status/StatusLink.vue
Normal file
48
components/status/StatusLink.vue
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { mastodon } from 'masto'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
status: mastodon.v1.Status
|
||||||
|
hover?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const el = ref<HTMLElement>()
|
||||||
|
const router = useRouter()
|
||||||
|
const statusRoute = $computed(() => getStatusRoute(props.status))
|
||||||
|
|
||||||
|
function onclick(evt: MouseEvent | KeyboardEvent) {
|
||||||
|
const path = evt.composedPath() as HTMLElement[]
|
||||||
|
const el = path.find(el => ['A', 'BUTTON', 'IMG', 'VIDEO'].includes(el.tagName?.toUpperCase()))
|
||||||
|
const text = window.getSelection()?.toString()
|
||||||
|
if (!el && !text)
|
||||||
|
go(evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
function go(evt: MouseEvent | KeyboardEvent) {
|
||||||
|
if (evt.metaKey || evt.ctrlKey) {
|
||||||
|
window.open(statusRoute.href)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cacheStatus(props.status)
|
||||||
|
router.push(statusRoute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:id="`status-${status.id}`"
|
||||||
|
ref="el"
|
||||||
|
relative flex="~ col gap1"
|
||||||
|
p="b-2 is-3 ie-4"
|
||||||
|
:class="{ 'hover:bg-active': hover }"
|
||||||
|
tabindex="0"
|
||||||
|
focus:outline-none focus-visible:ring="2 primary"
|
||||||
|
aria-roledescription="status-card"
|
||||||
|
:lang="status.language ?? undefined"
|
||||||
|
@click="onclick"
|
||||||
|
@keydown.enter="onclick"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
Loading…
Reference in a new issue