Merge branch 'develop' into pr/12141
This commit is contained in:
@@ -84,7 +84,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
|
||||
</div>
|
||||
<MkReactionsViewer v-show="appearNote.cw == null || showContent" :note="appearNote" :maxNumber="16" @mockUpdateMyReaction="emitUpdReaction">
|
||||
<MkReactionsViewer :note="appearNote" :maxNumber="16" @mockUpdateMyReaction="emitUpdReaction">
|
||||
<template #more>
|
||||
<div :class="$style.reactionOmitted">{{ i18n.ts.more }}</div>
|
||||
</template>
|
||||
|
@@ -9,9 +9,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkAvatar v-if="notification.type === 'pollEnded'" :class="$style.icon" :user="notification.note.user" link preview/>
|
||||
<MkAvatar v-else-if="notification.type === 'note'" :class="$style.icon" :user="notification.note.user" link preview/>
|
||||
<MkAvatar v-else-if="notification.type === 'achievementEarned'" :class="$style.icon" :user="$i" link preview/>
|
||||
<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div>
|
||||
<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div>
|
||||
<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
|
||||
<MkAvatar v-else-if="notification.user" :class="$style.icon" :user="notification.user" link preview/>
|
||||
<img v-else-if="notification.icon" :class="$style.icon" :src="notification.icon" alt=""/>
|
||||
<img v-else-if="notification.icon" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/>
|
||||
<div
|
||||
:class="[$style.subIcon, {
|
||||
[$style.t_follow]: notification.type === 'follow',
|
||||
@@ -39,7 +41,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
v-else-if="notification.type === 'reaction'"
|
||||
ref="reactionRef"
|
||||
:reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction"
|
||||
:customEmojis="notification.note.emojis"
|
||||
:noStyle="true"
|
||||
style="width: 100%; height: 100%;"
|
||||
/>
|
||||
@@ -52,16 +53,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
|
||||
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
|
||||
<MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
|
||||
<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.t('_notification.reactedBySomeUsers', { n: notification.reactions.length }) }}</span>
|
||||
<span v-else-if="notification.type === 'renote:grouped'">{{ i18n.t('_notification.renotedBySomeUsers', { n: notification.users.length }) }}</span>
|
||||
<span v-else>{{ notification.header }}</span>
|
||||
<MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/>
|
||||
</header>
|
||||
<div>
|
||||
<MkA v-if="notification.type === 'reaction'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||
<MkA v-if="notification.type === 'reaction' || notification.type === 'reaction:grouped'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
|
||||
<i class="ti ti-quote" :class="$style.quote"></i>
|
||||
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :author="notification.note.user"/>
|
||||
<i class="ti ti-quote" :class="$style.quote"></i>
|
||||
</MkA>
|
||||
<MkA v-else-if="notification.type === 'renote'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
|
||||
<MkA v-else-if="notification.type === 'renote' || notification.type === 'renote:grouped'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
|
||||
<i class="ti ti-quote" :class="$style.quote"></i>
|
||||
<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="true" :author="notification.note.renote.user"/>
|
||||
<i class="ti ti-quote" :class="$style.quote"></i>
|
||||
@@ -102,6 +105,24 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<span v-else-if="notification.type === 'app'" :class="$style.text">
|
||||
<Mfm :text="notification.body" :nowrap="false"/>
|
||||
</span>
|
||||
|
||||
<div v-if="notification.type === 'reaction:grouped'">
|
||||
<div v-for="reaction of notification.reactions" :class="$style.reactionsItem">
|
||||
<MkAvatar :class="$style.reactionsItemAvatar" :user="reaction.user" link preview/>
|
||||
<div :class="$style.reactionsItemReaction">
|
||||
<MkReactionIcon
|
||||
:reaction="reaction.reaction ? reaction.reaction.replace(/^:(\w+):$/, ':$1@.:') : reaction.reaction"
|
||||
:noStyle="true"
|
||||
style="width: 100%; height: 100%;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="notification.type === 'renote:grouped'">
|
||||
<div v-for="user of notification.users" :class="$style.reactionsItem">
|
||||
<MkAvatar :class="$style.reactionsItemAvatar" :user="user" link preview/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -181,6 +202,29 @@ useTooltip(reactionRef, (showing) => {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon_reactionGroup,
|
||||
.icon_renoteGroup {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
font-size: 15px;
|
||||
border-radius: 100%;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.icon_reactionGroup {
|
||||
background: #e99a0b;
|
||||
}
|
||||
|
||||
.icon_renoteGroup {
|
||||
background: #36d298;
|
||||
}
|
||||
|
||||
.icon_app {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@@ -305,6 +349,36 @@ useTooltip(reactionRef, (showing) => {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.reactionsItem {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
margin-top: 8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.reactionsItemAvatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.reactionsItemReaction {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: -2px;
|
||||
right: -2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 100%;
|
||||
background: var(--panel);
|
||||
box-shadow: 0 0 0 3px var(--panel);
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@container (max-width: 600px) {
|
||||
.root {
|
||||
padding: 16px;
|
||||
|
@@ -4,25 +4,27 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkPagination ref="pagingComponent" :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noNotifications }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<MkPullToRefresh :refresher="() => reload()">
|
||||
<MkPagination ref="pagingComponent" :pagination="pagination">
|
||||
<template #empty>
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" class="_ghost"/>
|
||||
<div>{{ i18n.ts.noNotifications }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #default="{ items: notifications }">
|
||||
<MkDateSeparatedList v-slot="{ item: notification }" :class="$style.list" :items="notifications" :noGap="true">
|
||||
<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note"/>
|
||||
<XNotification v-else :key="notification.id" :notification="notification" :withTime="true" :full="true" class="_panel notification"/>
|
||||
</MkDateSeparatedList>
|
||||
</template>
|
||||
</MkPagination>
|
||||
<template #default="{ items: notifications }">
|
||||
<MkDateSeparatedList v-slot="{ item: notification }" :class="$style.list" :items="notifications" :noGap="true">
|
||||
<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note"/>
|
||||
<XNotification v-else :key="notification.id" :notification="notification" :withTime="true" :full="true" class="_panel"/>
|
||||
</MkDateSeparatedList>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</MkPullToRefresh>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onUnmounted, onDeactivated, onMounted, computed, shallowRef } from 'vue';
|
||||
import { onUnmounted, onDeactivated, onMounted, computed, shallowRef, onActivated } from 'vue';
|
||||
import MkPagination, { Paging } from '@/components/MkPagination.vue';
|
||||
import XNotification from '@/components/MkNotification.vue';
|
||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
||||
@@ -32,6 +34,8 @@ import { $i } from '@/account.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { notificationTypes } from '@/const.js';
|
||||
import { infoImageUrl } from '@/instance.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
excludeTypes?: typeof notificationTypes[number][];
|
||||
@@ -39,7 +43,13 @@ const props = defineProps<{
|
||||
|
||||
const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
|
||||
|
||||
const pagination: Paging = {
|
||||
const pagination: Paging = defaultStore.state.useGroupedNotifications ? {
|
||||
endpoint: 'i/notifications-grouped' as const,
|
||||
limit: 20,
|
||||
params: computed(() => ({
|
||||
excludeTypes: props.excludeTypes ?? undefined,
|
||||
})),
|
||||
} : {
|
||||
endpoint: 'i/notifications' as const,
|
||||
limit: 20,
|
||||
params: computed(() => ({
|
||||
@@ -47,7 +57,7 @@ const pagination: Paging = {
|
||||
})),
|
||||
};
|
||||
|
||||
const onNotification = (notification) => {
|
||||
function onNotification(notification) {
|
||||
const isMuted = props.excludeTypes ? props.excludeTypes.includes(notification.type) : false;
|
||||
if (isMuted || document.visibilityState === 'visible') {
|
||||
useStream().send('readNotification');
|
||||
@@ -56,7 +66,15 @@ const onNotification = (notification) => {
|
||||
if (!isMuted) {
|
||||
pagingComponent.value.prepend(notification);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function reload() {
|
||||
return new Promise<void>((res) => {
|
||||
pagingComponent.value?.reload().then(() => {
|
||||
res();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let connection;
|
||||
|
||||
@@ -65,6 +83,12 @@ onMounted(() => {
|
||||
connection.on('notification', onNotification);
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
pagingComponent.value?.reload();
|
||||
connection = useStream().useChannel('main');
|
||||
connection.on('notification', onNotification);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (connection) connection.dispose();
|
||||
});
|
||||
|
@@ -47,7 +47,13 @@ let scrollEl: HTMLElement | null = null;
|
||||
|
||||
let disabled = false;
|
||||
|
||||
const emits = defineEmits<{
|
||||
const props = withDefaults(defineProps<{
|
||||
refresher: () => Promise<void>;
|
||||
}>(), {
|
||||
refresher: () => Promise.resolve(),
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'refresh'): void;
|
||||
}>();
|
||||
|
||||
@@ -120,7 +126,12 @@ function moveEnd() {
|
||||
if (isPullEnd) {
|
||||
isPullEnd = false;
|
||||
isRefreshing = true;
|
||||
fixOverContent().then(() => emits('refresh'));
|
||||
fixOverContent().then(() => {
|
||||
emit('refresh');
|
||||
props.refresher().then(() => {
|
||||
refreshFinished();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
closeContent().then(() => isPullStart = false);
|
||||
}
|
||||
@@ -188,7 +199,6 @@ onUnmounted(() => {
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
refreshFinished,
|
||||
setDisabled,
|
||||
});
|
||||
</script>
|
||||
|
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkPullToRefresh ref="prComponent" @refresh="() => reloadTimeline(true)">
|
||||
<MkPullToRefresh ref="prComponent" :refresher="() => reloadTimeline()">
|
||||
<MkNotes ref="tlComponent" :noGap="!defaultStore.state.showGapBetweenNotesInTimeline" :pagination="pagination" @queue="emit('queue', $event)" @status="prComponent.setDisabled($event)"/>
|
||||
</MkPullToRefresh>
|
||||
</template>
|
||||
@@ -196,25 +196,18 @@ const pagination = {
|
||||
params: query,
|
||||
};
|
||||
|
||||
const reloadTimeline = (fromPR = false) => {
|
||||
tlNotesCount = 0;
|
||||
function reloadTimeline() {
|
||||
return new Promise<void>((res) => {
|
||||
tlNotesCount = 0;
|
||||
|
||||
tlComponent.pagingComponent?.reload().then(() => {
|
||||
reloadStream();
|
||||
if (fromPR) prComponent.refreshFinished();
|
||||
tlComponent.pagingComponent?.reload().then(() => {
|
||||
reloadStream();
|
||||
res();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
//const pullRefresh = () => reloadTimeline(true);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
reloadTimeline,
|
||||
});
|
||||
|
||||
/* TODO
|
||||
const timetravel = (date?: Date) => {
|
||||
this.date = date;
|
||||
this.$refs.tl.reload();
|
||||
};
|
||||
*/
|
||||
</script>
|
||||
|
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="_gaps">
|
||||
<div>
|
||||
<MkInput v-model="host" :debounce="true" class="">
|
||||
<template #prefix><i class="ti ti-search"></i></template>
|
||||
|
@@ -88,6 +88,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #label>{{ i18n.ts.notificationDisplay }}</template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="useGroupedNotifications">{{ i18n.ts.useGroupedNotifications }}</MkSwitch>
|
||||
|
||||
<MkRadios v-model="notificationPosition">
|
||||
<template #label>{{ i18n.ts.position }}</template>
|
||||
<option value="leftTop"><i class="ti ti-align-box-left-top"></i> {{ i18n.ts.leftTop }}</option>
|
||||
@@ -255,6 +257,7 @@ const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificati
|
||||
const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
|
||||
const defaultWithReplies = computed(defaultStore.makeGetterSetter('defaultWithReplies'));
|
||||
const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline'));
|
||||
const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications'));
|
||||
|
||||
watch(lang, () => {
|
||||
miLocalStorage.setItem('lang', lang.value as string);
|
||||
|
@@ -378,6 +378,10 @@ export const defaultStore = markRaw(new Storage('base', {
|
||||
where: 'device',
|
||||
default: false,
|
||||
},
|
||||
useGroupedNotifications: {
|
||||
where: 'device',
|
||||
default: true,
|
||||
},
|
||||
}));
|
||||
|
||||
// TODO: 他のタブと永続化されたstateを同期
|
||||
|
Reference in New Issue
Block a user