From f2f5cfe2e8130f213a83edea0dfc189cbe4f5738 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 29 Jan 2025 02:53:06 +0900 Subject: [PATCH 1/4] =?UTF-8?q?enhance(backend):=20=E3=82=B9=E3=83=AC?= =?UTF-8?q?=E3=83=83=E3=83=89=E3=83=9F=E3=83=A5=E3=83=BC=E3=83=88=E3=81=AB?= =?UTF-8?q?=E3=81=8A=E3=81=84=E3=81=A6=E3=80=81RN/=E5=BC=95=E7=94=A8/?= =?UTF-8?q?=E3=83=AA=E3=82=A2=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AE?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E3=82=82=E3=83=9F=E3=83=A5=E3=83=BC=E3=83=88?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(misskey-dev#15?= =?UTF-8?q?271)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 7de945d5d98934f1354bf3d579710c1cc6c4a29f Merge: ff3c1540a9 4f31dcfed3 Author: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Mon Jan 27 19:25:03 2025 +0900 Merge branch 'develop' into enhance-thread-mute commit ff3c1540a9aaaf81f9b607208a7caebf176dc5b3 Merge: 97d6561647 8f37fb6713 Author: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Sat Jan 25 21:39:48 2025 +0900 Merge branch 'develop' into enhance-thread-mute commit 97d65616474f821dc16da1c3283014bb94d381a6 Merge: 72aa1cb55e 31ccefa050 Author: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Tue Jan 21 19:47:39 2025 +0900 Merge branch 'develop' into enhance-thread-mute commit 72aa1cb55ee516fab1e93ecaf693ef44b932358f Author: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Sun Jan 19 03:57:55 2025 +0900 test(backend): fix test commit 9f94145dd43a5ab2a55f85113791edddd0ae615c Author: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Sun Jan 19 03:49:07 2025 +0900 test(backend): fix test commit cf67704713b83b335d8d5b615bb08a6d0ca917fe Author: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Wed Jan 15 23:23:11 2025 +0900 test(backend): update thread-mute test commit a379c85c8e472cca89685a72bb105d40a205d986 Merge: fb43b2deb5 145c6cf2b5 Author: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Tue Jan 14 21:39:11 2025 +0900 Merge branch 'develop' into enhance-thread-mute commit fb43b2deb50f1de8992aa5ba07f09e89b83dca74 Author: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Mon Jan 13 21:45:26 2025 +0900 docs(changelog): update changelog commit 17bf0323b19be0e2a5804af63e248572259b9ac9 Author: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Mon Jan 13 20:39:30 2025 +0900 fix(backend): thread mute did not suppress Quote when Quoted is not muted but Quoting is on muted tree commit 109718f4f2a04edd44b4852c65c71f294c163210 Author: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Mon Jan 13 17:18:30 2025 +0900 chore: move comment position commit b3d0ac970daaa1ba3dff2dc1614a3ca6f15fa08c Author: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Sun Jul 21 00:25:43 2024 +0900 fix(backend): thread mute did not suppress Reaction commit 61f517a3d1933817f23701694aae2974ca669b91 Author: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Sat Jul 20 23:44:05 2024 +0900 fix(backend): thread mute did not suppress RN/Quote --- CHANGELOG.md | 5 +- .../backend/src/core/NoteCreateService.ts | 24 ++++-- packages/backend/src/core/ReactionService.ts | 22 ++++-- packages/backend/test/e2e/thread-mute.ts | 75 ++++++++++++++++--- 4 files changed, 103 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4511bd5ef174..1c93883a46f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,10 @@ - Fix: ログインしていないときに VRTL が見えない問題 ### Server -- +- Enhance: スレッドミュートにおいて、リノート、引用、リアクションの通知もミュートするように + - なお、以下のケースでは引き続き通知がミュートされません。(ミュートを行っているユーザーをAとします) + - ミュート対象ノートを、当該スレッドの外にあるAへの返信/メンション付きノートにおいて引用する + ## 2025.1.0 (merged to 2025.1.0-kinel.1) ### Note diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index f355218fa8e7..679b085f25bb 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -643,6 +643,7 @@ export class NoteCreateService implements OnApplicationShutdown { await this.createMentionedEvents(mentionedUsers, note, nm); // If has in reply to note + let isOnThreadMutedTree = false; if (data.reply) { // 通知 if (data.reply.userHost === null) { @@ -652,6 +653,7 @@ export class NoteCreateService implements OnApplicationShutdown { threadId: data.reply.threadId ?? data.reply.id, }, }); + isOnThreadMutedTree = isThreadMuted; if (!isThreadMuted) { nm.push(data.reply.userId, 'reply'); @@ -667,13 +669,23 @@ export class NoteCreateService implements OnApplicationShutdown { // Notify if (data.renote.userHost === null) { - nm.push(data.renote.userId, type); - } + const isThreadMuted = await this.noteThreadMutingsRepository.exists({ + where: { + userId: data.renote.userId, + threadId: data.renote.threadId ?? data.renote.id, + }, + }); + + // If the quoted note is not thread muted but the quoting note is on thread muted tree, need to mute it. + if (!isThreadMuted && !isOnThreadMutedTree) { + nm.push(data.renote.userId, type); - // Publish event - if ((user.id !== data.renote.userId) && data.renote.userHost === null) { - this.globalEventService.publishMainStream(data.renote.userId, 'renote', noteObj); - this.webhookService.enqueueUserWebhook(data.renote.userId, 'renote', { note: noteObj }); + // Publish event + if (user.id !== data.renote.userId) { + this.globalEventService.publishMainStream(data.renote.userId, 'renote', noteObj); + this.webhookService.enqueueUserWebhook(data.renote.userId, 'renote', { note: noteObj }); + } + } } } diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index 7d2e0a5be6f7..d5c08a12b818 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -5,7 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository, MiMeta } from '@/models/_.js'; +import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository, MiMeta, NoteThreadMutingsRepository } from '@/models/_.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import type { MiRemoteUser, MiUser } from '@/models/User.js'; import type { MiNote } from '@/models/Note.js'; @@ -82,6 +82,9 @@ export class ReactionService { @Inject(DI.noteReactionsRepository) private noteReactionsRepository: NoteReactionsRepository, + @Inject(DI.noteThreadMutingsRepository) + private noteThreadMutingsRepository: NoteThreadMutingsRepository, + @Inject(DI.emojisRepository) private emojisRepository: EmojisRepository, @@ -256,10 +259,19 @@ export class ReactionService { // リアクションされたユーザーがローカルユーザーなら通知を作成 if (note.userHost === null) { - this.notificationService.createNotification(note.userId, 'reaction', { - noteId: note.id, - reaction: reaction, - }, user.id); + const isThreadMuted = await this.noteThreadMutingsRepository.exists({ + where: { + userId: note.userId, + threadId: note.threadId ?? note.id, + }, + }); + + if (!isThreadMuted) { + this.notificationService.createNotification(note.userId, 'reaction', { + noteId: note.id, + reaction: reaction, + }, user.id); + } } //#region 配信 diff --git a/packages/backend/test/e2e/thread-mute.ts b/packages/backend/test/e2e/thread-mute.ts index 1ac99df884fe..0fbc8258a54b 100644 --- a/packages/backend/test/e2e/thread-mute.ts +++ b/packages/backend/test/e2e/thread-mute.ts @@ -6,9 +6,14 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; -import { api, connectStream, post, signup } from '../utils.js'; +import { setTimeout } from 'node:timers/promises'; +import { api, connectStream, post, signup, react } from '../utils.js'; import type * as misskey from 'misskey-js'; +function waitForPushToNotification() { + return setTimeout(500); +} + describe('Note thread mute', () => { let alice: misskey.entities.SignupResponse; let bob: misskey.entities.SignupResponse; @@ -29,6 +34,8 @@ describe('Note thread mute', () => { const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); const carolReplyWithoutMention = await post(carol, { replyId: aliceReply.id, text: 'child note' }); + await waitForPushToNotification(); + const res = await api('notes/mentions', {}, alice); assert.strictEqual(res.status, 200); @@ -48,13 +55,15 @@ describe('Note thread mute', () => { const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); + await waitForPushToNotification(); + const res = await api('i', {}, alice); assert.strictEqual(res.status, 200); assert.strictEqual(res.body.hasUnreadMentions, false); }); - test('ミュートしているスレッドからメンションされても、ストリームに unreadMention イベントが流れてこない', () => new Promise(async done => { + test('ミュートしているスレッドからメンションされても、ストリームに unreadMention イベントが流れてこない', async () => { // 状態リセット await api('i/read-all-unread-notes', {}, alice); @@ -66,21 +75,19 @@ describe('Note thread mute', () => { const ws = await connectStream(alice, 'main', async ({ type, body }) => { if (type === 'unreadMention') { - if (body === bobNote.id) return; - fired = true; + if (body === bobNote.id) fired = true; } }); const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); - setTimeout(() => { - assert.strictEqual(fired, false); - ws.close(); - done(); - }, 5000); - })); + await setTimeout(5000); + + assert.strictEqual(fired, false); + ws.close(); + }); - test('i/notifications にミュートしているスレッドの通知が含まれない', async () => { + test('i/notifications にミュートしているスレッドの通知(メンション, リプライ, リノート, 引用リノート, リアクション)が含まれない', async () => { const bobNote = await post(bob, { text: '@alice @carol root note' }); const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' }); @@ -88,6 +95,11 @@ describe('Note thread mute', () => { const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); const carolReplyWithoutMention = await post(carol, { replyId: aliceReply.id, text: 'child note' }); + const carolRenote = await post(carol, { renoteId: aliceReply.id }); + const carolQuote = await post(carol, { renoteId: aliceReply.id, text: 'quote note' }); + await react(carol, aliceReply, 'like'); // react method returns nothing. + + await waitForPushToNotification(); const res = await api('i/notifications', {}, alice); @@ -95,7 +107,48 @@ describe('Note thread mute', () => { assert.strictEqual(Array.isArray(res.body), true); assert.strictEqual(res.body.some(notification => 'note' in notification && notification.note.id === carolReply.id), false); assert.strictEqual(res.body.some(notification => 'note' in notification && notification.note.id === carolReplyWithoutMention.id), false); + assert.strictEqual(res.body.some(notification => 'note' in notification && notification.note.id === carolRenote.id), false); + assert.strictEqual(res.body.some(notification => 'note' in notification && notification.note.id === carolQuote.id), false); + assert.strictEqual(res.body.some(notification => 'userId' in notification && notification.userId === carol.id), false); // NOTE: bobの投稿はスレッドミュート前に行われたため通知に含まれていてもよい }); + + test('ミュートしているスレッドへのリプライで、ミュートしていないスレッドのノートが引用されても i/notifications に通知が含まれない', async () => { + const bobNote = await post(bob, { text: '@alice @carol root note' }); + const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' }); + const aliceNote = await post(alice, { text: 'another root note' }); + + await api('notes/thread-muting/create', { noteId: bobNote.id }, alice); + + const carolReplyWithQuotingAnother = await post(carol, { replyId: aliceReply.id, renoteId: aliceNote.id, text: 'child note with quoting another note' }); + + await waitForPushToNotification(); + + const res = await api('i/notifications', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + assert.strictEqual(res.body.some(notification => 'note' in notification && notification.note.id === carolReplyWithQuotingAnother.id), false); + }); + + test('ミュートしていないスレッドでのメンション付きノートまたはリプライで、ミュートしているスレッドのノートが引用された場合は i/notifications に通知が含まれる', async () => { + const bobNote = await post(bob, { text: '@alice @carol root note' }); + const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' }); + const aliceNote = await post(alice, { text: 'another root note' }); + + await api('notes/thread-muting/create', { noteId: bobNote.id }, alice); + + const carolMentionWithQuotingMuted = await post(carol, { renoteId: aliceReply.id, text: '@alice another root note with quoting muted note' }); + const carolReplyWithQuotingMuted = await post(carol, { replyId: aliceNote.id, renoteId: aliceReply.id, text: 'another child note with quoting muted note' }); + + await waitForPushToNotification(); + + const res = await api('i/notifications', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + assert.strictEqual(res.body.some(notification => 'note' in notification && notification.note.id === carolMentionWithQuotingMuted.id), true); + assert.strictEqual(res.body.some(notification => 'note' in notification && notification.note.id === carolReplyWithQuotingMuted.id), true); + }); }); From 2e38f84db5cab1e7ba99262292ad2cbafcbb37ee Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 29 Jan 2025 02:53:38 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat(frontend):=20=E7=94=BB=E5=83=8F?= =?UTF-8?q?=E3=82=92=E6=8A=95=E7=A8=BF=E5=89=8D=E3=81=AB=E3=83=97=E3=83=AC?= =?UTF-8?q?=E3=83=93=E3=83=A5=E3=83=BC=E5=8F=AF=E8=83=BD=E3=81=AB=20(missk?= =?UTF-8?q?ey-dev#15341)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 83d2794c30326e9b64069d46404780656023440a Author: taichan <40626578+tai-cha@users.noreply.github.com> Date: Mon Jan 27 10:23:18 2025 +0900 Update packages/frontend/.storybook/generate.tsx commit 842c745b982a8fc00a69e3b117f778e14c37c948 Author: tai-cha Date: Sat Jan 25 17:09:01 2025 +0900 Add MkImgPreviewDialog to storybook generator commit e1365fb43ecde11127fcb8d8a044eac01354a0a8 Author: tai-cha Date: Sat Jan 25 17:02:37 2025 +0900 backgroundのスタイル変更 Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com> commit 4bb30a1fb9c043090f4f804cd95fdb4732505cf6 Author: tai-cha Date: Sat Jan 25 16:59:32 2025 +0900 Add storybook for MkImgPreviewDialog commit c838f8005b3b7f100b1ebc5d986bda2410762fc8 Author: tai-cha Date: Sat Jan 25 16:43:25 2025 +0900 消えてたのFix commit b4842c0ad5b4ed258f0bb30df74eee1ec8df66ae Author: tai-cha Date: Sat Jan 25 16:41:09 2025 +0900 SPDX commit 5b0aec5ba5bca0e9f07bd7d033e275fe269eae56 Author: tai-cha Date: Sat Jan 25 16:33:33 2025 +0900 Update Changelog commit ce787e49aa70e75720e5069594742ffb108e2f82 Author: tai-cha Date: Sat Jan 25 16:31:51 2025 +0900 feat(client): 画像をプレビュー可能に --- CHANGELOG.md | 1 + packages/frontend/.storybook/generate.tsx | 1 + .../MkImgPreviewDialog.stories.impl.ts | 40 +++++++++++++ .../src/components/MkImgPreviewDialog.vue | 58 +++++++++++++++++++ .../src/components/MkPostFormAttaches.vue | 20 +++++-- 5 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts create mode 100644 packages/frontend/src/components/MkImgPreviewDialog.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c93883a46f5..b57a55510f72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - ### Client +- Feat: 投稿フォームで画像をプレビュー可能に - Fix: ログインしていないときに VRTL が見えない問題 ### Server diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx index 8830523810e5..3cd08191f5fa 100644 --- a/packages/frontend/.storybook/generate.tsx +++ b/packages/frontend/.storybook/generate.tsx @@ -414,6 +414,7 @@ function toStories(component: string): Promise { glob('src/components/MkSignupServerRules.vue'), glob('src/components/MkUserSetupDialog.vue'), glob('src/components/MkUserSetupDialog.*.vue'), + glob('src/components/MkImgPreviewDialog.vue'), glob('src/components/MkInstanceCardMini.vue'), glob('src/components/MkInviteCode.vue'), glob('src/components/MkTagItem.vue'), diff --git a/packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts b/packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts new file mode 100644 index 000000000000..339e6d10f39a --- /dev/null +++ b/packages/frontend/src/components/MkImgPreviewDialog.stories.impl.ts @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { StoryObj } from '@storybook/vue3'; +import { file } from '../../.storybook/fakes.js'; +import MkImgPreviewDialog from './MkImgPreviewDialog.vue'; +export const Default = { + render(args) { + return { + components: { + MkImgPreviewDialog, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + file: file(), + }, + parameters: { + chromatic: { + // NOTE: ロードが終わるまで待つ + delay: 3000, + }, + layout: 'centered', + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/MkImgPreviewDialog.vue b/packages/frontend/src/components/MkImgPreviewDialog.vue new file mode 100644 index 000000000000..3e6e4e0ec98b --- /dev/null +++ b/packages/frontend/src/components/MkImgPreviewDialog.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue index 1336b61356fb..33fe82b75965 100644 --- a/packages/frontend/src/components/MkPostFormAttaches.vue +++ b/packages/frontend/src/components/MkPostFormAttaches.vue @@ -22,20 +22,24 @@ SPDX-License-Identifier: AGPL-3.0-only -

{{ 16 - props.modelValue.length }}/16

+

+ {{ 16 - props.modelValue.length }}/16 +