From 9828562e57f742a9beb1a143fee045cd6aa7c470 Mon Sep 17 00:00:00 2001 From: piuvas Date: Mon, 23 Dec 2024 21:03:04 -0300 Subject: [PATCH 01/26] testing w/ reactions and stuff --- .../src/core/entities/NoteEntityService.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index eb6b353752..4f229da211 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -529,6 +529,7 @@ export class NoteEntityService implements OnModuleInit { const oldId = this.idService.gen(Date.now() - 2000); for (const note of notes) { + // get my reaction for a renote. if (isPureRenote(note)) { const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.renote.reactions, bufferedReactions?.get(note.renote.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); if (reactionsCount === 0) { @@ -544,6 +545,39 @@ export class NoteEntityService implements OnModuleInit { } else { idsNeedFetchMyReaction.add(note.renote.id); } + // get my reaction for OP if this is a renote of a reply. + if (note.renote.reply) { + const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.renote.reply.reactions, bufferedReactions?.get(note.renote.reply.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); + if (reactionsCount === 0) { + myReactionsMap.set(note.renote.reply.id, null); + } else if (reactionsCount <= note.renote.reply.reactionAndUserPairCache.length + (bufferedReactions?.get(note.renote.reply.id)?.pairs.length ?? 0)) { + const pairInBuffer = bufferedReactions?.get(note.renote.reply.id)?.pairs.find(p => p[0] === meId); + if (pairInBuffer) { + myReactionsMap.set(note.renote.reply.id, pairInBuffer[1]); + } else { + const pair = note.renote.reply.reactionAndUserPairCache.find(p => p.startsWith(meId)); + myReactionsMap.set(note.renote.reply.id, pair ? pair.split('/')[1] : null); + } + } else { + idsNeedFetchMyReaction.add(note.renote.reply.id); + } + } + // get my reaction for OP if this is a reply. + } else if (note.reply) { + const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.reply.reactions, bufferedReactions?.get(note.reply.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); + if (reactionsCount === 0) { + myReactionsMap.set(note.reply.id, null); + } else if (reactionsCount <= note.reply.reactionAndUserPairCache.length + (bufferedReactions?.get(note.reply.id)?.pairs.length ?? 0)) { + const pairInBuffer = bufferedReactions?.get(note.reply.id)?.pairs.find(p => p[0] === meId); + if (pairInBuffer) { + myReactionsMap.set(note.reply.id, pairInBuffer[1]); + } else { + const pair = note.reply.reactionAndUserPairCache.find(p => p.startsWith(meId)); + myReactionsMap.set(note.reply.id, pair ? pair.split('/')[1] : null); + } + } else { + idsNeedFetchMyReaction.add(note.reply.id); + } } else { if (note.id < oldId) { const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.get(note.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); From e5312da6fe356d77474770575e0534d62c22149a Mon Sep 17 00:00:00 2001 From: piuvas Date: Wed, 25 Dec 2024 14:19:05 -0300 Subject: [PATCH 02/26] improve readability and conciseness --- .../src/core/entities/NoteEntityService.ts | 84 ++++++------------- 1 file changed, 25 insertions(+), 59 deletions(-) diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 4f229da211..be45e75d74 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -528,78 +528,44 @@ export class NoteEntityService implements OnModuleInit { // パフォーマンスのためノートが作成されてから2秒以上経っていない場合はリアクションを取得しない const oldId = this.idService.gen(Date.now() - 2000); + const targetNotes: MiNote[] = []; for (const note of notes) { - // get my reaction for a renote. if (isPureRenote(note)) { - const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.renote.reactions, bufferedReactions?.get(note.renote.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); - if (reactionsCount === 0) { - myReactionsMap.set(note.renote.id, null); - } else if (reactionsCount <= note.renote.reactionAndUserPairCache.length + (bufferedReactions?.get(note.renote.id)?.pairs.length ?? 0)) { - const pairInBuffer = bufferedReactions?.get(note.renote.id)?.pairs.find(p => p[0] === meId); - if (pairInBuffer) { - myReactionsMap.set(note.renote.id, pairInBuffer[1]); - } else { - const pair = note.renote.reactionAndUserPairCache.find(p => p.startsWith(meId)); - myReactionsMap.set(note.renote.id, pair ? pair.split('/')[1] : null); - } - } else { - idsNeedFetchMyReaction.add(note.renote.id); - } - // get my reaction for OP if this is a renote of a reply. + // we may need to fetch 'my reaction' for renote target. + targetNotes.push(note.renote); if (note.renote.reply) { - const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.renote.reply.reactions, bufferedReactions?.get(note.renote.reply.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); - if (reactionsCount === 0) { - myReactionsMap.set(note.renote.reply.id, null); - } else if (reactionsCount <= note.renote.reply.reactionAndUserPairCache.length + (bufferedReactions?.get(note.renote.reply.id)?.pairs.length ?? 0)) { - const pairInBuffer = bufferedReactions?.get(note.renote.reply.id)?.pairs.find(p => p[0] === meId); - if (pairInBuffer) { - myReactionsMap.set(note.renote.reply.id, pairInBuffer[1]); - } else { - const pair = note.renote.reply.reactionAndUserPairCache.find(p => p.startsWith(meId)); - myReactionsMap.set(note.renote.reply.id, pair ? pair.split('/')[1] : null); - } - } else { - idsNeedFetchMyReaction.add(note.renote.reply.id); - } + // idem if the renote is also a reply. + targetNotes.push(note.renote.reply); } - // get my reaction for OP if this is a reply. } else if (note.reply) { - const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.reply.reactions, bufferedReactions?.get(note.reply.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); - if (reactionsCount === 0) { - myReactionsMap.set(note.reply.id, null); - } else if (reactionsCount <= note.reply.reactionAndUserPairCache.length + (bufferedReactions?.get(note.reply.id)?.pairs.length ?? 0)) { - const pairInBuffer = bufferedReactions?.get(note.reply.id)?.pairs.find(p => p[0] === meId); - if (pairInBuffer) { - myReactionsMap.set(note.reply.id, pairInBuffer[1]); - } else { - const pair = note.reply.reactionAndUserPairCache.find(p => p.startsWith(meId)); - myReactionsMap.set(note.reply.id, pair ? pair.split('/')[1] : null); - } - } else { - idsNeedFetchMyReaction.add(note.reply.id); - } + // idem for OP of a regular reply. + targetNotes.push(note.reply); } else { if (note.id < oldId) { - const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.get(note.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); - if (reactionsCount === 0) { - myReactionsMap.set(note.id, null); - } else if (reactionsCount <= note.reactionAndUserPairCache.length + (bufferedReactions?.get(note.id)?.pairs.length ?? 0)) { - const pairInBuffer = bufferedReactions?.get(note.id)?.pairs.find(p => p[0] === meId); - if (pairInBuffer) { - myReactionsMap.set(note.id, pairInBuffer[1]); - } else { - const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId)); - myReactionsMap.set(note.id, pair ? pair.split('/')[1] : null); - } - } else { - idsNeedFetchMyReaction.add(note.id); - } + targetNotes.push(note); } else { myReactionsMap.set(note.id, null); } } } + for (const note of targetNotes) { + const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.get(note.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); + if (reactionsCount === 0) { + myReactionsMap.set(note.id, null); + } else if (reactionsCount <= note.reactionAndUserPairCache.length + (bufferedReactions?.get(note.id)?.pairs.length ?? 0)) { + const pairInBuffer = bufferedReactions?.get(note.id)?.pairs.find(p => p[0] === meId); + if (pairInBuffer) { + myReactionsMap.set(note.id, pairInBuffer[1]); + } else { + const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId)); + myReactionsMap.set(note.id, pair ? pair.split('/')[1] : null); + } + } else { + idsNeedFetchMyReaction.add(note.id); + } + } + const myReactions = idsNeedFetchMyReaction.size > 0 ? await this.noteReactionsRepository.findBy({ userId: meId, noteId: In(Array.from(idsNeedFetchMyReaction)), From 5fc9c1c8cdebec868440cd8ed4b3ad84f214e117 Mon Sep 17 00:00:00 2001 From: piuvas Date: Wed, 8 Jan 2025 12:51:46 -0300 Subject: [PATCH 03/26] shallow clone --- .../backend/src/server/api/stream/channel.ts | 37 ++++++++++++++++--- .../api/stream/channels/bubble-timeline.ts | 19 ++-------- .../api/stream/channels/global-timeline.ts | 19 ++-------- .../api/stream/channels/home-timeline.ts | 19 ++-------- .../api/stream/channels/hybrid-timeline.ts | 19 ++-------- .../api/stream/channels/local-timeline.ts | 19 ++-------- 6 files changed, 47 insertions(+), 85 deletions(-) diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 93d5046902..ed6e41c4bb 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -9,8 +9,8 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { Packed } from '@/misc/json-schema.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; -import type Connection from './Connection.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; +import type Connection from './Connection.js'; /** * Stream channel @@ -103,14 +103,41 @@ export default abstract class Channel { public onMessage?(type: string, body: JsonValue): void; - public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService) { - if (this.user && Object.keys(note.reactions).length > 0) { - const myReaction = await noteEntityService.populateMyReaction(note, this.user.id); - note.myReaction = myReaction; + public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService): Promise> { + let changed = false; + const clonedNote = { ...note }; + if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { + if (note.renote && Object.keys(note.renote.reactions).length > 0) { + const myReaction = await noteEntityService.populateMyReaction(note.renote, this.user.id); + if (myReaction) { + changed = true; + clonedNote.renote = { ...note.renote }; + clonedNote.renote.myReaction = myReaction; + } + } } + if (this.user && note.renote?.reply && Object.keys(note.renote.reply.reactions).length > 0) { + const myReaction = await noteEntityService.populateMyReaction(note.renote.reply, this.user.id); + if (myReaction) { + changed = true; + clonedNote.renote = { ...note.renote }; + clonedNote.renote.reply = { ...note.renote.reply }; + clonedNote.renote.reply.myReaction = myReaction; + } + } + if (this.user && note.reply && Object.keys(note.reply.reactions).length > 0) { + const myReaction = await noteEntityService.populateMyReaction(note.reply, this.user.id); + if (myReaction) { + changed = true; + clonedNote.reply = { ...note.reply }; + clonedNote.reply.myReaction = myReaction; + } + } + return changed ? clonedNote : note; } } + export type MiChannelService = { shouldShare: boolean; requireCredential: T; diff --git a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts index b2745db92d..98ecf16a83 100644 --- a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts @@ -65,24 +65,11 @@ class BubbleTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); - await Promise.all(reactionsToFetch); + this.connection.cacheNote(clonedNote); - this.connection.cacheNote(note); - - this.send('note', note); + this.send('note', clonedNote); } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index 8df59d906d..4443b20bed 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -60,24 +60,11 @@ class GlobalTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); - await Promise.all(reactionsToFetch); + this.connection.cacheNote(clonedNote); - this.connection.cacheNote(note); - - this.send('note', note); + this.send('note', clonedNote); } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index f48eff85c9..af1b17b533 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -81,24 +81,11 @@ class HomeTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); - await Promise.all(reactionsToFetch); + this.connection.cacheNote(clonedNote); - this.connection.cacheNote(note); - - this.send('note', note); + this.send('note', clonedNote); } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index 8c58b2518e..7c604c0b58 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -98,24 +98,11 @@ class HybridTimelineChannel extends Channel { } } - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); - await Promise.all(reactionsToFetch); + this.connection.cacheNote(clonedNote); - this.connection.cacheNote(note); - - this.send('note', note); + this.send('note', clonedNote); } @bindThis diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index cb832bd3c2..2d48b6ecfb 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -70,24 +70,11 @@ class LocalTimelineChannel extends Channel { if (this.isNoteMutedOrBlocked(note)) return; - const reactionsToFetch = []; - if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { - if (note.renote) { - reactionsToFetch.push(this.assignMyReaction(note.renote, this.noteEntityService)); - if (note.renote.reply) { - reactionsToFetch.push(this.assignMyReaction(note.renote.reply, this.noteEntityService)); - } - } - } - if (this.user && note.reply) { - reactionsToFetch.push(this.assignMyReaction(note.reply, this.noteEntityService)); - } + const clonedNote = await this.assignMyReaction(note, this.noteEntityService); - await Promise.all(reactionsToFetch); + this.connection.cacheNote(clonedNote); - this.connection.cacheNote(note); - - this.send('note', note); + this.send('note', clonedNote); } @bindThis From e76e6cd08f69ddd7f8c62039b9d3e64a7a6189ca Mon Sep 17 00:00:00 2001 From: piuvas Date: Wed, 8 Jan 2025 12:58:57 -0300 Subject: [PATCH 04/26] small refactor --- .../backend/src/server/api/stream/channel.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index ed6e41c4bb..6589a7af8d 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -115,14 +115,14 @@ export default abstract class Channel { clonedNote.renote.myReaction = myReaction; } } - } - if (this.user && note.renote?.reply && Object.keys(note.renote.reply.reactions).length > 0) { - const myReaction = await noteEntityService.populateMyReaction(note.renote.reply, this.user.id); - if (myReaction) { - changed = true; - clonedNote.renote = { ...note.renote }; - clonedNote.renote.reply = { ...note.renote.reply }; - clonedNote.renote.reply.myReaction = myReaction; + if (note.renote?.reply && Object.keys(note.renote.reply.reactions).length > 0) { + const myReaction = await noteEntityService.populateMyReaction(note.renote.reply, this.user.id); + if (myReaction) { + changed = true; + clonedNote.renote = { ...note.renote }; + clonedNote.renote.reply = { ...note.renote.reply }; + clonedNote.renote.reply.myReaction = myReaction; + } } } if (this.user && note.reply && Object.keys(note.reply.reactions).length > 0) { From a3fc9a1085c7ad1fedf64fd6417c04cdcc936887 Mon Sep 17 00:00:00 2001 From: piuvas Date: Wed, 8 Jan 2025 13:10:20 -0300 Subject: [PATCH 05/26] comment code --- packages/backend/src/server/api/stream/channel.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 6589a7af8d..7a044296fe 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -105,6 +105,8 @@ export default abstract class Channel { public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService): Promise> { let changed = false; + // cloning here seems like the best solution for a race condition + // where multiple users shared the same myReaction. (Sharkey #877) const clonedNote = { ...note }; if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { if (note.renote && Object.keys(note.renote.reactions).length > 0) { From f1d9bb2cf1d9045ec13cdeb231656054b9e5bfde Mon Sep 17 00:00:00 2001 From: piuvas Date: Fri, 10 Jan 2025 22:10:18 -0300 Subject: [PATCH 06/26] requested changes --- packages/backend/src/server/api/stream/channel.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 7a044296fe..cfac4b30ce 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -9,8 +9,8 @@ import { isUserRelated } from '@/misc/is-user-related.js'; import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import type { Packed } from '@/misc/json-schema.js'; import type { JsonObject, JsonValue } from '@/misc/json-value.js'; -import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import type Connection from './Connection.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; /** * Stream channel @@ -105,7 +105,7 @@ export default abstract class Channel { public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService): Promise> { let changed = false; - // cloning here seems like the best solution for a race condition + // cloning here seems like the best solution for not sharing changes with other users. // where multiple users shared the same myReaction. (Sharkey #877) const clonedNote = { ...note }; if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { @@ -139,7 +139,6 @@ export default abstract class Channel { } } - export type MiChannelService = { shouldShare: boolean; requireCredential: T; From 3ceac893c942b0baad2f8a0c6b51baa2b8477b5e Mon Sep 17 00:00:00 2001 From: dakkar Date: Sun, 12 Jan 2025 12:33:08 +0000 Subject: [PATCH 07/26] attribute invite codes to admins/moderators when a regular user (who has the appropriate permissions) creates an invite, we record that user's id in the `createdById` column but when an admin/mod creates an invite via the control panel, we didn't now we do --- .../backend/src/server/api/endpoints/admin/invite/create.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/backend/src/server/api/endpoints/admin/invite/create.ts b/packages/backend/src/server/api/endpoints/admin/invite/create.ts index 5ecae3161a..e52b177e2b 100644 --- a/packages/backend/src/server/api/endpoints/admin/invite/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/invite/create.ts @@ -68,6 +68,8 @@ export default class extends Endpoint { // eslint- for (let i = 0; i < ps.count; i++) { ticketsPromises.push(this.registrationTicketsRepository.insertOne({ id: this.idService.gen(), + createdBy: me, + createdById: me.id, expiresAt: ps.expiresAt ? new Date(ps.expiresAt) : null, code: generateInviteCode(), })); From 3fc377839c6155d1eb3c55ae7eb24e85e4ad90c7 Mon Sep 17 00:00:00 2001 From: piuvas Date: Sun, 12 Jan 2025 21:11:36 -0300 Subject: [PATCH 08/26] comment :3 --- packages/backend/src/server/api/stream/channel.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index cfac4b30ce..047dedd5ce 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -105,8 +105,11 @@ export default abstract class Channel { public async assignMyReaction(note: Packed<'Note'>, noteEntityService: NoteEntityService): Promise> { let changed = false; - // cloning here seems like the best solution for not sharing changes with other users. - // where multiple users shared the same myReaction. (Sharkey #877) + // StreamingApiServerService creates a single EventEmitter per server process, + // so a new note arriving from redis gets de-serialised once per server process, + // and then that single object is passed to all active channels on each connection. + // If we didn't clone the notes here, different connections would asynchronously write + // different values to the same object, resulting in a random value being sent to each frontend. -- Dakkar const clonedNote = { ...note }; if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { if (note.renote && Object.keys(note.renote.reactions).length > 0) { From 7825f6e95eab864796b31e589e14cdc2a0353096 Mon Sep 17 00:00:00 2001 From: HellhoundSoftware Date: Tue, 14 Jan 2025 19:42:35 -0500 Subject: [PATCH 09/26] Open boost visibility menu on shift-click --- packages/frontend/src/components/MkNote.vue | 6 +++--- packages/frontend/src/components/MkNoteDetailed.vue | 6 +++--- packages/frontend/src/components/MkNoteSub.vue | 6 +++--- packages/frontend/src/components/SkNote.vue | 6 +++--- packages/frontend/src/components/SkNoteDetailed.vue | 6 +++--- packages/frontend/src/components/SkNoteSub.vue | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 25d04a0b6a..98ec61cefd 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -132,7 +132,7 @@ SPDX-License-Identifier: AGPL-3.0-only class="_button" :style="renoted ? 'color: var(--MI_THEME-accent) !important;' : ''" @click.stop - @mousedown.prevent="renoted ? undoRenote(appearNote) : boostVisibility()" + @mousedown.prevent="renoted ? undoRenote(appearNote) : boostVisibility($event.shiftKey)" >

{{ number(appearNote.renoteCount) }}

@@ -506,10 +506,10 @@ if (!props.mock) { } } -function boostVisibility() { +function boostVisibility(forceMenu: boolean = false) { if (renoting) return; - if (!defaultStore.state.showVisibilitySelectorOnBoost) { + if (!defaultStore.state.showVisibilitySelectorOnBoost && !forceMenu) { renote(defaultStore.state.visibilityOnBoost); } else { os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 243b95215a..a5e82782b0 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -143,7 +143,7 @@ SPDX-License-Identifier: AGPL-3.0-only class="_button" :class="$style.noteFooterButton" :style="renoted ? 'color: var(--MI_THEME-accent) !important;' : ''" - @mousedown.prevent="renoted ? undoRenote() : boostVisibility()" + @mousedown.prevent="renoted ? undoRenote() : boostVisibility($event.shiftKey)" >

{{ number(appearNote.renoteCount) }}

@@ -478,10 +478,10 @@ useTooltip(quoteButton, async (showing) => { }); }); -function boostVisibility() { +function boostVisibility(forceMenu: boolean = false) { if (renoting) return; - if (!defaultStore.state.showVisibilitySelectorOnBoost) { + if (!defaultStore.state.showVisibilitySelectorOnBoost && !forceMenu) { renote(defaultStore.state.visibilityOnBoost); } else { os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue index 3523babe46..f9e54f70aa 100644 --- a/packages/frontend/src/components/MkNoteSub.vue +++ b/packages/frontend/src/components/MkNoteSub.vue @@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only class="_button" :class="$style.noteFooterButton" :style="renoted ? 'color: var(--MI_THEME-accent) !important;' : ''" - @mousedown="renoted ? undoRenote() : boostVisibility()" + @mousedown="renoted ? undoRenote() : boostVisibility($event.shiftKey)" >

{{ note.renoteCount }}

@@ -285,8 +285,8 @@ watch(() => props.expandAllCws, (expandAllCws) => { if (expandAllCws !== showContent.value) showContent.value = expandAllCws; }); -function boostVisibility() { - if (!defaultStore.state.showVisibilitySelectorOnBoost) { +function boostVisibility(forceMenu: boolean = false) { + if (!defaultStore.state.showVisibilitySelectorOnBoost && !forceMenu) { renote(defaultStore.state.visibilityOnBoost); } else { os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue index b4a9fa34cc..c6754764b2 100644 --- a/packages/frontend/src/components/SkNote.vue +++ b/packages/frontend/src/components/SkNote.vue @@ -133,7 +133,7 @@ SPDX-License-Identifier: AGPL-3.0-only class="_button" :style="renoted ? 'color: var(--MI_THEME-accent) !important;' : ''" @click.stop - @mousedown.prevent="renoted ? undoRenote(appearNote) : boostVisibility()" + @mousedown.prevent="renoted ? undoRenote(appearNote) : boostVisibility($event.shiftKey)" >

{{ number(appearNote.renoteCount) }}

@@ -506,10 +506,10 @@ if (!props.mock) { } } -function boostVisibility() { +function boostVisibility(forceMenu: boolean = false) { if (renoting) return; - if (!defaultStore.state.showVisibilitySelectorOnBoost) { + if (!defaultStore.state.showVisibilitySelectorOnBoost && !forceMenu) { renote(defaultStore.state.visibilityOnBoost); } else { os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue index 8d7b47ae6d..5017e3d902 100644 --- a/packages/frontend/src/components/SkNoteDetailed.vue +++ b/packages/frontend/src/components/SkNoteDetailed.vue @@ -148,7 +148,7 @@ SPDX-License-Identifier: AGPL-3.0-only class="_button" :class="$style.noteFooterButton" :style="renoted ? 'color: var(--MI_THEME-accent) !important;' : ''" - @mousedown.prevent="renoted ? undoRenote() : boostVisibility()" + @mousedown.prevent="renoted ? undoRenote() : boostVisibility($event.shiftKey)" >

{{ number(appearNote.renoteCount) }}

@@ -484,10 +484,10 @@ useTooltip(quoteButton, async (showing) => { }); }); -function boostVisibility() { +function boostVisibility(forceMenu: boolean = false) { if (renoting) return; - if (!defaultStore.state.showVisibilitySelectorOnBoost) { + if (!defaultStore.state.showVisibilitySelectorOnBoost && !forceMenu) { renote(defaultStore.state.visibilityOnBoost); } else { os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue index bd25e1e3ad..9205983850 100644 --- a/packages/frontend/src/components/SkNoteSub.vue +++ b/packages/frontend/src/components/SkNoteSub.vue @@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only class="_button" :class="$style.noteFooterButton" :style="renoted ? 'color: var(--MI_THEME-accent) !important;' : ''" - @mousedown="renoted ? undoRenote() : boostVisibility()" + @mousedown="renoted ? undoRenote() : boostVisibility($event.shiftKey)" >

{{ note.renoteCount }}

@@ -299,8 +299,8 @@ watch(() => props.expandAllCws, (expandAllCws) => { if (expandAllCws !== showContent.value) showContent.value = expandAllCws; }); -function boostVisibility() { - if (!defaultStore.state.showVisibilitySelectorOnBoost) { +function boostVisibility(forceMenu: boolean = false) { + if (!defaultStore.state.showVisibilitySelectorOnBoost && !forceMenu) { renote(defaultStore.state.visibilityOnBoost); } else { os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value); From 2c90eebe869d56f6c6e948d96e4565c8c6761a45 Mon Sep 17 00:00:00 2001 From: HellhoundSoftware Date: Wed, 15 Jan 2025 14:38:34 -0500 Subject: [PATCH 10/26] Add tooltip for shift-boost --- locales/index.d.ts | 4 ++++ packages/frontend/src/components/MkNote.vue | 1 + packages/frontend/src/components/MkNoteDetailed.vue | 1 + packages/frontend/src/components/MkNoteSub.vue | 1 + packages/frontend/src/components/SkNote.vue | 1 + packages/frontend/src/components/SkNoteDetailed.vue | 1 + packages/frontend/src/components/SkNoteSub.vue | 1 + sharkey-locales/en-US.yml | 1 + 8 files changed, 11 insertions(+) diff --git a/locales/index.d.ts b/locales/index.d.ts index bcb78e4ee1..8276ead05e 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -10805,6 +10805,10 @@ export interface Locale extends ILocale { * Date */ "date": string; + /** + * Boost (hold Shift for visibility menu) + */ + "renoteShift": string; /** * Quoted. */ diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 98ec61cefd..b4193369ef 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -128,6 +128,7 @@ SPDX-License-Identifier: AGPL-3.0-only