Show a reaction the moment it is clicked
The pill changed only when the gateway echoed the reaction back, which made a click on a reaction-role message look like it had done nothing: those bots take the reaction off again the instant they grant the role, so the add and the removal both arrive and the row ends up exactly as it started. The natural response is to click again, and again. Showing it at once separates "this did not register" from "this happened and was undone by somebody else". A failure puts the row back, so the optimistic view never outlives being wrong. Only ever moves this account's own vote: the count follows because we are one of the reactors, everyone else's stays where it is, and a tally that reaches zero disappears exactly as it does when the service reports it. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
3719717cf5
commit
f08c65cbe7
2 files changed
+48
-8
No files matched your search
+1
-1
Submodule nobilis updated: 80a182035e...8b0aae3b02.
@@ -1,19 +1,20 @@
|
||||
import type {
|
||||
Account,
|
||||
AudioDevice,
|
||||
Buffer as WireBuffer,
|
||||
NobilisEvent,
|
||||
BufferGroup,
|
||||
CustomEmoji,
|
||||
IncomingCall,
|
||||
MatrixVerification,
|
||||
Member,
|
||||
Message,
|
||||
BufferGroup,
|
||||
MatrixVerification,
|
||||
NobilisEvent,
|
||||
Reaction,
|
||||
RoomPermissions,
|
||||
SockchatSmilie,
|
||||
AudioDevice,
|
||||
VoicePrefs,
|
||||
VoiceChannel,
|
||||
VoiceSession,
|
||||
IncomingCall
|
||||
VoicePrefs,
|
||||
VoiceSession
|
||||
} from '../../../shared/wire'
|
||||
import { buildSmilieIndex, type SmilieEntry, type SmilieIndex } from '../lib/format'
|
||||
import { isImageFile, resolveMediaUrl } from '../lib/util'
|
||||
@@ -303,6 +304,26 @@ async function uploadHost(service: string | undefined, attachmentPath: string):
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This account's own reaction added to or taken off a tally.
|
||||
*
|
||||
* Only ever moves our own vote: the count follows because we are one of the
|
||||
* reactors, and everyone else's stays where it is. A tally that reaches zero
|
||||
* disappears, the same as it does when the service reports it, so an
|
||||
* optimistic row and a real one look alike.
|
||||
*/
|
||||
function applyOwnReaction(list: Reaction[], emoji: string, add: boolean): Reaction[] {
|
||||
const existing = list.find((r) => r.emoji === emoji)
|
||||
if (!existing) {
|
||||
return add ? [...list, { emoji, count: 1, me: true }] : list
|
||||
}
|
||||
// Already in the state being asked for - the service will say the same.
|
||||
if (existing.me === add) return list
|
||||
const count = Math.max(0, existing.count + (add ? 1 : -1))
|
||||
if (count === 0) return list.filter((r) => r.emoji !== emoji)
|
||||
return list.map((r) => (r.emoji === emoji ? { ...r, count, me: add } : r))
|
||||
}
|
||||
|
||||
function bestEffort(work: Promise<unknown>, what: string): void {
|
||||
void work.catch((e: Error) => console.debug(`[moho] ${what}:`, e.message))
|
||||
}
|
||||
@@ -1560,10 +1581,29 @@ export class ChatStore {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reacting, shown immediately rather than when the service agrees.
|
||||
*
|
||||
* The pill used to change only when the gateway echoed the reaction back,
|
||||
* which made a click on a reaction-role message look like it had done
|
||||
* nothing at all: those bots take the reaction off again the moment they
|
||||
* grant the role, so the add and the removal both arrive and the row ends
|
||||
* up exactly as it started. People click again, and again.
|
||||
*
|
||||
* Showing it at once separates "this did not register" from "this
|
||||
* happened and was undone by somebody else" - and a failure puts the row
|
||||
* back, so an optimistic view never outlives being wrong.
|
||||
*/
|
||||
async toggleReaction(bufferId: string, messageId: string, emoji: string, add: boolean): Promise<void> {
|
||||
const before = this.state.messagesByBuffer[bufferId]?.find((m) => m.id === messageId)?.reactions
|
||||
this.mapMessage(bufferId, messageId, (m) => ({
|
||||
...m,
|
||||
reactions: applyOwnReaction(m.reactions ?? [], emoji, add)
|
||||
}))
|
||||
try {
|
||||
await window.moho.rpc('toggleReaction', { bufferId, messageId, emoji, add })
|
||||
} catch (e) {
|
||||
this.mapMessage(bufferId, messageId, (m) => ({ ...m, reactions: before }))
|
||||
this.toast('error', (e as Error).message)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user