feat: 通知の受信設定を強化
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkModalWindow
|
||||
ref="dialog"
|
||||
:width="400"
|
||||
:height="450"
|
||||
:withOkButton="true"
|
||||
:okButtonDisabled="false"
|
||||
@ok="ok()"
|
||||
@close="dialog?.close()"
|
||||
@closed="emit('closed')"
|
||||
>
|
||||
<template #header>{{ i18n.ts.notificationSetting }}</template>
|
||||
|
||||
<MkSpacer :marginMin="20" :marginMax="28">
|
||||
<div class="_gaps_m">
|
||||
<MkInfo>{{ i18n.ts.notificationSettingDesc }}</MkInfo>
|
||||
<div class="_buttons">
|
||||
<MkButton inline @click="disableAll">{{ i18n.ts.disableAll }}</MkButton>
|
||||
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
|
||||
</div>
|
||||
<MkSwitch v-for="ntype in notificationTypes" :key="ntype" v-model="typesMap[ntype].value">{{ i18n.t(`_notification._types.${ntype}`) }}</MkSwitch>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</MkModalWindow>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, Ref } from 'vue';
|
||||
import MkSwitch from './MkSwitch.vue';
|
||||
import MkInfo from './MkInfo.vue';
|
||||
import MkButton from './MkButton.vue';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import { notificationTypes } from '@/const.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>>
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'done', v: { excludeTypes: string[] }): void,
|
||||
(ev: 'closed'): void,
|
||||
}>();
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
excludeTypes?: typeof notificationTypes[number][];
|
||||
}>(), {
|
||||
excludeTypes: () => [],
|
||||
});
|
||||
|
||||
const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||
|
||||
const typesMap: TypesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as any);
|
||||
|
||||
function ok() {
|
||||
emit('done', {
|
||||
excludeTypes: (Object.keys(typesMap) as typeof notificationTypes[number][])
|
||||
.filter(type => !typesMap[type].value),
|
||||
});
|
||||
|
||||
if (dialog) dialog.close();
|
||||
}
|
||||
|
||||
function disableAll() {
|
||||
for (const type of notificationTypes) {
|
||||
typesMap[type].value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function enableAll() {
|
||||
for (const type of notificationTypes) {
|
||||
typesMap[type].value = true;
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -1,95 +0,0 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<MkModalWindow
|
||||
ref="dialog"
|
||||
:width="400"
|
||||
:height="450"
|
||||
:withOkButton="true"
|
||||
:okButtonDisabled="false"
|
||||
@ok="ok()"
|
||||
@close="dialog?.close()"
|
||||
@closed="emit('closed')"
|
||||
>
|
||||
<template #header>{{ i18n.ts.notificationSetting }}</template>
|
||||
|
||||
<MkSpacer :marginMin="20" :marginMax="28">
|
||||
<div class="_gaps_m">
|
||||
<template v-if="showGlobalToggle">
|
||||
<MkSwitch v-model="useGlobalSetting">
|
||||
{{ i18n.ts.useGlobalSetting }}
|
||||
<template #caption>{{ i18n.ts.useGlobalSettingDesc }}</template>
|
||||
</MkSwitch>
|
||||
</template>
|
||||
<template v-if="!useGlobalSetting">
|
||||
<MkInfo>{{ i18n.ts.notificationSettingDesc }}</MkInfo>
|
||||
<div class="_buttons">
|
||||
<MkButton inline @click="disableAll">{{ i18n.ts.disableAll }}</MkButton>
|
||||
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
|
||||
</div>
|
||||
<MkSwitch v-for="ntype in notificationTypes" :key="ntype" v-model="typesMap[ntype].value">{{ i18n.t(`_notification._types.${ntype}`) }}</MkSwitch>
|
||||
</template>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</MkModalWindow>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, Ref } from 'vue';
|
||||
import MkSwitch from './MkSwitch.vue';
|
||||
import MkInfo from './MkInfo.vue';
|
||||
import MkButton from './MkButton.vue';
|
||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||
import { notificationTypes } from '@/const';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>>
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'done', v: { includingTypes: string[] | null }): void,
|
||||
(ev: 'closed'): void,
|
||||
}>();
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
includingTypes?: typeof notificationTypes[number][] | null;
|
||||
showGlobalToggle?: boolean;
|
||||
}>(), {
|
||||
includingTypes: () => [],
|
||||
showGlobalToggle: true,
|
||||
});
|
||||
|
||||
let includingTypes = $computed(() => props.includingTypes?.filter(x => notificationTypes.includes(x)) ?? []);
|
||||
|
||||
const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||
|
||||
const typesMap: TypesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(includingTypes.includes(t)) }), {} as any);
|
||||
let useGlobalSetting = $ref((includingTypes === null || includingTypes.length === 0) && props.showGlobalToggle);
|
||||
|
||||
function ok() {
|
||||
if (useGlobalSetting) {
|
||||
emit('done', { includingTypes: null });
|
||||
} else {
|
||||
emit('done', {
|
||||
includingTypes: (Object.keys(typesMap) as typeof notificationTypes[number][])
|
||||
.filter(type => typesMap[type].value),
|
||||
});
|
||||
}
|
||||
|
||||
if (dialog) dialog.close();
|
||||
}
|
||||
|
||||
function disableAll() {
|
||||
for (const type of notificationTypes) {
|
||||
typesMap[type].value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function enableAll() {
|
||||
for (const type of notificationTypes) {
|
||||
typesMap[type].value = true;
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -30,11 +30,11 @@ import MkNote from '@/components/MkNote.vue';
|
||||
import { useStream } from '@/stream.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { notificationTypes } from '@/const';
|
||||
import { notificationTypes } from '@/const.js';
|
||||
import { infoImageUrl } from '@/instance.js';
|
||||
|
||||
const props = defineProps<{
|
||||
includeTypes?: typeof notificationTypes[number][];
|
||||
excludeTypes?: typeof notificationTypes[number][];
|
||||
}>();
|
||||
|
||||
const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
|
||||
@@ -43,13 +43,12 @@ const pagination: Paging = {
|
||||
endpoint: 'i/notifications' as const,
|
||||
limit: 10,
|
||||
params: computed(() => ({
|
||||
includeTypes: props.includeTypes ?? undefined,
|
||||
excludeTypes: props.includeTypes ? undefined : $i.mutingNotificationTypes,
|
||||
excludeTypes: props.excludeTypes ?? undefined,
|
||||
})),
|
||||
};
|
||||
|
||||
const onNotification = (notification) => {
|
||||
const isMuted = props.includeTypes ? !props.includeTypes.includes(notification.type) : $i.mutingNotificationTypes.includes(notification.type);
|
||||
const isMuted = props.excludeTypes ? props.excludeTypes.includes(notification.type) : false;
|
||||
if (isMuted || document.visibilityState === 'visible') {
|
||||
useStream().send('readNotification');
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :contentMax="800">
|
||||
<div v-if="tab === 'all'">
|
||||
<XNotifications class="notifications" :includeTypes="includeTypes"/>
|
||||
<XNotifications class="notifications" :excludeTypes="excludeTypes"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'mentions'">
|
||||
<MkNotes :pagination="mentionsPagination"/>
|
||||
@@ -27,10 +27,11 @@ import MkNotes from '@/components/MkNotes.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { notificationTypes } from '@/const';
|
||||
import { notificationTypes } from '@/const.js';
|
||||
|
||||
let tab = $ref('all');
|
||||
let includeTypes = $ref<string[] | null>(null);
|
||||
const excludeTypes = $computed(() => includeTypes ? notificationTypes.filter(t => !includeTypes.includes(t)) : null);
|
||||
|
||||
const mentionsPagination = {
|
||||
endpoint: 'notes/mentions' as const,
|
||||
|
@@ -0,0 +1,50 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<MkSelect v-model="type">
|
||||
<option value="all">{{ i18n.ts.all }}</option>
|
||||
<option value="following">{{ i18n.ts.following }}</option>
|
||||
<option value="follower">{{ i18n.ts.followers }}</option>
|
||||
<option value="mutualFollow">{{ i18n.ts.mutualFollow }}</option>
|
||||
<option value="list">{{ i18n.ts.userList }}</option>
|
||||
<option value="never">{{ i18n.ts.none }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkSelect v-if="type === 'list'" v-model="userListId">
|
||||
<template #label>{{ i18n.ts.userList }}</template>
|
||||
<option v-for="list in props.userLists" :key="list.id" :value="list.id">{{ list.name }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<div class="_buttons">
|
||||
<MkButton inline primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
const props = defineProps<{
|
||||
value: any;
|
||||
userLists: Misskey.entities.UserList[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'update', result: any): void;
|
||||
}>();
|
||||
|
||||
let type = $ref(props.value.type);
|
||||
let userListId = $ref(props.value.userListId);
|
||||
|
||||
function save() {
|
||||
emit('update', { type, userListId });
|
||||
}
|
||||
</script>
|
@@ -5,7 +5,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<template>
|
||||
<div class="_gaps_m">
|
||||
<FormLink @click="configure"><template #icon><i class="ti ti-settings"></i></template>{{ i18n.ts.notificationSetting }}</FormLink>
|
||||
<FormSection first>
|
||||
<template #label>{{ i18n.ts.notificationRecieveConfig }}</template>
|
||||
<div class="_gaps_s">
|
||||
<MkFolder v-for="type in notificationTypes" :key="type">
|
||||
<template #label>{{ i18n.t('_notification._types.' + type) }}</template>
|
||||
<template #suffix>
|
||||
{{
|
||||
$i.notificationRecieveConfig[type]?.type === 'never' ? i18n.ts.none :
|
||||
$i.notificationRecieveConfig[type]?.type === 'following' ? i18n.ts.following :
|
||||
$i.notificationRecieveConfig[type]?.type === 'follower' ? i18n.ts.followers :
|
||||
$i.notificationRecieveConfig[type]?.type === 'mutualFollow' ? i18n.ts.mutualFollow :
|
||||
$i.notificationRecieveConfig[type]?.type === 'list' ? i18n.ts.userList :
|
||||
i18n.ts.all
|
||||
}}
|
||||
</template>
|
||||
|
||||
<XNotificationConfig :userLists="userLists" :value="$i.notificationRecieveConfig[type] ?? { type: 'all' }" @update="(res) => updateReceiveConfig(type, res)"/>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
<FormSection>
|
||||
<div class="_gaps_m">
|
||||
<FormLink @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</FormLink>
|
||||
@@ -37,19 +56,22 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import XNotificationConfig from './notifications.notification-config.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { $i } from '@/account.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
|
||||
import { notificationTypes } from '@/const';
|
||||
import { notificationTypes } from '@/const.js';
|
||||
|
||||
let allowButton = $shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
|
||||
let pushRegistrationInServer = $computed(() => allowButton?.pushRegistrationInServer);
|
||||
let sendReadMessage = $computed(() => pushRegistrationInServer?.sendReadMessage || false);
|
||||
const userLists = await os.api('users/lists/list');
|
||||
|
||||
async function readAllUnreadNotes() {
|
||||
await os.api('i/read-all-unread-notes');
|
||||
@@ -59,21 +81,15 @@ async function readAllNotifications() {
|
||||
await os.api('notifications/mark-all-as-read');
|
||||
}
|
||||
|
||||
function configure() {
|
||||
const includingTypes = notificationTypes.filter(x => !$i!.mutingNotificationTypes.includes(x));
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSettingWindow.vue')), {
|
||||
includingTypes,
|
||||
showGlobalToggle: false,
|
||||
}, {
|
||||
done: async (res) => {
|
||||
const { includingTypes: value } = res;
|
||||
await os.apiWithDialog('i/update', {
|
||||
mutingNotificationTypes: notificationTypes.filter(x => !value.includes(x)),
|
||||
}).then(i => {
|
||||
$i!.mutingNotificationTypes = i.mutingNotificationTypes;
|
||||
});
|
||||
async function updateReceiveConfig(type, value) {
|
||||
await os.apiWithDialog('i/update', {
|
||||
notificationRecieveConfig: {
|
||||
...$i!.notificationRecieveConfig,
|
||||
[type]: value,
|
||||
},
|
||||
}, 'closed');
|
||||
}).then(i => {
|
||||
$i!.notificationRecieveConfig = i.notificationRecieveConfig;
|
||||
});
|
||||
}
|
||||
|
||||
function onChangeSendReadMessage(v: boolean) {
|
||||
|
@@ -65,9 +65,7 @@ const dev = _DEV_;
|
||||
|
||||
let notifications = $ref<Misskey.entities.Notification[]>([]);
|
||||
|
||||
function onNotification(notification: Misskey.entities.Notification, isClient: boolean = false) {
|
||||
if ($i.mutingNotificationTypes.includes(notification.type)) return;
|
||||
|
||||
function onNotification(notification: Misskey.entities.Notification, isClient = false) {
|
||||
if (document.visibilityState === 'visible') {
|
||||
if (!isClient) {
|
||||
useStream().send('readNotification');
|
||||
|
@@ -28,7 +28,7 @@ export type Column = {
|
||||
listId?: string;
|
||||
channelId?: string;
|
||||
roleId?: string;
|
||||
includingTypes?: typeof notificationTypes[number][];
|
||||
excludeTypes?: typeof notificationTypes[number][];
|
||||
tl?: 'home' | 'local' | 'social' | 'global';
|
||||
withRenotes?: boolean;
|
||||
withReplies?: boolean;
|
||||
|
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<XColumn :column="column" :isStacked="isStacked" :menu="menu">
|
||||
<template #header><i class="ti ti-bell" style="margin-right: 8px;"></i>{{ column.name }}</template>
|
||||
|
||||
<XNotifications :includeTypes="column.includingTypes"/>
|
||||
<XNotifications :excludeTypes="props.column.excludeTypes"/>
|
||||
</XColumn>
|
||||
</template>
|
||||
|
||||
@@ -25,13 +25,13 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
function func() {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSettingWindow.vue')), {
|
||||
includingTypes: props.column.includingTypes,
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSelectWindow.vue')), {
|
||||
excludeTypes: props.column.excludeTypes,
|
||||
}, {
|
||||
done: async (res) => {
|
||||
const { includingTypes } = res;
|
||||
const { excludeTypes } = res;
|
||||
updateColumn(props.column.id, {
|
||||
includingTypes: includingTypes,
|
||||
excludeTypes: excludeTypes,
|
||||
});
|
||||
},
|
||||
}, 'closed');
|
||||
|
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="configureNotification()"><i class="ti ti-settings"></i></button></template>
|
||||
|
||||
<div>
|
||||
<XNotifications :includeTypes="widgetProps.includingTypes"/>
|
||||
<XNotifications :excludeTypes="widgetProps.excludeTypes"/>
|
||||
</div>
|
||||
</MkContainer>
|
||||
</template>
|
||||
@@ -35,10 +35,10 @@ const widgetPropsDef = {
|
||||
type: 'number' as const,
|
||||
default: 300,
|
||||
},
|
||||
includingTypes: {
|
||||
excludeTypes: {
|
||||
type: 'array' as const,
|
||||
hidden: true,
|
||||
default: null,
|
||||
default: [],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -54,12 +54,12 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name,
|
||||
);
|
||||
|
||||
const configureNotification = () => {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSettingWindow.vue')), {
|
||||
includingTypes: widgetProps.includingTypes,
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSelectWindow.vue')), {
|
||||
excludeTypes: widgetProps.excludeTypes,
|
||||
}, {
|
||||
done: async (res) => {
|
||||
const { includingTypes } = res;
|
||||
widgetProps.includingTypes = includingTypes;
|
||||
const { excludeTypes } = res;
|
||||
widgetProps.excludeTypes = excludeTypes;
|
||||
save();
|
||||
},
|
||||
}, 'closed');
|
||||
|
Reference in New Issue
Block a user