forked from FoundKeyGang/FoundKey
Merge pull request 'mute notifications in muted threads' (#119) from mute-notifications into main
Reviewed-on: FoundKeyGang/FoundKey#119 Changelog: Changed Fixes: #12
This commit is contained in:
commit
dba63e4000
15 changed files with 145 additions and 70 deletions
|
@ -804,6 +804,7 @@ makeReactionsPublicDescription: "Jeder wird die Liste deiner gesendeten Reaktion
|
||||||
classic: "Classic"
|
classic: "Classic"
|
||||||
muteThread: "Thread stummschalten"
|
muteThread: "Thread stummschalten"
|
||||||
unmuteThread: "Threadstummschaltung aufheben"
|
unmuteThread: "Threadstummschaltung aufheben"
|
||||||
|
threadMuteNotificationsDesc: "Wähle die Benachrichtigungen, die du aus diesem Thread erhalten möchtest. Globale Benachrichtigungs-Einstellungen werden zusätzlich angewandt. Das Deaktivieren einer Benachrichtigung hat Vorrang."
|
||||||
ffVisibility: "Sichtbarkeit von Gefolgten/Followern"
|
ffVisibility: "Sichtbarkeit von Gefolgten/Followern"
|
||||||
ffVisibilityDescription: "Konfiguriere wer sehen kann, wem du folgst sowie wer dir folgt."
|
ffVisibilityDescription: "Konfiguriere wer sehen kann, wem du folgst sowie wer dir folgt."
|
||||||
continueThread: "Weiteren Threadverlauf anzeigen"
|
continueThread: "Weiteren Threadverlauf anzeigen"
|
||||||
|
|
|
@ -804,6 +804,7 @@ makeReactionsPublicDescription: "This will make the list of all your past reacti
|
||||||
classic: "Classic"
|
classic: "Classic"
|
||||||
muteThread: "Mute thread"
|
muteThread: "Mute thread"
|
||||||
unmuteThread: "Unmute thread"
|
unmuteThread: "Unmute thread"
|
||||||
|
threadMuteNotificationsDesc: "Select the notifications you wish to view from this thread. Global notification settings also apply. Disabling takes precedence."
|
||||||
ffVisibility: "Follows/Followers Visibility"
|
ffVisibility: "Follows/Followers Visibility"
|
||||||
ffVisibilityDescription: "Allows you to configure who can see who you follow and who follows you."
|
ffVisibilityDescription: "Allows you to configure who can see who you follow and who follows you."
|
||||||
continueThread: "View thread continuation"
|
continueThread: "View thread continuation"
|
||||||
|
|
|
@ -805,6 +805,7 @@ makeReactionsPublicDescription: "あなたがしたリアクション一覧を
|
||||||
classic: "クラシック"
|
classic: "クラシック"
|
||||||
muteThread: "スレッドをミュート"
|
muteThread: "スレッドをミュート"
|
||||||
unmuteThread: "スレッドのミュートを解除"
|
unmuteThread: "スレッドのミュートを解除"
|
||||||
|
threadMuteNotificationsDesc: "このスレッドから表示する通知を選択します。グローバル通知設定も適用され、禁止が優先されます。"
|
||||||
ffVisibility: "つながりの公開範囲"
|
ffVisibility: "つながりの公開範囲"
|
||||||
ffVisibilityDescription: "自分のフォロー/フォロワー情報の公開範囲を設定できます。"
|
ffVisibilityDescription: "自分のフォロー/フォロワー情報の公開範囲を設定できます。"
|
||||||
continueThread: "さらにスレッドを見る"
|
continueThread: "さらにスレッドを見る"
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
export class threadMuteNotifications1655793461890 {
|
||||||
|
name = 'threadMuteNotifications1655793461890'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`CREATE TYPE "public"."note_thread_muting_mutingnotificationtypes_enum" AS ENUM('mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded')`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "note_thread_muting" ADD "mutingNotificationTypes" "public"."note_thread_muting_mutingnotificationtypes_enum" array NOT NULL DEFAULT '{}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "note_thread_muting" DROP COLUMN "mutingNotificationTypes"`);
|
||||||
|
await queryRunner.query(`DROP TYPE "public"."note_thread_muting_mutingnotificationtypes_enum"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||||
|
import { noteNotificationTypes } from 'foundkey-js';
|
||||||
import { id } from '../id.js';
|
import { id } from '../id.js';
|
||||||
import { User } from './user.js';
|
import { User } from './user.js';
|
||||||
import { Note } from './note.js';
|
import { Note } from './note.js';
|
||||||
|
@ -30,4 +31,11 @@ export class NoteThreadMuting {
|
||||||
length: 256,
|
length: 256,
|
||||||
})
|
})
|
||||||
public threadId: string;
|
public threadId: string;
|
||||||
|
|
||||||
|
@Column('enum', {
|
||||||
|
enum: noteNotificationTypes,
|
||||||
|
array: true,
|
||||||
|
default: [],
|
||||||
|
})
|
||||||
|
public mutingNotificationTypes: typeof notificationTypes[number][];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
|
import { noteNotificationTypes } from 'foundkey-js';
|
||||||
import { db } from '@/db/postgre.js';
|
import { db } from '@/db/postgre.js';
|
||||||
import { aggregateNoteEmojis, prefetchEmojis } from '@/misc/populate-emojis.js';
|
import { aggregateNoteEmojis, prefetchEmojis } from '@/misc/populate-emojis.js';
|
||||||
import { Packed } from '@/misc/schema.js';
|
import { Packed } from '@/misc/schema.js';
|
||||||
|
@ -28,50 +29,18 @@ export const NotificationRepository = db.getRepository(Notification).extend({
|
||||||
isRead: notification.isRead,
|
isRead: notification.isRead,
|
||||||
userId: notification.notifierId,
|
userId: notification.notifierId,
|
||||||
user: notification.notifierId ? Users.pack(notification.notifier || notification.notifierId) : null,
|
user: notification.notifierId ? Users.pack(notification.notifier || notification.notifierId) : null,
|
||||||
...(notification.type === 'mention' ? {
|
...(noteNotificationTypes.includes(notification.type) ? {
|
||||||
note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, {
|
|
||||||
detail: true,
|
|
||||||
_hint_: options._hintForEachNotes_,
|
|
||||||
}),
|
|
||||||
} : {}),
|
|
||||||
...(notification.type === 'reply' ? {
|
|
||||||
note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, {
|
|
||||||
detail: true,
|
|
||||||
_hint_: options._hintForEachNotes_,
|
|
||||||
}),
|
|
||||||
} : {}),
|
|
||||||
...(notification.type === 'renote' ? {
|
|
||||||
note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, {
|
|
||||||
detail: true,
|
|
||||||
_hint_: options._hintForEachNotes_,
|
|
||||||
}),
|
|
||||||
} : {}),
|
|
||||||
...(notification.type === 'quote' ? {
|
|
||||||
note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, {
|
note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, {
|
||||||
detail: true,
|
detail: true,
|
||||||
_hint_: options._hintForEachNotes_,
|
_hint_: options._hintForEachNotes_,
|
||||||
}),
|
}),
|
||||||
} : {}),
|
} : {}),
|
||||||
...(notification.type === 'reaction' ? {
|
...(notification.type === 'reaction' ? {
|
||||||
note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, {
|
|
||||||
detail: true,
|
|
||||||
_hint_: options._hintForEachNotes_,
|
|
||||||
}),
|
|
||||||
reaction: notification.reaction,
|
reaction: notification.reaction,
|
||||||
} : {}),
|
} : {}),
|
||||||
...(notification.type === 'pollVote' ? {
|
...(notification.type === 'pollVote' ? {
|
||||||
note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, {
|
|
||||||
detail: true,
|
|
||||||
_hint_: options._hintForEachNotes_,
|
|
||||||
}),
|
|
||||||
choice: notification.choice,
|
choice: notification.choice,
|
||||||
} : {}),
|
} : {}),
|
||||||
...(notification.type === 'pollEnded' ? {
|
|
||||||
note: Notes.pack(notification.note || notification.noteId!, { id: notification.notifieeId }, {
|
|
||||||
detail: true,
|
|
||||||
_hint_: options._hintForEachNotes_,
|
|
||||||
}),
|
|
||||||
} : {}),
|
|
||||||
...(notification.type === 'groupInvited' ? {
|
...(notification.type === 'groupInvited' ? {
|
||||||
invitation: UserGroupInvitations.pack(notification.userGroupInvitationId!),
|
invitation: UserGroupInvitations.pack(notification.userGroupInvitationId!),
|
||||||
} : {}),
|
} : {}),
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Not } from 'typeorm';
|
import { ArrayOverlap, Not } from 'typeorm';
|
||||||
import { publishNoteStream } from '@/services/stream.js';
|
import { publishNoteStream } from '@/services/stream.js';
|
||||||
import { createNotification } from '@/services/create-notification.js';
|
import { createNotification } from '@/services/create-notification.js';
|
||||||
import { deliver } from '@/queue/index.js';
|
import { deliver } from '@/queue/index.js';
|
||||||
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
||||||
import renderVote from '@/remote/activitypub/renderer/vote.js';
|
import renderVote from '@/remote/activitypub/renderer/vote.js';
|
||||||
import { deliverQuestionUpdate } from '@/services/note/polls/update.js';
|
import { deliverQuestionUpdate } from '@/services/note/polls/update.js';
|
||||||
import { PollVotes, NoteWatchings, Users, Polls, Blockings } from '@/models/index.js';
|
import { PollVotes, NoteWatchings, Users, Polls, Blockings, NoteThreadMutings } from '@/models/index.js';
|
||||||
import { IRemoteUser } from '@/models/entities/user.js';
|
import { IRemoteUser } from '@/models/entities/user.js';
|
||||||
import { genId } from '@/misc/gen-id.js';
|
import { genId } from '@/misc/gen-id.js';
|
||||||
import { getNote } from '../../../common/getters.js';
|
import { getNote } from '../../../common/getters.js';
|
||||||
|
@ -136,14 +136,24 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notify
|
// check if this thread and notification type is muted
|
||||||
createNotification(note.userId, 'pollVote', {
|
const threadMuted = await NoteThreadMutings.findOne({
|
||||||
notifierId: user.id,
|
userId: note.userId,
|
||||||
noteId: note.id,
|
threadId: note.threadId || note.id,
|
||||||
choice: ps.choice,
|
mutingNotificationTypes: ArrayOverlap(['pollVote']),
|
||||||
});
|
});
|
||||||
|
// Notify
|
||||||
|
if (!threadMuted) {
|
||||||
|
createNotification(note.userId, 'pollVote', {
|
||||||
|
notifierId: user.id,
|
||||||
|
noteId: note.id,
|
||||||
|
choice: ps.choice,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch watchers
|
// Fetch watchers
|
||||||
|
// checking for mutes is not necessary here, as note watchings will be
|
||||||
|
// deleted when a thread is muted
|
||||||
NoteWatchings.findBy({
|
NoteWatchings.findBy({
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
userId: Not(user.id),
|
userId: Not(user.id),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Notes, NoteThreadMutings } from '@/models/index.js';
|
import { noteNotificationTypes } from 'foundkey-js';
|
||||||
|
import { Notes, NoteThreadMutings, NoteWatchings } from '@/models/index.js';
|
||||||
import { genId } from '@/misc/gen-id.js';
|
import { genId } from '@/misc/gen-id.js';
|
||||||
import readNote from '@/services/note/read.js';
|
import readNote from '@/services/note/read.js';
|
||||||
import define from '../../../define.js';
|
import define from '../../../define.js';
|
||||||
|
@ -25,6 +26,14 @@ export const paramDef = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
noteId: { type: 'string', format: 'misskey:id' },
|
noteId: { type: 'string', format: 'misskey:id' },
|
||||||
|
mutingNotificationTypes: {
|
||||||
|
description: 'Defines which notification types from the thread should be muted. Replies are always muted. Applies in addition to the global settings, muting takes precedence.',
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'string', enum: noteNotificationTypes,
|
||||||
|
},
|
||||||
|
uniqueItems: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
required: ['noteId'],
|
required: ['noteId'],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -51,5 +60,19 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
threadId: note.threadId || note.id,
|
threadId: note.threadId || note.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
mutingNotificationTypes: ps.mutingNotificationTypes,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// remove all note watchings in the muted thread
|
||||||
|
const notesThread = Notes.createQueryBuilder("notes")
|
||||||
|
.select("note.id")
|
||||||
|
.where({
|
||||||
|
threadId: note.threadId ?? note.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await NoteWatchings.createQueryBuilder()
|
||||||
|
.delete()
|
||||||
|
.where(`"note_watching"."noteId" IN (${ notesThread.getQuery() })`)
|
||||||
|
.setParameters(notesThread.getParameters())
|
||||||
|
.execute();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Not, In } from 'typeorm';
|
import { ArrayOverlap, Not, In } from 'typeorm';
|
||||||
import * as mfm from 'mfm-js';
|
import * as mfm from 'mfm-js';
|
||||||
import { db } from '@/db/postgre.js';
|
import { db } from '@/db/postgre.js';
|
||||||
import es from '@/db/elasticsearch.js';
|
import es from '@/db/elasticsearch.js';
|
||||||
|
@ -80,15 +80,19 @@ class NotificationManager {
|
||||||
|
|
||||||
public async deliver() {
|
public async deliver() {
|
||||||
for (const x of this.queue) {
|
for (const x of this.queue) {
|
||||||
// ミュート情報を取得
|
// check if the sender or thread are muted
|
||||||
const mentioneeMutes = await Mutings.findBy({
|
const userMuted = await Mutings.findOneBy({
|
||||||
muterId: x.target,
|
muterId: x.target,
|
||||||
|
muteeId: this.notifier.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mentioneesMutedUserIds = mentioneeMutes.map(m => m.muteeId);
|
const threadMuted = await NoteThreadMutings.findOneBy({
|
||||||
|
userId: x.target,
|
||||||
|
threadId: this.note.threadId || this.note.id,
|
||||||
|
mutingNotificationTypes: ArrayOverlap([x.reason]),
|
||||||
|
});
|
||||||
|
|
||||||
// 通知される側のユーザーが通知する側のユーザーをミュートしていない限りは通知する
|
if (!userMuted && !threadMuted) {
|
||||||
if (!mentioneesMutedUserIds.includes(this.notifier.id)) {
|
|
||||||
createNotification(x.target, x.reason, {
|
createNotification(x.target, x.reason, {
|
||||||
notifierId: this.notifier.id,
|
notifierId: this.notifier.id,
|
||||||
noteId: this.note.id,
|
noteId: this.note.id,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Not } from 'typeorm';
|
import { ArrayOverlap, Not } from 'typeorm';
|
||||||
import { publishNoteStream } from '@/services/stream.js';
|
import { publishNoteStream } from '@/services/stream.js';
|
||||||
import { CacheableUser } from '@/models/entities/user.js';
|
import { CacheableUser } from '@/models/entities/user.js';
|
||||||
import { Note } from '@/models/entities/note.js';
|
import { Note } from '@/models/entities/note.js';
|
||||||
import { PollVotes, NoteWatchings, Polls, Blockings } from '@/models/index.js';
|
import { PollVotes, NoteWatchings, Polls, Blockings, NoteThreadMutings } from '@/models/index.js';
|
||||||
import { genId } from '@/misc/gen-id.js';
|
import { genId } from '@/misc/gen-id.js';
|
||||||
import { createNotification } from '../../create-notification.js';
|
import { createNotification } from '../../create-notification.js';
|
||||||
|
|
||||||
|
@ -57,12 +57,20 @@ export default async function(user: CacheableUser, note: Note, choice: number) {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notify
|
// check if this thread and notification type is muted
|
||||||
createNotification(note.userId, 'pollVote', {
|
const muted = await NoteThreadMutings.findOne({
|
||||||
notifierId: user.id,
|
userId: note.userId,
|
||||||
noteId: note.id,
|
threadId: note.threadId || note.id,
|
||||||
choice,
|
mutingNotificationTypes: ArrayOverlap(['pollVote']),
|
||||||
});
|
});
|
||||||
|
// Notify
|
||||||
|
if (!muted) {
|
||||||
|
createNotification(note.userId, 'pollVote', {
|
||||||
|
notifierId: user.id,
|
||||||
|
noteId: note.id,
|
||||||
|
choice: choice,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch watchers
|
// Fetch watchers
|
||||||
NoteWatchings.findBy({
|
NoteWatchings.findBy({
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IsNull, Not } from 'typeorm';
|
import { ArrayOverlap, IsNull, Not } from 'typeorm';
|
||||||
import { publishNoteStream } from '@/services/stream.js';
|
import { publishNoteStream } from '@/services/stream.js';
|
||||||
import { renderLike } from '@/remote/activitypub/renderer/like.js';
|
import { renderLike } from '@/remote/activitypub/renderer/like.js';
|
||||||
import DeliverManager from '@/remote/activitypub/deliver-manager.js';
|
import DeliverManager from '@/remote/activitypub/deliver-manager.js';
|
||||||
|
@ -6,7 +6,7 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
||||||
import { toDbReaction, decodeReaction } from '@/misc/reaction-lib.js';
|
import { toDbReaction, decodeReaction } from '@/misc/reaction-lib.js';
|
||||||
import { User, IRemoteUser } from '@/models/entities/user.js';
|
import { User, IRemoteUser } from '@/models/entities/user.js';
|
||||||
import { Note } from '@/models/entities/note.js';
|
import { Note } from '@/models/entities/note.js';
|
||||||
import { NoteReactions, Users, NoteWatchings, Notes, Emojis, Blockings } from '@/models/index.js';
|
import { NoteReactions, Users, NoteWatchings, Notes, Emojis, Blockings, NoteThreadMutings } from '@/models/index.js';
|
||||||
import { perUserReactionsChart } from '@/services/chart/index.js';
|
import { perUserReactionsChart } from '@/services/chart/index.js';
|
||||||
import { genId } from '@/misc/gen-id.js';
|
import { genId } from '@/misc/gen-id.js';
|
||||||
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
||||||
|
@ -98,8 +98,14 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// check if this thread is muted
|
||||||
|
const threadMuted = await NoteThreadMutings.findOne({
|
||||||
|
userId: note.userId,
|
||||||
|
threadId: note.threadId || note.id,
|
||||||
|
mutingNotificationTypes: ArrayOverlap(['reaction']),
|
||||||
|
});
|
||||||
// リアクションされたユーザーがローカルユーザーなら通知を作成
|
// リアクションされたユーザーがローカルユーザーなら通知を作成
|
||||||
if (note.userHost === null) {
|
if (note.userHost === null && !threadMuted) {
|
||||||
createNotification(note.userId, 'reaction', {
|
createNotification(note.userId, 'reaction', {
|
||||||
notifierId: user.id,
|
notifierId: user.id,
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
</MkSwitch>
|
</MkSwitch>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!useGlobalSetting" class="_section">
|
<div v-if="!useGlobalSetting" class="_section">
|
||||||
<MkInfo>{{ i18n.ts.notificationSettingDesc }}</MkInfo>
|
<MkInfo>{{ message }}</MkInfo>
|
||||||
<MkButton inline @click="disableAll">{{ i18n.ts.disableAll }}</MkButton>
|
<MkButton inline @click="disableAll">{{ i18n.ts.disableAll }}</MkButton>
|
||||||
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
|
<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
|
||||||
<MkSwitch v-for="ntype in notificationTypes" :key="ntype" v-model="typesMap[ntype]">{{ i18n.t(`_notification._types.${ntype}`) }}</MkSwitch>
|
<MkSwitch v-for="ntype in notificationTypes" :key="ntype" v-model="typesMap[ntype]">{{ i18n.t(`_notification._types.${ntype}`) }}</MkSwitch>
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { notificationTypes } from 'foundkey-js';
|
import * as foundkey from 'foundkey-js';
|
||||||
import MkSwitch from './form/switch.vue';
|
import MkSwitch from './form/switch.vue';
|
||||||
import MkInfo from './ui/info.vue';
|
import MkInfo from './ui/info.vue';
|
||||||
import MkButton from './ui/button.vue';
|
import MkButton from './ui/button.vue';
|
||||||
|
@ -41,21 +41,25 @@ const emit = defineEmits<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
includingTypes?: typeof notificationTypes[number][] | null;
|
includingTypes?: typeof foundkey.notificationTypes[number][] | null;
|
||||||
|
notificationTypes?: typeof foundkey.notificationTypes[number][] | null;
|
||||||
showGlobalToggle?: boolean;
|
showGlobalToggle?: boolean;
|
||||||
|
message?: string,
|
||||||
}>(), {
|
}>(), {
|
||||||
includingTypes: () => [],
|
includingTypes: () => [],
|
||||||
|
notificationTypes: () => [],
|
||||||
showGlobalToggle: true,
|
showGlobalToggle: true,
|
||||||
|
message: i18n.ts.notificationSettingDesc,
|
||||||
});
|
});
|
||||||
|
|
||||||
let includingTypes = $computed(() => props.includingTypes || []);
|
let includingTypes = $computed(() => props.includingTypes || []);
|
||||||
|
|
||||||
const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
||||||
|
|
||||||
let typesMap = $ref<Record<typeof notificationTypes[number], boolean>>({});
|
let typesMap = $ref<Record<typeof foundkey.notificationTypes[number], boolean>>({});
|
||||||
let useGlobalSetting = $ref((includingTypes === null || includingTypes.length === 0) && props.showGlobalToggle);
|
let useGlobalSetting = $ref((includingTypes === null || includingTypes.length === 0) && props.showGlobalToggle);
|
||||||
|
|
||||||
for (const ntype of notificationTypes) {
|
for (const ntype of props.notificationTypes) {
|
||||||
typesMap[ntype] = includingTypes.includes(ntype);
|
typesMap[ntype] = includingTypes.includes(ntype);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +68,7 @@ function ok() {
|
||||||
emit('done', { includingTypes: null });
|
emit('done', { includingTypes: null });
|
||||||
} else {
|
} else {
|
||||||
emit('done', {
|
emit('done', {
|
||||||
includingTypes: (Object.keys(typesMap) as typeof notificationTypes[number][])
|
includingTypes: (Object.keys(typesMap) as typeof foundkey.notificationTypes[number][])
|
||||||
.filter(type => typesMap[type]),
|
.filter(type => typesMap[type]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -74,13 +78,13 @@ function ok() {
|
||||||
|
|
||||||
function disableAll() {
|
function disableAll() {
|
||||||
for (const type in typesMap) {
|
for (const type in typesMap) {
|
||||||
typesMap[type as typeof notificationTypes[number]] = false;
|
typesMap[type as typeof foundkey.notificationTypes[number]] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableAll() {
|
function enableAll() {
|
||||||
for (const type in typesMap) {
|
for (const type in typesMap) {
|
||||||
typesMap[type as typeof notificationTypes[number]] = true;
|
typesMap[type as typeof foundkey.notificationTypes[number]] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -65,9 +65,33 @@ export function getNoteMenu(props: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleThreadMute(mute: boolean): void {
|
function muteThread(): void {
|
||||||
os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', {
|
// show global settings by default
|
||||||
noteId: appearNote.id,
|
const includingTypes = foundkey.notificationTypes.filter(x => !$i.mutingNotificationTypes.includes(x));
|
||||||
|
os.popup(defineAsyncComponent(() => import('@/components/notification-setting-window.vue')), {
|
||||||
|
includingTypes,
|
||||||
|
showGlobalToggle: false,
|
||||||
|
message: i18n.ts.threadMuteNotificationsDesc,
|
||||||
|
notificationTypes: foundkey.noteNotificationTypes,
|
||||||
|
}, {
|
||||||
|
done: async (res) => {
|
||||||
|
const { includingTypes: value } = res;
|
||||||
|
let mutingNotificationTypes: string[] | undefined;
|
||||||
|
if (value != null) {
|
||||||
|
mutingNotificationTypes = foundkey.noteNotificationTypes.filter(x => !value.includes(x))
|
||||||
|
}
|
||||||
|
|
||||||
|
await os.apiWithDialog('notes/thread-muting/create', {
|
||||||
|
noteId: appearNote.id,
|
||||||
|
mutingNotificationTypes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 'closed');
|
||||||
|
}
|
||||||
|
|
||||||
|
function unmuteThread(): void {
|
||||||
|
os.apiWithDialog('notes/thread-muting/delete', {
|
||||||
|
noteId: appearNote.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,11 +275,11 @@ export function getNoteMenu(props: {
|
||||||
statePromise.then(state => state.isMutedThread ? {
|
statePromise.then(state => state.isMutedThread ? {
|
||||||
icon: 'fas fa-comment-slash',
|
icon: 'fas fa-comment-slash',
|
||||||
text: i18n.ts.unmuteThread,
|
text: i18n.ts.unmuteThread,
|
||||||
action: () => toggleThreadMute(false),
|
action: () => unmuteThread(),
|
||||||
} : {
|
} : {
|
||||||
icon: 'fas fa-comment-slash',
|
icon: 'fas fa-comment-slash',
|
||||||
text: i18n.ts.muteThread,
|
text: i18n.ts.muteThread,
|
||||||
action: () => toggleThreadMute(true),
|
action: () => muteThread(),
|
||||||
}),
|
}),
|
||||||
appearNote.userId === $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? {
|
appearNote.userId === $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? {
|
||||||
icon: 'fas fa-thumbtack',
|
icon: 'fas fa-thumbtack',
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'] as const;
|
export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'] as const;
|
||||||
|
|
||||||
|
export const noteNotificationTypes = ['mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded'] as const;
|
||||||
|
|
||||||
export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const;
|
export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const;
|
||||||
|
|
||||||
export const ffVisibility = ['public', 'followers', 'private'] as const;
|
export const ffVisibility = ['public', 'followers', 'private'] as const;
|
||||||
|
|
|
@ -10,6 +10,7 @@ export {
|
||||||
export {
|
export {
|
||||||
permissions,
|
permissions,
|
||||||
notificationTypes,
|
notificationTypes,
|
||||||
|
noteNotificationTypes,
|
||||||
mutedNoteReasons,
|
mutedNoteReasons,
|
||||||
ffVisibility,
|
ffVisibility,
|
||||||
} from './consts.js';
|
} from './consts.js';
|
||||||
|
|
Loading…
Reference in a new issue