diff --git a/src/domain/session/room/timeline/tiles/BaseMessageTile.js b/src/domain/session/room/timeline/tiles/BaseMessageTile.js index 3c20f7f7..b73e1c26 100644 --- a/src/domain/session/room/timeline/tiles/BaseMessageTile.js +++ b/src/domain/session/room/timeline/tiles/BaseMessageTile.js @@ -119,8 +119,7 @@ export class BaseMessageTile extends SimpleTile { } get canReact() { - // TODO - return true; + return this._powerLevels.canSendType("m.reaction"); } react(key, log = null) { diff --git a/src/matrix/room/timeline/PowerLevels.js b/src/matrix/room/timeline/PowerLevels.js index 87be562f..9161c8ed 100644 --- a/src/matrix/room/timeline/PowerLevels.js +++ b/src/matrix/room/timeline/PowerLevels.js @@ -29,8 +29,16 @@ export class PowerLevels { } } + canSendType(eventType) { + return this._myLevel >= this._getEventTypeLevel(eventType); + } + get canRedact() { - return this._getUserLevel(this._ownUserId) >= this._getActionLevel("redact"); + return this._myLevel >= this._getActionLevel("redact"); + } + + get _myLevel() { + return this._getUserLevel(this._ownUserId); } _getUserLevel(userId) { @@ -59,25 +67,51 @@ export class PowerLevels { return 50; } } + + _getEventTypeLevel(eventType) { + const level = this._plEvent?.content.events?.[eventType]; + if (typeof level === "number") { + return level; + } else { + const level = this._plEvent?.content.events_default; + if (typeof level === "number") { + return level; + } else { + return 0; + } + } + } } export function tests() { const alice = "@alice:hs.tld"; const bob = "@bob:hs.tld"; + const charly = "@charly:hs.tld"; const createEvent = {content: {creator: alice}}; - const powerLevelEvent = {content: { + const redactPowerLevelEvent = {content: { redact: 50, users: { [alice]: 50 }, users_default: 0 }}; + const eventsPowerLevelEvent = {content: { + events_default: 5, + events: { + "m.room.message": 45 + }, + users: { + [alice]: 50, + [bob]: 10 + }, + users_default: 0 + }}; return { "redact somebody else event with power level event": assert => { - const pl1 = new PowerLevels({powerLevelEvent, ownUserId: alice}); + const pl1 = new PowerLevels({powerLevelEvent: redactPowerLevelEvent, ownUserId: alice}); assert.equal(pl1.canRedact, true); - const pl2 = new PowerLevels({powerLevelEvent, ownUserId: bob}); + const pl2 = new PowerLevels({powerLevelEvent: redactPowerLevelEvent, ownUserId: bob}); assert.equal(pl2.canRedact, false); }, "redact somebody else event with create event": assert => { @@ -91,5 +125,22 @@ export function tests() { assert.equal(pl.canRedactFromSender(alice), true); assert.equal(pl.canRedactFromSender(bob), false); }, + "can send event without power levels": assert => { + const pl = new PowerLevels({createEvent, ownUserId: charly}); + assert.equal(pl.canSendType("m.room.message"), true); + }, + "can't send any event below events_default": assert => { + const pl = new PowerLevels({powerLevelEvent: eventsPowerLevelEvent, ownUserId: charly}); + assert.equal(pl.canSendType("m.foo"), false); + }, + "can't send event below events[type]": assert => { + const pl = new PowerLevels({powerLevelEvent: eventsPowerLevelEvent, ownUserId: bob}); + assert.equal(pl.canSendType("m.foo"), true); + assert.equal(pl.canSendType("m.room.message"), false); + }, + "can send event below events[type]": assert => { + const pl = new PowerLevels({powerLevelEvent: eventsPowerLevelEvent, ownUserId: alice}); + assert.equal(pl.canSendType("m.room.message"), true); + }, } -} \ No newline at end of file +} diff --git a/src/platform/web/ui/session/room/timeline/BaseMessageView.js b/src/platform/web/ui/session/room/timeline/BaseMessageView.js index 9afd7609..8c98a3a2 100644 --- a/src/platform/web/ui/session/room/timeline/BaseMessageView.js +++ b/src/platform/web/ui/session/room/timeline/BaseMessageView.js @@ -110,12 +110,15 @@ export class BaseMessageView extends TemplateView { createMenuOptions(vm) { const options = []; + if (vm.canReact) { + options.push(Menu.option(vm.i18n`React with 👍`, () => vm.react("👍"))) + options.push(Menu.option(vm.i18n`React with 🙈`, () => vm.react("🙈"))) + } if (vm.canAbortSending) { options.push(Menu.option(vm.i18n`Cancel`, () => vm.abortSending())); } else if (vm.canRedact) { options.push(Menu.option(vm.i18n`Delete`, () => vm.redact()).setDestructive()); } - options.push(Menu.option(vm.i18n`React with 👍`, () => vm.react("👍"))) return options; }