diff --git a/res/css/_components.scss b/res/css/_components.scss index 2734939ae3..3f36b6bbdf 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -59,6 +59,7 @@ @import "./views/elements/_RoleButton.scss"; @import "./views/elements/_Spinner.scss"; @import "./views/elements/_SyntaxHighlight.scss"; +@import "./views/elements/_TextForEvent.scss"; @import "./views/elements/_ToolTipButton.scss"; @import "./views/globals/_MatrixToolbar.scss"; @import "./views/groups/_GroupPublicityToggle.scss"; diff --git a/res/css/views/elements/_TextForEvent.scss b/res/css/views/elements/_TextForEvent.scss new file mode 100644 index 0000000000..8d46cbf84c --- /dev/null +++ b/res/css/views/elements/_TextForEvent.scss @@ -0,0 +1,19 @@ +/* +Copyright 2018 Michael Telatynski <7t3chguy@gmail.com> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_TextForEvent_username { + cursor: pointer; +} diff --git a/src/TextForEvent.js b/src/TextForEvent.js index 712150af4d..0cdaaac4ab 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -17,11 +17,28 @@ import MatrixClientPeg from './MatrixClientPeg'; import CallHandler from './CallHandler'; import { _t } from './languageHandler'; import * as Roles from './Roles'; +import dis from "./dispatcher"; +import React from 'react'; + +function onUsernameClick(e) { + dis.dispatch({ + action: 'insert_mention', + user_id: e.target.id, + }); +} + +function makeUsernameSpan(mxid, text) { + return { text }; +} function textForMemberEvent(ev) { // XXX: SYJS-16 "sender is sometimes null for join messages" const senderName = ev.sender ? ev.sender.name : ev.getSender(); const targetName = ev.target ? ev.target.name : ev.getStateKey(); + + const sender = makeUsernameSpan(ev.getSender(), senderName); + const target = makeUsernameSpan(ev.getStateKey(), targetName); + const prevContent = ev.getPrevContent(); const content = ev.getContent(); @@ -32,47 +49,48 @@ function textForMemberEvent(ev) { const threePidContent = content.third_party_invite; if (threePidContent) { if (threePidContent.display_name) { - return _t('%(targetName)s accepted the invitation for %(displayName)s.', { - targetName, + return _t(' accepted the invitation for %(displayName)s.', { displayName: threePidContent.display_name, + }, { + target, }); } else { - return _t('%(targetName)s accepted an invitation.', {targetName}); + return _t(' accepted an invitation.', {}, {target}); } } else { if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { - return _t('%(senderName)s requested a VoIP conference.', {senderName}); + return _t(' requested a VoIP conference.', {}, {sender}); } else { - return _t('%(senderName)s invited %(targetName)s.', {senderName, targetName}); + return _t(' invited .', {}, {sender, target}); } } } case 'ban': - return _t('%(senderName)s banned %(targetName)s.', {senderName, targetName}) + ' ' + reason; + return _t(' banned .', {}, {sender, target}) + ' ' + reason; case 'join': if (prevContent && prevContent.membership === 'join') { if (prevContent.displayname && content.displayname && prevContent.displayname !== content.displayname) { - return _t('%(oldDisplayName)s changed their display name to %(displayName)s.', { - oldDisplayName: prevContent.displayname, - displayName: content.displayname, + return _t(' changed their display name to .', {}, { + oldDisplayName: makeUsernameSpan(ev.getStateKey(), prevContent.displayname), + displayName: makeUsernameSpan(ev.getStateKey(), content.displayname), }); } else if (!prevContent.displayname && content.displayname) { - return _t('%(senderName)s set their display name to %(displayName)s.', { - senderName: ev.getSender(), - displayName: content.displayname, + return _t(' set their display name to .', {}, { + sender, + displayName: makeUsernameSpan(ev.getSender(), content.displayname), }); } else if (prevContent.displayname && !content.displayname) { - return _t('%(senderName)s removed their display name (%(oldDisplayName)s).', { - senderName, - oldDisplayName: prevContent.displayname, + return _t(' removed their display name ().', { + sender, + oldDisplayName: makeUsernameSpan(ev.getSender(), prevContent.displayname), }); } else if (prevContent.avatar_url && !content.avatar_url) { - return _t('%(senderName)s removed their profile picture.', {senderName}); + return _t(' removed their profile picture.', {}, {sender}); } else if (prevContent.avatar_url && content.avatar_url && prevContent.avatar_url !== content.avatar_url) { - return _t('%(senderName)s changed their profile picture.', {senderName}); + return _t(' changed their profile picture.', {}, {sender}); } else if (!prevContent.avatar_url && content.avatar_url) { - return _t('%(senderName)s set a profile picture.', {senderName}); + return _t(' set a profile picture.', {}, {sender}); } else { // suppress null rejoins return ''; @@ -82,7 +100,7 @@ function textForMemberEvent(ev) { if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { return _t('VoIP conference started.'); } else { - return _t('%(targetName)s joined the room.', {targetName}); + return _t(' joined the room.', {}, {target}); } } case 'leave': @@ -90,42 +108,42 @@ function textForMemberEvent(ev) { if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { return _t('VoIP conference finished.'); } else if (prevContent.membership === "invite") { - return _t('%(targetName)s rejected the invitation.', {targetName}); + return _t(' rejected the invitation.', {}, {target}); } else { - return _t('%(targetName)s left the room.', {targetName}); + return _t(' left the room.', {}, {target}); } } else if (prevContent.membership === "ban") { - return _t('%(senderName)s unbanned %(targetName)s.', {senderName, targetName}); + return _t(' unbanned .', {}, {sender, target}); } else if (prevContent.membership === "join") { - return _t('%(senderName)s kicked %(targetName)s.', {senderName, targetName}) + ' ' + reason; + return _t(' kicked .', {}, {sender, target}) + ' ' + reason; } else if (prevContent.membership === "invite") { - return _t('%(senderName)s withdrew %(targetName)s\'s invitation.', { - senderName, - targetName, - }) + ' ' + reason; + return _t(' withdrew \'s invitation.', {}, {sender, target}) + ' ' + reason; } else { - return _t('%(targetName)s left the room.', {targetName}); + return _t(' left the room.', {}, {target}); } } } function textForTopicEvent(ev) { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); - return _t('%(senderDisplayName)s changed the topic to "%(topic)s".', { - senderDisplayName, + return _t(' changed the topic to "%(topic)s".', { topic: ev.getContent().topic, + }, { + sender: makeUsernameSpan(ev.getSender(), senderDisplayName), }); } function textForRoomNameEvent(ev) { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); + const sender = makeUsernameSpan(ev.getSender(), senderDisplayName); if (!ev.getContent().name || ev.getContent().name.trim().length === 0) { - return _t('%(senderDisplayName)s removed the room name.', {senderDisplayName}); + return _t(' removed the room name.', {}, {sender}); } - return _t('%(senderDisplayName)s changed the room name to %(roomName)s.', { - senderDisplayName, + return _t(' changed the room name to %(roomName)s.', { roomName: ev.getContent().name, + }, { + sender, }); } @@ -135,7 +153,9 @@ function textForMessageEvent(ev) { if (ev.getContent().msgtype === "m.emote") { message = "* " + senderDisplayName + " " + message; } else if (ev.getContent().msgtype === "m.image") { - message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName}); + message = _t(' sent an image.', {}, { + sender: makeUsernameSpan(ev.getSender(), senderDisplayName), + }); } return message; } @@ -143,7 +163,9 @@ function textForMessageEvent(ev) { function textForCallAnswerEvent(event) { const senderName = event.sender ? event.sender.name : _t('Someone'); const supported = MatrixClientPeg.get().supportsVoip() ? '' : _t('(not supported by this browser)'); - return _t('%(senderName)s answered the call.', {senderName}) + ' ' + supported; + return _t(' answered the call.', {}, { + sender: makeUsernameSpan(event.getSender(), senderName), + }) + ' ' + supported; } function textForCallHangupEvent(event) { @@ -161,11 +183,14 @@ function textForCallHangupEvent(event) { reason = _t('(unknown failure: %(reason)s)', {reason: eventContent.reason}); } } - return _t('%(senderName)s ended the call.', {senderName}) + ' ' + reason; + return _t(' ended the call.', {}, { + sender: makeUsernameSpan(event.getSender(), senderName), + }) + ' ' + reason; } function textForCallInviteEvent(event) { const senderName = event.sender ? event.sender.name : _t('Someone'); + const sender = makeUsernameSpan(event.getSender(), senderName); // FIXME: Find a better way to determine this from the event? let callType = "voice"; if (event.getContent().offer && event.getContent().offer.sdp && @@ -173,43 +198,47 @@ function textForCallInviteEvent(event) { callType = "video"; } const supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)'); - return _t('%(senderName)s placed a %(callType)s call.', {senderName, callType}) + ' ' + supported; + return _t(' placed a %(callType)s call.', {callType}, {sender}) + ' ' + supported; } function textForThreePidInviteEvent(event) { const senderName = event.sender ? event.sender.name : event.getSender(); - return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', { - senderName, + return _t(' sent an invitation to %(targetDisplayName)s to join the room.', { targetDisplayName: event.getContent().display_name, + }, { + sender: makeUsernameSpan(event.getSender(), senderName), }); } function textForHistoryVisibilityEvent(event) { const senderName = event.sender ? event.sender.name : event.getSender(); + const sender = makeUsernameSpan(event.getSender(), senderName); switch (event.getContent().history_visibility) { case 'invited': - return _t('%(senderName)s made future room history visible to all room members, ' - + 'from the point they are invited.', {senderName}); + return _t(' made future room history visible to all room members, ' + + 'from the point they are invited.', {}, {sender}); case 'joined': - return _t('%(senderName)s made future room history visible to all room members, ' - + 'from the point they joined.', {senderName}); + return _t(' made future room history visible to all room members, ' + + 'from the point they joined.', {}, {sender}); case 'shared': - return _t('%(senderName)s made future room history visible to all room members.', {senderName}); + return _t(' made future room history visible to all room members.', {}, {sender}); case 'world_readable': - return _t('%(senderName)s made future room history visible to anyone.', {senderName}); + return _t(' made future room history visible to anyone.', {}, {sender}); default: - return _t('%(senderName)s made future room history visible to unknown (%(visibility)s).', { - senderName, + return _t(' made future room history visible to unknown (%(visibility)s).', { visibility: event.getContent().history_visibility, + }, { + sender, }); } } function textForEncryptionEvent(event) { const senderName = event.sender ? event.sender.name : event.getSender(); - return _t('%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).', { - senderName, + return _t(' turned on end-to-end encryption (algorithm %(algorithm)s).', { algorithm: event.getContent().algorithm, + }, { + sender: makeUsernameSpan(event.getSender(), senderName), }); } @@ -241,10 +270,11 @@ function textForPowerEvent(event) { const to = event.getContent().users[userId]; if (to !== from) { diff.push( - _t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', { - userId, + _t(' from %(fromPowerLevel)s to %(toPowerLevel)s', { fromPowerLevel: Roles.textualPowerLevel(from, userDefault), toPowerLevel: Roles.textualPowerLevel(to, userDefault), + }, { + user: makeUsernameSpan(userId, userId), }), ); } @@ -252,19 +282,23 @@ function textForPowerEvent(event) { if (!diff.length) { return ''; } - return _t('%(senderName)s changed the power level of %(powerLevelDiffText)s.', { - senderName, + return _t(' changed the power level of %(powerLevelDiffText)s.', { powerLevelDiffText: diff.join(", "), + }, { + sender: makeUsernameSpan(event.getSender(), senderName), }); } function textForPinnedEvent(event) { - const senderName = event.getSender(); - return _t("%(senderName)s changed the pinned messages for the room.", {senderName}); + const senderName = event.sender ? event.sender.name : event.getSender(); + const sender = makeUsernameSpan(event.getSender(), senderName); + return _t(" changed the pinned messages for the room.", {}, {sender}); } function textForWidgetEvent(event) { - const senderName = event.getSender(); + const senderName = event.sender ? event.sender.name : event.getSender(); + const sender = makeUsernameSpan(event.getSender(), senderName); + const {name: prevName, type: prevType, url: prevUrl} = event.getPrevContent(); const {name, type, url} = event.getContent() || {}; @@ -278,18 +312,12 @@ function textForWidgetEvent(event) { // equivalent to that condition. if (url) { if (prevUrl) { - return _t('%(widgetName)s widget modified by %(senderName)s', { - widgetName, senderName, - }); + return _t('%(widgetName)s widget modified by ', {widgetName}, {sender}); } else { - return _t('%(widgetName)s widget added by %(senderName)s', { - widgetName, senderName, - }); + return _t('%(widgetName)s widget added by ', {widgetName}, {sender}); } } else { - return _t('%(widgetName)s widget removed by %(senderName)s', { - widgetName, senderName, - }); + return _t('%(widgetName)s widget removed by ', {widgetName}, {sender}); } }