diff --git a/src/domain/session/room/timeline/ReactionsViewModel.js b/src/domain/session/room/timeline/ReactionsViewModel.js index cdb98dcd..9385664c 100644 --- a/src/domain/session/room/timeline/ReactionsViewModel.js +++ b/src/domain/session/room/timeline/ReactionsViewModel.js @@ -86,7 +86,11 @@ class ReactionViewModel extends ViewModel { return this._annotation.count - other._annotation.count; } - react() { - return this._parentEntry.react(this.key); + toggleReaction() { + if (this.haveReacted) { + return this._parentEntry.redactReaction(this.key); + } else { + return this._parentEntry.react(this.key); + } } } \ No newline at end of file diff --git a/src/domain/session/room/timeline/tiles/BaseMessageTile.js b/src/domain/session/room/timeline/tiles/BaseMessageTile.js index f480f0db..5cfd6239 100644 --- a/src/domain/session/room/timeline/tiles/BaseMessageTile.js +++ b/src/domain/session/room/timeline/tiles/BaseMessageTile.js @@ -123,7 +123,14 @@ export class BaseMessageTile extends SimpleTile { } react(key) { - this._room.sendEvent("m.reaction", this._entry.annotate(key)); + return this._room.sendEvent("m.reaction", this._entry.annotate(key)); + } + + async redactReaction(key) { + const id = await this._entry.getOwnAnnotationId(this._room, key); + if (id) { + this._room.sendRedaction(id); + } } _updateReactions() { diff --git a/src/matrix/room/BaseRoom.js b/src/matrix/room/BaseRoom.js index 8df281fd..2513f67b 100644 --- a/src/matrix/room/BaseRoom.js +++ b/src/matrix/room/BaseRoom.js @@ -28,6 +28,7 @@ import {EventEntry} from "./timeline/entries/EventEntry.js"; import {ObservedEventMap} from "./ObservedEventMap.js"; import {DecryptionSource} from "../e2ee/common.js"; import {ensureLogItem} from "../../logging/utils.js"; +import {ANNOTATION_RELATION_TYPE, getRelation} from "./timeline/relations.js"; const EVENT_ENCRYPTED_TYPE = "m.room.encrypted"; @@ -451,6 +452,21 @@ export class BaseRoom extends EventEmitter { return observable; } + async getOwnAnnotationEventId(targetId, key) { + const txn = await this._storage.readWriteTxn([ + this._storage.storeNames.timelineEvents, + this._storage.storeNames.timelineRelations, + ]); + const relations = await txn.timelineRelations.getForTargetAndType(this.id, targetId, ANNOTATION_RELATION_TYPE); + for (const relation of relations) { + const annotation = await txn.timelineEvents.getByEventId(this.id, relation.sourceEventId); + if (annotation.event.sender === this._user.id && getRelation(annotation.event).key === key) { + return annotation.event.event_id; + } + } + return null; + } + async _readEventById(eventId) { let stores = [this._storage.storeNames.timelineEvents]; if (this.isEncrypted) { diff --git a/src/matrix/room/timeline/entries/EventEntry.js b/src/matrix/room/timeline/entries/EventEntry.js index 8f532f02..311cea8c 100644 --- a/src/matrix/room/timeline/entries/EventEntry.js +++ b/src/matrix/room/timeline/entries/EventEntry.js @@ -130,4 +130,8 @@ export class EventEntry extends BaseEventEntry { get annotations() { return this._eventEntry.annotations; } + + getOwnAnnotationId(room, key) { + return room.getOwnAnnotationEventId(this.id, key); + } } \ No newline at end of file diff --git a/src/matrix/room/timeline/entries/PendingEventEntry.js b/src/matrix/room/timeline/entries/PendingEventEntry.js index 64771ffc..77f6da93 100644 --- a/src/matrix/room/timeline/entries/PendingEventEntry.js +++ b/src/matrix/room/timeline/entries/PendingEventEntry.js @@ -85,4 +85,9 @@ export class PendingEventEntry extends BaseEventEntry { get relatedEventId() { return this._pendingEvent.relatedEventId; } + + getOwnAnnotationId(_, key) { + // TODO: implement this once local reactions are implemented + return null; + } } diff --git a/src/platform/web/ui/css/themes/element/timeline.css b/src/platform/web/ui/css/themes/element/timeline.css index 0e1e867a..1ae67d4a 100644 --- a/src/platform/web/ui/css/themes/element/timeline.css +++ b/src/platform/web/ui/css/themes/element/timeline.css @@ -217,6 +217,11 @@ only loads when the top comes into view*/ grid-area: reactions; } +.Timeline_messageReactions button.haveReacted { + background-color: green; + color: white; +} + .AnnouncementView { margin: 5px 0; padding: 5px 10%; diff --git a/src/platform/web/ui/session/room/timeline/ReactionsView.js b/src/platform/web/ui/session/room/timeline/ReactionsView.js index aa717b94..a0979186 100644 --- a/src/platform/web/ui/session/room/timeline/ReactionsView.js +++ b/src/platform/web/ui/session/room/timeline/ReactionsView.js @@ -32,12 +32,11 @@ class ReactionView extends TemplateView { render(t, vm) { const haveReacted = vm => vm.haveReacted; return t.button({ - disabled: haveReacted, className: {haveReacted}, }, [vm.key, " ", vm => `${vm.count}`]); } onClick() { - this.value.react(); + this.value.toggleReaction(); } } \ No newline at end of file