From f27607a74cb8b6d2d4f7c724cdae5bfeb6346ff9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 17:58:22 +0100 Subject: [PATCH 01/43] don't put cursor position in NewlinePart after adding it You can't append to it anyway, so mark it uneditable and skip uneditable parts if that's where an edit ended up. This has the added advantage that if there is text after a newly insert pill, the cursor will be put just before it rather than in the pill, after the last character. --- src/editor/model.js | 12 +++++++++++- src/editor/parts.js | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/editor/model.js b/src/editor/model.js index 85dd425b0e..c6f0c5529f 100644 --- a/src/editor/model.js +++ b/src/editor/model.js @@ -88,7 +88,8 @@ export default class EditorModel { } this._mergeAdjacentParts(); const caretOffset = diff.at - removedOffsetDecrease + addedLen; - const newPosition = this._positionForOffset(caretOffset, true); + let newPosition = this._positionForOffset(caretOffset, true); + newPosition = newPosition.skipUneditableParts(this._parts); this._setActivePart(newPosition); this._updateCallback(newPosition); } @@ -261,4 +262,13 @@ class DocumentPosition { get offset() { return this._offset; } + + skipUneditableParts(parts) { + const part = parts[this.index]; + if (part && !part.canEdit) { + return new DocumentPosition(this.index + 1, 0); + } else { + return this; + } + } } diff --git a/src/editor/parts.js b/src/editor/parts.js index a20b857fee..8ba18997a9 100644 --- a/src/editor/parts.js +++ b/src/editor/parts.js @@ -205,6 +205,14 @@ export class NewlinePart extends BasePart { get type() { return "newline"; } + + // this makes the cursor skip this part when it is inserted + // rather than trying to append to it, which is what we want. + // As a newline can also be only one character, it makes sense + // as it can only be one character long. This caused #9741. + get canEdit() { + return false; + } } export class RoomPillPart extends PillPart { From 98e033a5292b34e81fa16cbfe86896a6894041f9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 18:13:48 +0100 Subject: [PATCH 02/43] don't allow newline parts of longer than one newline --- src/editor/parts.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor/parts.js b/src/editor/parts.js index 8ba18997a9..bf792b1ab9 100644 --- a/src/editor/parts.js +++ b/src/editor/parts.js @@ -57,7 +57,7 @@ class BasePart { appendUntilRejected(str) { for (let i = 0; i < str.length; ++i) { const chr = str.charAt(i); - if (!this.acceptsInsertion(chr)) { + if (!this.acceptsInsertion(chr, i)) { this._text = this._text + str.substr(0, i); return str.substr(i); } @@ -180,8 +180,8 @@ class PillPart extends BasePart { } export class NewlinePart extends BasePart { - acceptsInsertion(chr) { - return this.text.length === 0 && chr === "\n"; + acceptsInsertion(chr, i) { + return (this.text.length + i) === 0 && chr === "\n"; } acceptsRemoval(position, chr) { From 245f48a22c9a7d5de6f2756ad754558f2054835e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 18:39:20 +0100 Subject: [PATCH 03/43] =?UTF-8?q?set=20caret=20on=20mount=20as=20we=20usua?= =?UTF-8?q?lly=20do,=20so=20FF=20doesn't=20enter=202=20newlines=20?= =?UTF-8?q?=F0=9F=A4=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/views/elements/MessageEditor.js | 7 +------ src/editor/model.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/views/elements/MessageEditor.js b/src/components/views/elements/MessageEditor.js index b42923954b..b59227ecc8 100644 --- a/src/components/views/elements/MessageEditor.js +++ b/src/components/views/elements/MessageEditor.js @@ -144,12 +144,7 @@ export default class MessageEditor extends React.Component { componentDidMount() { this._updateEditorState(); - const sel = document.getSelection(); - const range = document.createRange(); - range.selectNodeContents(this._editorRef); - range.collapse(false); - sel.removeAllRanges(); - sel.addRange(range); + setCaretPosition(this._editorRef, this.model, this.model.getPositionAtEnd()); this._editorRef.focus(); } diff --git a/src/editor/model.js b/src/editor/model.js index c6f0c5529f..5a571640c6 100644 --- a/src/editor/model.js +++ b/src/editor/model.js @@ -61,6 +61,16 @@ export default class EditorModel { return null; } + getPositionAtEnd() { + if (this._parts.length) { + const index = this._parts.length - 1; + const part = this._parts[index]; + return new DocumentPosition(index, part.text.length); + } else { + return new DocumentPosition(0, 0); + } + } + serializeParts() { return this._parts.map(({type, text}) => {return {type, text};}); } From 690ee63bb460273c10b616b10f301fb97bc468ba Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 16 May 2019 19:14:24 +0100 Subject: [PATCH 04/43] prevent zero-length removals from deleting uneditable parts This solves an issue where, when backspacing the proceeding character next to a pill, chrome reports the caret as being in the pill node, not at the start of the proceeding text node. This would cause the pill to be removed together with proceeding character. This is a bug in any case, removing 0 characters shouldn't remove the part --- src/editor/model.js | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/editor/model.js b/src/editor/model.js index 5a571640c6..13066897b9 100644 --- a/src/editor/model.js +++ b/src/editor/model.js @@ -183,21 +183,26 @@ export default class EditorModel { // part might be undefined here let part = this._parts[index]; const amount = Math.min(len, part.text.length - offset); - if (part.canEdit) { - const replaceWith = part.remove(offset, amount); - if (typeof replaceWith === "string") { - this._replacePart(index, this._partCreator.createDefaultPart(replaceWith)); - } - part = this._parts[index]; - // remove empty part - if (!part.text.length) { - this._removePart(index); + // don't allow 0 amount deletions + if (amount) { + if (part.canEdit) { + const replaceWith = part.remove(offset, amount); + if (typeof replaceWith === "string") { + this._replacePart(index, this._partCreator.createDefaultPart(replaceWith)); + } + part = this._parts[index]; + // remove empty part + if (!part.text.length) { + this._removePart(index); + } else { + index += 1; + } } else { - index += 1; + removedOffsetDecrease += offset; + this._removePart(index); } } else { - removedOffsetDecrease += offset; - this._removePart(index); + index += 1; } len -= amount; offset = 0; From bc5227a191adf6fbc5d2f5bf7bc5086b9336ac6e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 May 2019 14:35:43 -0600 Subject: [PATCH 05/43] Fix some source strings noticed as incorrect by translators --- src/CallHandler.js | 2 +- src/GroupAddressPicker.js | 2 +- src/RoomInvite.js | 4 ++-- src/SlashCommands.js | 2 +- src/components/structures/GroupView.js | 2 +- src/i18n/strings/en_EN.json | 8 ++++---- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index acdc3e5122..e47209eebe 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -361,7 +361,7 @@ async function _startCallApp(roomId, type) { Modal.createTrackedDialog('Could not connect to the integration server', '', ErrorDialog, { title: _t('Could not connect to the integration server'), - description: _t('A conference call could not be started because the intgrations server is not available'), + description: _t('A conference call could not be started because the integrations server is not available'), }); return; } diff --git a/src/GroupAddressPicker.js b/src/GroupAddressPicker.js index 532ee23c25..cd5ecc790d 100644 --- a/src/GroupAddressPicker.js +++ b/src/GroupAddressPicker.js @@ -38,7 +38,7 @@ export function showGroupInviteDialog(groupId) { Modal.createTrackedDialog('Group Invite', '', AddressPickerDialog, { title: _t("Invite new community members"), description: description, - placeholder: _t("Name or matrix ID"), + placeholder: _t("Name or Matrix ID"), button: _t("Invite to Community"), validAddressTypes: ['mx-user-id'], onFinished: (success, addrs) => { diff --git a/src/RoomInvite.js b/src/RoomInvite.js index b808b935a6..34b9635780 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -45,7 +45,7 @@ export function showStartChatInviteDialog() { Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, { title: _t('Start a chat'), description: _t("Who would you like to communicate with?"), - placeholder: _t("Email, name or matrix ID"), + placeholder: _t("Email, name or Matrix ID"), validAddressTypes: ['mx-user-id', 'email'], button: _t("Start Chat"), onFinished: _onStartChatFinished, @@ -58,7 +58,7 @@ export function showRoomInviteDialog(roomId) { title: _t('Invite new room members'), description: _t('Who would you like to add to this room?'), button: _t('Send Invites'), - placeholder: _t("Email, name or matrix ID"), + placeholder: _t("Email, name or Matrix ID"), onFinished: (shouldInvite, addrs) => { _onRoomInviteFinished(roomId, shouldInvite, addrs); }, diff --git a/src/SlashCommands.js b/src/SlashCommands.js index 55107db899..f25bc9af07 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -518,7 +518,7 @@ export const CommandMap = { unban: new Command({ name: 'unban', args: '', - description: _td('Unbans user with given id'), + description: _td('Unbans user with given ID'), runFn: function(roomId, args) { if (args) { const matches = args.match(/^(\S+)$/); diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index dcbe212267..cdfbe26fea 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -265,7 +265,7 @@ const RoleUserList = React.createClass({ Modal.createTrackedDialog('Add Users to Group Summary', '', AddressPickerDialog, { title: _t('Add users to the community summary'), description: _t("Who would you like to add to this summary?"), - placeholder: _t("Name or matrix ID"), + placeholder: _t("Name or Matrix ID"), button: _t("Add to summary"), validAddressTypes: ['mx-user-id'], groupId: this.props.groupId, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 24e448da09..121845567b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -35,7 +35,7 @@ "You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.", "You cannot place a call with yourself.": "You cannot place a call with yourself.", "Could not connect to the integration server": "Could not connect to the integration server", - "A conference call could not be started because the intgrations server is not available": "A conference call could not be started because the intgrations server is not available", + "A conference call could not be started because the integrations server is not available": "A conference call could not be started because the integrations server is not available", "Call in Progress": "Call in Progress", "A call is currently being placed!": "A call is currently being placed!", "A call is already in progress!": "A call is already in progress!", @@ -80,7 +80,7 @@ "Who would you like to add to this community?": "Who would you like to add to this community?", "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID", "Invite new community members": "Invite new community members", - "Name or matrix ID": "Name or matrix ID", + "Name or Matrix ID": "Name or Matrix ID", "Invite to Community": "Invite to Community", "Which rooms would you like to add to this community?": "Which rooms would you like to add to this community?", "Show these rooms to non-members on the community page and room list?": "Show these rooms to non-members on the community page and room list?", @@ -109,7 +109,7 @@ "Admin": "Admin", "Start a chat": "Start a chat", "Who would you like to communicate with?": "Who would you like to communicate with?", - "Email, name or matrix ID": "Email, name or matrix ID", + "Email, name or Matrix ID": "Email, name or Matrix ID", "Start Chat": "Start Chat", "Invite new room members": "Invite new room members", "Who would you like to add to this room?": "Who would you like to add to this room?", @@ -157,7 +157,7 @@ "Unrecognised room alias:": "Unrecognised room alias:", "Kicks user with given id": "Kicks user with given id", "Bans user with given id": "Bans user with given id", - "Unbans user with given id": "Unbans user with given id", + "Unbans user with given ID": "Unbans user with given ID", "Ignores a user, hiding their messages from you": "Ignores a user, hiding their messages from you", "Ignored user": "Ignored user", "You are now ignoring %(userId)s": "You are now ignoring %(userId)s", From d7c686918866cc0262b3bda251a8768b61315ec8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 16 May 2019 21:55:17 +0100 Subject: [PATCH 06/43] Apply Flex voodoo Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/dialogs/_DevtoolsDialog.scss | 9 +++++++-- src/autocomplete/UserProvider.js | 2 +- src/components/views/dialogs/DevtoolsDialog.js | 6 ++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/res/css/views/dialogs/_DevtoolsDialog.scss b/res/css/views/dialogs/_DevtoolsDialog.scss index 2f01f3ecc6..1f5d36b57a 100644 --- a/res/css/views/dialogs/_DevtoolsDialog.scss +++ b/res/css/views/dialogs/_DevtoolsDialog.scss @@ -82,8 +82,13 @@ limitations under the License. display: inline-block; } -.mx_DevTools_content .mx_Field_input + .mx_Field_input { - margin-left: 42px; +.mx_DevTools_eventTypeStateKeyGroup { + display: flex; + flex-wrap: wrap; +} + +.mx_DevTools_content .mx_Field_input:first-of-type { + margin-right: 42px; } .mx_DevTools_tgl { diff --git a/src/autocomplete/UserProvider.js b/src/autocomplete/UserProvider.js index d4a5ec5e74..e1dd8ea620 100644 --- a/src/autocomplete/UserProvider.js +++ b/src/autocomplete/UserProvider.js @@ -119,7 +119,7 @@ export default class UserProvider extends AutocompleteProvider { component: ( } - title={displayName} + title={displayName}mx_DevTools_content description={user.userId} /> ), range, diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js index 0835c41bb9..9327e1e54e 100644 --- a/src/components/views/dialogs/DevtoolsDialog.js +++ b/src/components/views/dialogs/DevtoolsDialog.js @@ -128,8 +128,10 @@ class SendCustomEvent extends GenericEditor { return
- { this.textInput('eventType', _t('Event Type')) } - { this.state.isStateEvent && this.textInput('stateKey', _t('State Key')) } +
+ { this.textInput('eventType', _t('Event Type')) } + { this.state.isStateEvent && this.textInput('stateKey', _t('State Key')) } +

From 82bd893f032052d09a142ffd865c966428dc9c15 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 May 2019 16:36:23 -0600 Subject: [PATCH 07/43] Mute screen readers over reactions --- src/components/views/messages/ReactionDimension.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/messages/ReactionDimension.js b/src/components/views/messages/ReactionDimension.js index 843254ade3..de33ad1a57 100644 --- a/src/components/views/messages/ReactionDimension.js +++ b/src/components/views/messages/ReactionDimension.js @@ -168,6 +168,7 @@ export default class ReactionDimension extends React.PureComponent { return {items} ; From b2aad4afb10eb6d65ba6ee1023dff29f3951e0d8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 17 May 2019 00:28:45 +0100 Subject: [PATCH 08/43] remove accidental paste Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/autocomplete/UserProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autocomplete/UserProvider.js b/src/autocomplete/UserProvider.js index e1dd8ea620..d4a5ec5e74 100644 --- a/src/autocomplete/UserProvider.js +++ b/src/autocomplete/UserProvider.js @@ -119,7 +119,7 @@ export default class UserProvider extends AutocompleteProvider { component: ( } - title={displayName}mx_DevTools_content + title={displayName} description={user.userId} /> ), range, From ca2e6d8eb2475f4cb4159c501a3afaaefd75b55e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 10:22:02 +0100 Subject: [PATCH 09/43] Message editing: shift+enter for newline, enter to send --- .../views/elements/MessageEditor.js | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/components/views/elements/MessageEditor.js b/src/components/views/elements/MessageEditor.js index b59227ecc8..c18d1f56fd 100644 --- a/src/components/views/elements/MessageEditor.js +++ b/src/components/views/elements/MessageEditor.js @@ -77,35 +77,44 @@ export default class MessageEditor extends React.Component { } _onKeyDown = (event) => { + // insert newline on Shift+Enter + if (event.shiftKey && event.key === "Enter") { + event.preventDefault(); // just in case the browser does support this + document.execCommand("insertHTML", undefined, "\n"); + return; + } + // autocomplete or enter to send below shouldn't have any modifier keys pressed. if (event.metaKey || event.altKey || event.shiftKey) { return; } - if (!this.model.autoComplete) { - return; + if (this.model.autoComplete) { + const autoComplete = this.model.autoComplete; + switch (event.key) { + case "Enter": + autoComplete.onEnter(event); break; + case "ArrowUp": + autoComplete.onUpArrow(event); break; + case "ArrowDown": + autoComplete.onDownArrow(event); break; + case "Tab": + autoComplete.onTab(event); break; + case "Escape": + autoComplete.onEscape(event); break; + default: + return; // don't preventDefault on anything else + } + event.preventDefault(); + } else if (event.key === "Enter") { + this._sendEdit(); + event.preventDefault(); } - const autoComplete = this.model.autoComplete; - switch (event.key) { - case "Enter": - autoComplete.onEnter(event); break; - case "ArrowUp": - autoComplete.onUpArrow(event); break; - case "ArrowDown": - autoComplete.onDownArrow(event); break; - case "Tab": - autoComplete.onTab(event); break; - case "Escape": - autoComplete.onEscape(event); break; - default: - return; // don't preventDefault on anything else - } - event.preventDefault(); } _onCancelClicked = () => { dis.dispatch({action: "edit_event", event: null}); } - _onSaveClicked = () => { + _sendEdit = () => { const newContent = { "msgtype": "m.text", "body": textSerialize(this.model), @@ -177,7 +186,7 @@ export default class MessageEditor extends React.Component { >
{_t("Cancel")} - {_t("Save")} + {_t("Save")}
; } From c0cfa8ad00cd7f92d2a61db0de8839f4accbe56d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 10:26:19 +0100 Subject: [PATCH 10/43] only allow editing of text messages --- src/utils/EventUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/EventUtils.js b/src/utils/EventUtils.js index ac415ca6de..81aa82057b 100644 --- a/src/utils/EventUtils.js +++ b/src/utils/EventUtils.js @@ -47,5 +47,6 @@ export function isContentActionable(mxEvent) { export function canEditContent(mxEvent) { return isContentActionable(mxEvent) && + mxEvent.getContent().msgtype === "m.text" && mxEvent.getSender() === MatrixClientPeg.get().getUserId(); } From e731ce32b8c642dd59b642618c058e1453c5452e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 16 May 2019 20:31:29 +0000 Subject: [PATCH 11/43] Translated using Weblate (English (United States)) Currently translated at 44.1% (718 of 1628 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/en_US/ --- src/i18n/strings/en_US.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index 1c6f782db4..8829b1a421 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -887,5 +887,8 @@ "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s changed their display name to %(displayName)s.", "Spanner": "Wrench", "Aeroplane": "Airplane", - "Cat": "Cat" + "Cat": "Cat", + "Sends the given message coloured as a rainbow": "Sends the given message colored as a rainbow", + "Sends the given emote coloured as a rainbow": "Sends the given emote colored as a rainbow", + "Unrecognised address": "Unrecognized address" } From 2beb854d92afd4a4b0828788ed4d10333770d46f Mon Sep 17 00:00:00 2001 From: Tuomas Hietala Date: Thu, 16 May 2019 09:53:33 +0000 Subject: [PATCH 12/43] Translated using Weblate (Finnish) Currently translated at 98.9% (1610 of 1628 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fi/ --- src/i18n/strings/fi.json | 89 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/src/i18n/strings/fi.json b/src/i18n/strings/fi.json index 76448ddcb2..515ff416c1 100644 --- a/src/i18n/strings/fi.json +++ b/src/i18n/strings/fi.json @@ -426,7 +426,7 @@ "Start new chat": "Aloita uusi keskustelu", "Failed to invite": "Kutsu epäonnistui", "Failed to invite user": "Käyttäjän kutsuminen epäonnistui", - "Failed to invite the following users to the %(roomName)s room:": "Seuraavian käyttäjien kutsuminen huoneeseen %(roomName)s epäonnistui:", + "Failed to invite the following users to the %(roomName)s room:": "Seuraavien käyttäjien kutsuminen huoneeseen %(roomName)s epäonnistui:", "Confirm Removal": "Varmista poistaminen", "Unknown error": "Tuntematon virhe", "Incorrect password": "Virheellinen salasana", @@ -469,7 +469,7 @@ "Riot does not have permission to send you notifications - please check your browser settings": "Riotilla ei ole oikeuksia lähettää sinulle ilmoituksia. Ole hyvä ja tarkista selaimen asetukset", "Riot was not given permission to send notifications - please try again": "Riot ei saannut lupaa lähettää ilmoituksia. Ole hyvä ja yritä uudelleen", "Room %(roomId)s not visible": "Huone %(roomId)s ei ole näkyvissä", - "%(roomName)s does not exist.": "%(roomName)s ei ole olemassa.", + "%(roomName)s does not exist.": "Huonetta %(roomName)s ei ole olemassa.", "%(roomName)s is not accessible at this time.": "%(roomName)s ei ole saatavilla tällä hetkellä.", "Seen by %(userName)s at %(dateTime)s": "Käyttäjän %(userName)s näkemä %(dateTime)s", "Send Reset Email": "Lähetä salasanan palautusviesti", @@ -943,7 +943,7 @@ "Send Custom Event": "Lähetä mukautettu tapahtuma", "Advanced notification settings": "Lisäasetukset ilmoituksille", "delete the alias.": "poista alias.", - "To return to your account in future you need to set a password": "Voidaksesi tulevaisuudessa palata tilillesi sinun pitää asettaa salasana", + "To return to your account in future you need to set a password": "Jotta voit jatkossa palata tilillesi, sinun pitää asettaa salasana", "Forget": "Unohda", "#example": "#esimerkki", "Hide panel": "Piilota paneeli", @@ -1346,11 +1346,11 @@ "If you have previously used a more recent version of Riot, your session may be incompatible with this version. Close this window and return to the more recent version.": "Jos olet aikaisemmin käyttänyt uudempaa versiota Riotista, istuntosi voi olla epäyhteensopiva tämän version kanssa. Sulje tämä ikkuna ja yritä uudemman version kanssa.", "The platform you're on": "Alusta, jolla olet", "Whether or not you're logged in (we don't record your username)": "Riippumatta siitä oletko kirjautunut sisään (emme tallenna käyttäjätunnustasi)", - "Whether or not you're using the Richtext mode of the Rich Text Editor": "Riippumatta siitä, että käytätkö muotoillun tekstin tilaa muotoilueditorissa", + "Whether or not you're using the Richtext mode of the Rich Text Editor": "Riippumatta siitä, käytätkö muotoillun tekstin tilaa muotoilueditorissa", "Your User Agent": "Selaintunnisteesi", "The information being sent to us to help make Riot.im better includes:": "Tietoihin, jota lähetetään Riot.im:ään palvelun parantamiseksi, sisältyy:", "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Niissä kohdissa, missä tämä sivu sisältää yksilöivää tietoa, kuten huoneen, käyttäjän tai ryhmän ID:n, kyseinen tieto poistetaan ennen tiedon lähetystä palvelimelle.", - "A conference call could not be started because the intgrations server is not available": "Konferenssipuhelua ei pystytty aloittamaan, koska integraatiopalvelin ei ole käytettävissä", + "A conference call could not be started because the intgrations server is not available": "Konferenssipuhelua ei voitu aloittaa, koska integraatiopalvelin ei ole käytettävissä", "A call is currently being placed!": "Puhelua ollaan aloittamassa!", "A call is already in progress!": "Puhelu on jo meneillään!", "Permission Required": "Lisäoikeuksia tarvitaan", @@ -1760,5 +1760,82 @@ "Recovery Method Removed": "Palautustapa poistettu", "This device has detected that your recovery passphrase and key for Secure Messages have been removed.": "Tämä laite on huomannut, että palautuksen salalauseesi ja avaimesi salatuille viesteille on poistettu.", "If you did this accidentally, you can setup Secure Messages on this device which will re-encrypt this device's message history with a new recovery method.": "Jos teit tämän vahingossa, voit ottaa käyttöön salatut viestit tälle laitteelle, joka uudelleensalaa tämän laitteen keskusteluhistorian uudella palautustavalla.", - "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Jos et poistanut palautustapaa, hyökkääjä saattaa yrittää käyttää tiliäsi. Vaihda tilisi salasana ja aseta uusi palautustapa asetuksissa välittömästi." + "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Jos et poistanut palautustapaa, hyökkääjä saattaa yrittää käyttää tiliäsi. Vaihda tilisi salasana ja aseta uusi palautustapa asetuksissa välittömästi.", + "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Riippumatta siitä, käytätkö 'leivänmuruja' (kuvia huonelistan yläpuolella)", + "Replying With Files": "Tiedostoilla vastaaminen", + "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "Tiedostolla vastaaminen ei onnistu tällä kertaa. Haluatko ladata tiedoston vastaamatta?", + "The file '%(fileName)s' failed to upload.": "Tiedoston '%(fileName)s' lataaminen ei onnistunut.", + "The server does not support the room version specified.": "Palvelin ei tue määritettyä huoneversiota.", + "Please confirm that you'd like to go forward with upgrading this room from to .": "Vahvista, että haluat päivittää huoneen versiosta versioon .", + "Changes your avatar in this current room only": "Vaihtaa kuvasi vain nykyisessä huoneessa", + "Sends the given message coloured as a rainbow": "Lähettää viestin sateenkaaren väreissä", + "Sends the given emote coloured as a rainbow": "Lähettää emoten sateenkaaren väreissä", + "The user's homeserver does not support the version of the room.": "Käyttäjän kotipalvelin ei tue huoneen versiota.", + "Show recent room avatars above the room list": "Näytä viimeaikaiset huoneen kuvat huoneluettelon yläpuolella", + "Edit messages after they have been sent (refresh to apply changes)": "Muokkaa viestejä niiden lähettämisen jälkeen (päivitä saattaaksesi muutokset voimaan)", + "React to messages with emoji (refresh to apply changes)": "Reagoi viesteihin emojeilla (päivitä saattaaksesi muutokset voimaan)", + "This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "Tämä laite ei varmuuskopioi avaimiasi, mutta sinulla on olemassa varmuuskopio palauttamista ja lisäämistä varten.", + "Backup has an invalid signature from this device": "Varmuuskopiossa on epäkelpo allekirjoitus tältä laitteelta", + "this room": "tämä huone", + "View older messages in %(roomName)s.": "Näytä vanhemmat viestit huoneessa %(roomName)s.", + "Joining room …": "Liitytään huoneeseen …", + "Loading …": "Latataan …", + "Join the conversation with an account": "Liity keskusteluun tilin avulla", + "Sign Up": "Rekisteröidy", + "Sign In": "Kirjaudu", + "Reason: %(reason)s": "Syy: %(reason)s", + "Forget this room": "Unohda tämä huone", + "Re-join": "Liity uudelleen", + "You were banned from %(roomName)s by %(memberName)s": "%(memberName)s antoi sinulle porttikiellon huoneeseen %(roomName)s", + "Something went wrong with your invite to %(roomName)s": "Jotain meni vikaan kutsussasi huoneeseen %(roomName)s", + "%(errcode)s was returned while trying to valide your invite. You could try to pass this information on to a room admin.": "Kutsusi validointi palautti virhekoodin %(errcode)s. Voit koettaa välittää tiedon huoneen ylläpitäjälle.", + "You can only join it with a working invite.": "Voit liittyä siihen vain toimivalla kutsulla.", + "You can still join it because this is a public room.": "Voit silti liittyä siihen, koska huone on julkinen.", + "Join the discussion": "Liity keskusteluun", + "Try to join anyway": "Yritä silti liittyä", + "This invite to %(roomName)s wasn't sent to your account": "Kutsua huoneeseen %(roomName)s ei lähetetty tilillesi", + "Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.": "Kirjaudu eri tilillä, pyydä uutta kutsua tai lisää sähköpostiosoite %(email)s tähän tiliin.", + "Do you want to chat with %(user)s?": "Haluatko keskustella käyttäjän %(user)s kanssa?", + "Do you want to join %(roomName)s?": "Haluatko liittyä huoneeseen %(roomName)s?", + " invited you": " kutsui sinut", + "You're previewing %(roomName)s. Want to join it?": "Esikatselet huonetta %(roomName)s. Haluatko liittyä siihen?", + "%(roomName)s can't be previewed. Do you want to join it?": "Huonetta %(roomName)s ei voi esikatsella. Haluatko liittyä siihen?", + "This room doesn't exist. Are you sure you're at the right place?": "Tätä huonetta ei ole olemassa. Oletko varma, että olet oikeassa paikassa?", + "This room has already been upgraded.": "Tämä huone on jo päivitetty.", + "Rotate Left": "Kierrä vasempaan", + "Rotate counter-clockwise": "Kierrä vastapäivään", + "Rotate Right": "Kierrä oikeaan", + "Rotate clockwise": "Kierrä myötäpäivään", + "View Servers in Room": "Näytä huoneessa olevat palvelimet", + "Sign out and remove encryption keys?": "Kirjaudu ulos ja poista salausavaimet?", + "Missing session data": "Istunnon dataa puuttuu", + "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Istunnon dataa, mukaanlukien salausavaimia, puuttuu. Kirjaudu ulos ja sisään, jolloin avaimet palautetaan varmuuskopiosta.", + "Your browser likely removed this data when running low on disk space.": "Selaimesi luultavasti poisti tämän datan, kun levytila oli vähissä.", + "Upload files (%(current)s of %(total)s)": "Lataa tiedostot (%(current)s / %(total)s)", + "Upload files": "Lataa tiedostot", + "These files are too large to upload. The file size limit is %(limit)s.": "Tiedostot ovat liian isoja ladattaviksi. Tiedoston kokoraja on %(limit)s.", + "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Osa tiedostoista on liian isoja ladattaviksi. Tiedoston kokoraja on %(limit)s.", + "Upload %(count)s other files|other": "Lataa %(count)s muuta tiedostoa", + "Upload %(count)s other files|one": "Lataa %(count)s muu tiedosto", + "Cancel All": "Peruuta kaikki", + "Upload Error": "Latausvirhe", + "Use an email address to recover your account": "Palauta tilisi sähköpostiosoitteen avulla", + "Enter email address (required on this homeserver)": "Syötä sähköpostiosoite (vaaditaan tällä kotipalvelimella)", + "Doesn't look like a valid email address": "Ei näytä kelvolliselta sähköpostiosoitteelta", + "Enter password": "Syötä salasana", + "Password is allowed, but unsafe": "Salasana on sallittu, mutta turvaton", + "Nice, strong password!": "Hyvä, vahva salasana!", + "Passwords don't match": "Salasanat eivät täsmää", + "Other users can invite you to rooms using your contact details": "Muut voivat kutsua sinut huoneisiin yhteystietojesi avulla", + "Enter phone number (required on this homeserver)": "Syötä puhelinnumero (vaaditaan tällä kotipalvelimella)", + "Doesn't look like a valid phone number": "Ei näytä kelvolliselta puhelinnumerolta", + "Use letters, numbers, dashes and underscores only": "Käytä vain kirjaimia, numeroita, viivoja ja alaviivoja", + "Enter username": "Syötä käyttäjänimi", + "Some characters not allowed": "Osaa merkeistä ei sallita", + "Use an email address to recover your account.": "Palauta tilisi sähköpostiosoitteen avulla.", + "Other users can invite you to rooms using your contact details.": "Muut käyttäjät voivat kutsua sinut huoneisiin yhteystietojesi avulla.", + "Error loading Riot": "Virhe Riotin lataamisessa", + "If this is unexpected, please contact your system administrator or technical support representative.": "Jos et odottanut tätä, ota yhteyttä järjestelmänvalvojaan tai tekniseen tukeen.", + "Homeserver URL does not appear to be a valid Matrix homeserver": "Kotipalvelimen osoite ei näytä olevan kelvollinen Matrix-kotipalvelin", + "Identity server URL does not appear to be a valid identity server": "Identiteettipalvelimen osoite ei näytä olevan kelvollinen identiteettipalvelin" } From 34bd70f738d3a9f8d94bce06cfa6b358b1709835 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Thu, 16 May 2019 15:58:15 +0000 Subject: [PATCH 13/43] Translated using Weblate (Hungarian) Currently translated at 100.0% (1628 of 1628 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 72 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index d0a4560dc0..c758badebb 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -1892,5 +1892,75 @@ "Upload %(count)s other files|other": "Feltölt %(count)s másik fájlt", "Upload %(count)s other files|one": "Feltölt %(count)s másik fájlt", "Cancel All": "Mindent megszakít", - "Upload Error": "Feltöltési hiba" + "Upload Error": "Feltöltési hiba", + "The server does not support the room version specified.": "A szerver nem támogatja a megadott szoba verziót.", + "Please confirm that you'd like to go forward with upgrading this room from to .": "Kérlek erősítsd meg, hogy a szobát frissíted a verzióról verzióra.", + "Changes your avatar in this current room only": "A profilképedet csak ebben a szobában változtatja meg", + "Sends the given message coloured as a rainbow": "A megadott üzenetet szivárvány színben küldi el", + "Sends the given emote coloured as a rainbow": "A megadott hangulatjelet szivárvány színben küldi el", + "The user's homeserver does not support the version of the room.": "A felhasználó matrix szervere nem támogatja a megadott szoba verziót.", + "Edit messages after they have been sent (refresh to apply changes)": "Üzenet szerkesztése küldés után (újratöltés szükséges)", + "React to messages with emoji (refresh to apply changes)": "Reagálj az üzenetre emoji-val (újratöltés szükséges)", + "When rooms are upgraded": "Ha a szobák frissültek", + "This device is not backing up your keys, but you do have an existing backup you can restore from and add to going forward.": "Ez az eszköz nem menti el a kulcsaidat, de létezik mentés amit visszaállíthatsz és folytathatod.", + "Connect this device to key backup before signing out to avoid losing any keys that may only be on this device.": "Csatlakozz ezzel az eszközzel a kulcs mentéshez kilépés előtt, hogy ne veszíts el kulcsot ami esetleg csak ezen az eszközön van meg.", + "Connect this device to Key Backup": "Csatlakozz ezzel az eszközzel a Kulcs Mentéshez", + "Backup has an invalid signature from this device": "A mentés érvénytelen aláírással rendelkezik erről az eszközről", + "this room": "ez a szoba", + "View older messages in %(roomName)s.": "Régebbi üzenetek megjelenítése itt: %(roomName)s.", + "Joining room …": "Szobához csatlakozás …", + "Loading …": "Betöltés …", + "Rejecting invite …": "Meghívó elutasítása …", + "Join the conversation with an account": "Beszélgetéshez csatlakozás felhasználói fiókkal", + "Sign Up": "Fiók készítés", + "Sign In": "Bejelentkezés", + "You were kicked from %(roomName)s by %(memberName)s": "Téged kirúgott %(memberName)s ebből a szobából: %(roomName)s", + "Reason: %(reason)s": "Ok: %(reason)s", + "Forget this room": "Szoba elfelejtése", + "Re-join": "Újra-csatlakozás", + "You were banned from %(roomName)s by %(memberName)s": "Téged kitiltott %(memberName)s ebből a szobából: %(roomName)s", + "Something went wrong with your invite to %(roomName)s": "A meghívóddal ebbe a szobába: %(roomName)s valami baj történt", + "%(errcode)s was returned while trying to valide your invite. You could try to pass this information on to a room admin.": "A meghívód ellenőrzése során az alábbi hibakódot kaptuk: %(errcode)s. Megpróbálhatod ezt az információt átadni a szoba adminisztrátorának.", + "You can only join it with a working invite.": "Csak érvényes meghívóval tudsz csatlakozni.", + "You can still join it because this is a public room.": "Mivel a szoba nyilvános megpróbálhatsz csatlakozni.", + "Join the discussion": "Beszélgetéshez csatlakozás", + "Try to join anyway": "Mindennek ellenére próbálj csatlakozni", + "This invite to %(roomName)s wasn't sent to your account": "Ezt a meghívót ide: %(roomName)s nem a te fiókodnak küldték", + "Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.": "Jelentkezz be más fiókkal, kérj másik meghívót vagy add hozzá a fiókodhoz ezt az e-mail címet: %(email)s.", + "Do you want to chat with %(user)s?": "%(user)s felhasználóval szeretnél beszélgetni?", + "Do you want to join %(roomName)s?": "%(roomName)s szobába szeretnél belépni?", + " invited you": " meghívott", + "You're previewing %(roomName)s. Want to join it?": "%(roomName)s szoba előnézetét látod. Belépsz?", + "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s szobának nincs előnézete. Be szeretnél lépni?", + "This room doesn't exist. Are you sure you're at the right place?": "Ez a szoba nem létezik. Biztos, hogy jó helyen vagy?", + "Try again later, or ask a room admin to check if you have access.": "Próbálkozz később vagy kérd meg a szoba adminisztrátorát, hogy nézze meg van-e hozzáférésed.", + "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "Amikor a szobát próbáltuk elérni ezt a hibaüzenetet kaptuk: %(errcode)s. Ha úgy gondolod, hogy ez egy hiba légy szívesnyiss egy hibajegyet.", + "This room has already been upgraded.": "Ez a szoba már frissült.", + "Agree or Disagree": "Egyetért vagy Ellentmond", + "Like or Dislike": "Kedveli vagy Nem kedveli", + "Rotate Left": "Balra forgat", + "Rotate Right": "Jobbra forgat", + "View Servers in Room": "Szerverek megjelenítése a szobában", + "Use an email address to recover your account": "A felhasználói fiók visszaszerzése e-mail címmel", + "Enter email address (required on this homeserver)": "E-mail cím megadása (ezen a matrix szerveren kötelező)", + "Doesn't look like a valid email address": "Az e-mail cím nem tűnik érvényesnek", + "Enter password": "Jelszó megadása", + "Password is allowed, but unsafe": "A jelszó engedélyezett, de nem biztonságos", + "Nice, strong password!": "Szép, erős jelszó!", + "Passwords don't match": "A jelszavak nem egyeznek meg", + "Other users can invite you to rooms using your contact details": "Mások meghívhatnak a szobákba a kapcsolatoknál megadott adataiddal", + "Enter phone number (required on this homeserver)": "Telefonszám megadása (ennél a matrix szervernél kötelező)", + "Doesn't look like a valid phone number": "Ez a telefonszám nem tűnik érvényesnek", + "Use letters, numbers, dashes and underscores only": "Csak betűket, számokat, kötőjelet és aláhúzást használj", + "Enter username": "Felhasználói név megadása", + "Some characters not allowed": "Néhány karakter nem engedélyezett", + "Use an email address to recover your account.": "A felhasználói fiókod visszaszerzéséhez használd az e-mail címet.", + "Other users can invite you to rooms using your contact details.": "Mások meghívhatnak a szobákba a kapcsolatoknál megadott adataid alapján.", + "Error loading Riot": "A Riot betöltésénél hiba", + "If this is unexpected, please contact your system administrator or technical support representative.": "Ha ez váratlanul ért, kérlek vedd fel a kapcsolatot a rendszer adminisztrátorával vagy a technikai segítséggel.", + "Failed to get autodiscovery configuration from server": "A szerverről nem sikerült beszerezni az automatikus felderítés beállításait", + "Invalid base_url for m.homeserver": "Hibás base_url az m.homeserver -hez", + "Homeserver URL does not appear to be a valid Matrix homeserver": "A matrix URL nem tűnik érvényesnek", + "Invalid base_url for m.identity_server": "Érvénytelen base_url az m.identity_server -hez", + "Identity server URL does not appear to be a valid identity server": "Az Azonosító szerver URL nem tűnik érvényesnek" } From 317898048ec4b1e14292c1d61046697b5a1a6359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=A0?= Date: Thu, 16 May 2019 15:45:52 +0000 Subject: [PATCH 14/43] Translated using Weblate (Slovenian) Currently translated at 0.7% (11 of 1628 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/sl/ --- src/i18n/strings/sl.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sl.json b/src/i18n/strings/sl.json index 0967ef424b..39297c8ae9 100644 --- a/src/i18n/strings/sl.json +++ b/src/i18n/strings/sl.json @@ -1 +1,13 @@ -{} +{ + "This email address is already in use": "Ta e-poštni naslov je že v uporabi", + "This phone number is already in use": "Ta telefonska številka je že v uporabi", + "Failed to verify email address: make sure you clicked the link in the email": "E-poštnega naslova ni bilo mogoče preveriti: preverite, ali ste kliknili povezavo v e-poštnem sporočilu", + "The platform you're on": "Vaša platforma", + "The version of Riot.im": "Različica Riot.im", + "Dismiss": "Opusti", + "Chat with Riot Bot": "Klepetajte z Riot Botom", + "Sign In": "Prijava", + "powered by Matrix": "poganja Matrix", + "Custom Server Options": "Možnosti strežnika po meri", + "You can also set a custom identity server, but you won't be able to invite users by email address, or be invited by email address yourself.": "Nastavite lahko tudi strežnik za identiteto po meri, vendar ne boste mogli povabiti uporabnikov prek e-pošte, prav tako pa vas ne bodo mogli povabiti drugi." +} From 4a6725d4c2e12c6694d8f6bd186b1bdc132057b7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 11:36:36 +0100 Subject: [PATCH 15/43] Message editing: show (edited) marker on edited messages, with tooltip --- res/css/views/rooms/_EventTile.scss | 8 ++++++ src/components/views/messages/TextualBody.js | 30 ++++++++++++++++++++ src/i18n/strings/en_EN.json | 2 ++ 3 files changed, 40 insertions(+) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index f4c12bb734..8f67069c82 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -377,6 +377,14 @@ limitations under the License. left: 41px; } +.mx_EventTile_content .mx_EventTile_edited { + user-select: none; + font-size: 12px; + color: $roomtopic-color; + display: inline-block; + margin-left: 9px; +} + /* Various markdown overrides */ .mx_EventTile_content .markdown-body { diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 34769c060f..0ca4711b07 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -22,6 +22,7 @@ import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import highlight from 'highlight.js'; import * as HtmlUtils from '../../../HtmlUtils'; +import {formatDate} from '../../../DateUtils'; import sdk from '../../../index'; import ScalarAuthClient from '../../../ScalarAuthClient'; import Modal from '../../../Modal'; @@ -148,6 +149,7 @@ module.exports = React.createClass({ nextProps.highlightLink !== this.props.highlightLink || nextProps.showUrlPreview !== this.props.showUrlPreview || nextState.links !== this.state.links || + nextState.editedMarkerHovered !== this.state.editedMarkerHovered || nextState.widgetHidden !== this.state.widgetHidden); }, @@ -432,6 +434,31 @@ module.exports = React.createClass({ }); }, + _onMouseEnterEditedMarker: function() { + this.setState({editedMarkerHovered: true}); + }, + + _onMouseLeaveEditedMarker: function() { + this.setState({editedMarkerHovered: false}); + }, + + _renderEditedMarker: function() { + let editedTooltip; + if (this.state.editedMarkerHovered) { + const Tooltip = sdk.getComponent('elements.Tooltip'); + const editEvent = this.props.mxEvent.replacingEvent(); + const date = editEvent && formatDate(editEvent.getDate()); + editedTooltip = ; + } + return ( +
{editedTooltip}{`(${_t("Edited")})`}
+ ); + }, + render: function() { const EmojiText = sdk.getComponent('elements.EmojiText'); const mxEvent = this.props.mxEvent; @@ -443,6 +470,9 @@ module.exports = React.createClass({ // Part of Replies fallback support stripReplyFallback: stripReply, }); + if (this.props.replacingEventId) { + body = [body, this._renderEditedMarker()]; + } if (this.props.highlightLink) { body = { body }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 121845567b..f535549232 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -916,6 +916,8 @@ "Failed to copy": "Failed to copy", "Add an Integration": "Add an Integration", "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?", + "Edited at %(date)s.": "Edited at %(date)s.", + "Edited": "Edited", "Removed or unknown message type": "Removed or unknown message type", "Message removed by %(userId)s": "Message removed by %(userId)s", "Message removed": "Message removed", From 32c68feae2c5c66af26415e9dd7611b29e12fad5 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 17 May 2019 11:53:44 +0100 Subject: [PATCH 16/43] Run translation substitution in 2 passes By first substituting variables and then tags after, the translation handling can now support strings with variables inside tags, such as: "people reacted with %(foo)s" --- src/languageHandler.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/languageHandler.js b/src/languageHandler.js index 854ac079bc..bdef829933 100644 --- a/src/languageHandler.js +++ b/src/languageHandler.js @@ -125,20 +125,25 @@ export function _t(text, variables, tags) { * @return a React component if any non-strings were used in substitutions, otherwise a string */ export function substitute(text, variables, tags) { - const regexpMapping = {}; + let result = text; if (variables !== undefined) { + const regexpMapping = {}; for (const variable in variables) { regexpMapping[`%\\(${variable}\\)s`] = variables[variable]; } + result = replaceByRegexes(result, regexpMapping); } if (tags !== undefined) { + const regexpMapping = {}; for (const tag in tags) { regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag]; } + result = replaceByRegexes(result, regexpMapping); } - return replaceByRegexes(text, regexpMapping); + + return result; } /* From 3da1f73ea40b6b76cdbc449778a793b3272f87f6 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 17 May 2019 11:52:03 +0100 Subject: [PATCH 17/43] Add a basic tooltip showing who reacted This adds a first attempt at tooltip showing who reacted to a message. It doesn't limit senders or position the tooltip nicely, but the info is there at least. Part of https://github.com/vector-im/riot-web/issues/9722 --- res/css/_components.scss | 1 + .../messages/_ReactionsRowButtonTooltip.scss | 34 ++++++++ res/themes/dark/css/_dark.scss | 2 + res/themes/light/css/_light.scss | 2 + src/HtmlUtils.js | 11 +++ src/components/views/messages/ReactionsRow.js | 2 +- .../views/messages/ReactionsRowButton.js | 50 +++++++++++- .../messages/ReactionsRowButtonTooltip.js | 80 +++++++++++++++++++ src/i18n/strings/en_EN.json | 1 + 9 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 res/css/views/messages/_ReactionsRowButtonTooltip.scss create mode 100644 src/components/views/messages/ReactionsRowButtonTooltip.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 2e0c91bd8c..9823b4ac3d 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -119,6 +119,7 @@ @import "./views/messages/_ReactionDimension.scss"; @import "./views/messages/_ReactionsRow.scss"; @import "./views/messages/_ReactionsRowButton.scss"; +@import "./views/messages/_ReactionsRowButtonTooltip.scss"; @import "./views/messages/_RoomAvatarEvent.scss"; @import "./views/messages/_SenderProfile.scss"; @import "./views/messages/_TextualEvent.scss"; diff --git a/res/css/views/messages/_ReactionsRowButtonTooltip.scss b/res/css/views/messages/_ReactionsRowButtonTooltip.scss new file mode 100644 index 0000000000..086271e556 --- /dev/null +++ b/res/css/views/messages/_ReactionsRowButtonTooltip.scss @@ -0,0 +1,34 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +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_ReactionsRowButtonTooltip { + box-shadow: none; + background-color: $reaction-row-button-tooltip-bg-color; + color: $reaction-row-button-tooltip-fg-color; + text-align: center; + font-size: 8px; + padding: 6px; + border: none; + border-radius: 3px; + + .mx_Tooltip_chevron::after { + border-right-color: $reaction-row-button-tooltip-bg-color; + } + + .mx_ReactionsRowButtonTooltip_reactedWith { + opacity: 0.7; + } +} diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 592b1a1887..251f038f75 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -156,6 +156,8 @@ $reaction-row-button-border-color: #616b7f; $reaction-row-button-hover-border-color: $header-panel-text-primary-color; $reaction-row-button-selected-bg-color: #1f6954; $reaction-row-button-selected-border-color: $accent-color; +$reaction-row-button-tooltip-bg-color: $tagpanel-bg-color; +$reaction-row-button-tooltip-fg-color: #ffffff; // ***** Mixins! ***** diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index fc15170b87..375b4a44b3 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -264,6 +264,8 @@ $reaction-row-button-border-color: #e9edf1; $reaction-row-button-hover-border-color: $focus-bg-color; $reaction-row-button-selected-bg-color: #e9fff9; $reaction-row-button-selected-border-color: $accent-color; +$reaction-row-button-tooltip-bg-color: $tagpanel-bg-color; +$reaction-row-button-tooltip-fg-color: #ffffff; // ***** Mixins! ***** diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index d9d8bac93b..1032c52e32 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -107,6 +107,17 @@ function unicodeToImage(str, addAlt) { return str; } +/** + * Returns the shortcode for an emoji character. + * + * @param {String} char The emoji character + * @return {String} The shortcode (such as :thumbup:) + */ +export function unicodeToShort(char) { + const unicode = emojione.jsEscapeMap[char]; + return emojione.mapUnicodeToShort()[unicode]; +} + /** * Given one or more unicode characters (represented by unicode * character number), return an image node with the corresponding diff --git a/src/components/views/messages/ReactionsRow.js b/src/components/views/messages/ReactionsRow.js index d55ecd6578..d3bf6a2035 100644 --- a/src/components/views/messages/ReactionsRow.js +++ b/src/components/views/messages/ReactionsRow.js @@ -116,8 +116,8 @@ export default class ReactionsRow extends React.PureComponent { return ; }); diff --git a/src/components/views/messages/ReactionsRowButton.js b/src/components/views/messages/ReactionsRowButton.js index 721147cdb8..19cae27b87 100644 --- a/src/components/views/messages/ReactionsRowButton.js +++ b/src/components/views/messages/ReactionsRowButton.js @@ -19,17 +19,28 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; import MatrixClientPeg from '../../../MatrixClientPeg'; +import sdk from '../../../index'; export default class ReactionsRowButton extends React.PureComponent { static propTypes = { // The event we're displaying reactions for mxEvent: PropTypes.object.isRequired, + // The reaction content / key / emoji content: PropTypes.string.isRequired, - count: PropTypes.number.isRequired, + // A Set of Martix reaction events for this key + reactionEvents: PropTypes.object.isRequired, // A possible Matrix event if the current user has voted for this type myReactionEvent: PropTypes.object, } + constructor(props) { + super(props); + + this.state = { + tooltipVisible: false, + }; + } + onClick = (ev) => { const { mxEvent, myReactionEvent, content } = this.props; if (myReactionEvent) { @@ -48,18 +59,53 @@ export default class ReactionsRowButton extends React.PureComponent { } }; + onMouseOver = () => { + this.setState({ + // To avoid littering the DOM with a tooltip for every reaction, + // only render it on first use. + tooltipRendered: true, + tooltipVisible: true, + }); + } + + onMouseOut = () => { + this.setState({ + tooltipVisible: false, + }); + } + render() { - const { content, count, myReactionEvent } = this.props; + const ReactionsRowButtonTooltip = + sdk.getComponent('messages.ReactionsRowButtonTooltip'); + const { content, reactionEvents, myReactionEvent } = this.props; + + const count = reactionEvents.size; + if (!count) { + return null; + } const classes = classNames({ mx_ReactionsRowButton: true, mx_ReactionsRowButton_selected: !!myReactionEvent, }); + let tooltip; + if (this.state.tooltipRendered) { + tooltip = ; + } + return {content} {count} + {tooltip} ; } } diff --git a/src/components/views/messages/ReactionsRowButtonTooltip.js b/src/components/views/messages/ReactionsRowButtonTooltip.js new file mode 100644 index 0000000000..fc4aed0410 --- /dev/null +++ b/src/components/views/messages/ReactionsRowButtonTooltip.js @@ -0,0 +1,80 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; + +import MatrixClientPeg from '../../../MatrixClientPeg'; +import sdk from '../../../index'; +import { unicodeToShort } from '../../../HtmlUtils'; +import { _t } from '../../../languageHandler'; + +export default class ReactionsRowButtonTooltip extends React.PureComponent { + static propTypes = { + // The event we're displaying reactions for + mxEvent: PropTypes.object.isRequired, + // The reaction content / key / emoji + content: PropTypes.string.isRequired, + // A Set of Martix reaction events for this key + reactionEvents: PropTypes.object.isRequired, + visible: PropTypes.bool.isRequired, + } + + render() { + const Tooltip = sdk.getComponent('elements.Tooltip'); + const { content, reactionEvents, mxEvent, visible } = this.props; + + const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId()); + let tooltipLabel; + if (room) { + const senders = []; + for (const reactionEvent of reactionEvents) { + const { name } = room.getMember(reactionEvent.getSender()); + senders.push(name); + } + const shortName = unicodeToShort(content) || content; + tooltipLabel =
{_t( + "reacted with %(shortName)s", + { + shortName, + }, + { + reactors: () => { + return
+ {senders.join(", ")} +
; + }, + reactedWith: (sub) => { + return
+ {sub} +
; + }, + }, + )}
; + } + + let tooltip; + if (tooltipLabel) { + tooltip = ; + } + + return tooltip; + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f535549232..3a740ea515 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -907,6 +907,7 @@ "Invalid file%(extra)s": "Invalid file%(extra)s", "Error decrypting image": "Error decrypting image", "Error decrypting video": "Error decrypting video", + "reacted with %(shortName)s": "reacted with %(shortName)s", "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s changed the avatar for %(roomName)s", "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s removed the room avatar.", "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s changed the room avatar to ", From 059988ff5c1968fd0780f0d000c671d13f285910 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 17 May 2019 12:09:47 +0100 Subject: [PATCH 18/43] Extract tooltip styling to a shared class We want to use the same styling with edited tooltip as well, so this extracts the shared bits. --- res/css/_common.scss | 1 - res/css/views/elements/_Tooltip.scss | 13 +++++++++++++ .../views/messages/_ReactionsRowButtonTooltip.scss | 10 ---------- res/themes/dark/css/_dark.scss | 5 +++-- res/themes/light/css/_light.scss | 5 +++-- .../views/messages/ReactionsRowButtonTooltip.js | 2 +- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index d3cf1921e0..d46f38bddb 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -557,4 +557,3 @@ textarea { .mx_Username_color8 { color: $username-variant8-color; } - diff --git a/res/css/views/elements/_Tooltip.scss b/res/css/views/elements/_Tooltip.scss index 43ddf6dde5..66e8b5943f 100644 --- a/res/css/views/elements/_Tooltip.scss +++ b/res/css/views/elements/_Tooltip.scss @@ -74,3 +74,16 @@ limitations under the License. animation: mx_fadeout 0.1s forwards; } } + +.mx_Tooltip_timeline { + box-shadow: none; + background-color: $tooltip-timeline-bg-color; + color: $tooltip-timeline-fg-color; + text-align: center; + border: none; + border-radius: 3px; + + .mx_Tooltip_chevron::after { + border-right-color: $tooltip-timeline-bg-color; + } +} diff --git a/res/css/views/messages/_ReactionsRowButtonTooltip.scss b/res/css/views/messages/_ReactionsRowButtonTooltip.scss index 086271e556..95e339144f 100644 --- a/res/css/views/messages/_ReactionsRowButtonTooltip.scss +++ b/res/css/views/messages/_ReactionsRowButtonTooltip.scss @@ -15,18 +15,8 @@ limitations under the License. */ .mx_ReactionsRowButtonTooltip { - box-shadow: none; - background-color: $reaction-row-button-tooltip-bg-color; - color: $reaction-row-button-tooltip-fg-color; - text-align: center; font-size: 8px; padding: 6px; - border: none; - border-radius: 3px; - - .mx_Tooltip_chevron::after { - border-right-color: $reaction-row-button-tooltip-bg-color; - } .mx_ReactionsRowButtonTooltip_reactedWith { opacity: 0.7; diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 251f038f75..bdccf71540 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -156,8 +156,9 @@ $reaction-row-button-border-color: #616b7f; $reaction-row-button-hover-border-color: $header-panel-text-primary-color; $reaction-row-button-selected-bg-color: #1f6954; $reaction-row-button-selected-border-color: $accent-color; -$reaction-row-button-tooltip-bg-color: $tagpanel-bg-color; -$reaction-row-button-tooltip-fg-color: #ffffff; + +$tooltip-timeline-bg-color: $tagpanel-bg-color; +$tooltip-timeline-fg-color: #ffffff; // ***** Mixins! ***** diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 375b4a44b3..d11dfebda3 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -264,8 +264,9 @@ $reaction-row-button-border-color: #e9edf1; $reaction-row-button-hover-border-color: $focus-bg-color; $reaction-row-button-selected-bg-color: #e9fff9; $reaction-row-button-selected-border-color: $accent-color; -$reaction-row-button-tooltip-bg-color: $tagpanel-bg-color; -$reaction-row-button-tooltip-fg-color: #ffffff; + +$tooltip-timeline-bg-color: $tagpanel-bg-color; +$tooltip-timeline-fg-color: #ffffff; // ***** Mixins! ***** diff --git a/src/components/views/messages/ReactionsRowButtonTooltip.js b/src/components/views/messages/ReactionsRowButtonTooltip.js index fc4aed0410..709c20c113 100644 --- a/src/components/views/messages/ReactionsRowButtonTooltip.js +++ b/src/components/views/messages/ReactionsRowButtonTooltip.js @@ -69,7 +69,7 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent { let tooltip; if (tooltipLabel) { tooltip = ; From 603e6b7055a8cbfa12665c403bcf156085731426 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 17 May 2019 12:19:02 +0100 Subject: [PATCH 19/43] Adjust edited tooltip to use shared styles --- res/css/views/rooms/_EventTile.scss | 5 +++++ src/components/views/messages/TextualBody.js | 7 +++++-- src/i18n/strings/en_EN.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 8f67069c82..aa473ec317 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -490,6 +490,11 @@ limitations under the License. } */ +.mx_EventTile_editedTooltip { + font-size: 10px; + padding: 5px 6px; +} + /* end of overrides */ .mx_MatrixChat_useCompactLayout { diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 0ca4711b07..44c807e4e4 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -448,14 +448,17 @@ module.exports = React.createClass({ const Tooltip = sdk.getComponent('elements.Tooltip'); const editEvent = this.props.mxEvent.replacingEvent(); const date = editEvent && formatDate(editEvent.getDate()); - editedTooltip = ; + editedTooltip = ; } return (
{editedTooltip}{`(${_t("Edited")})`}
+ >{editedTooltip}{`(${_t("edited")})`} ); }, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3a740ea515..9d54a65eda 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -918,7 +918,7 @@ "Add an Integration": "Add an Integration", "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?", "Edited at %(date)s.": "Edited at %(date)s.", - "Edited": "Edited", + "edited": "edited", "Removed or unknown message type": "Removed or unknown message type", "Message removed by %(userId)s": "Message removed by %(userId)s", "Message removed": "Message removed", From f285040e0bf3fe3c14e5cdf3c5596f9f30e2feba Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 13:26:06 +0100 Subject: [PATCH 20/43] check msgtype of original event --- src/utils/EventUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/EventUtils.js b/src/utils/EventUtils.js index 81aa82057b..68f6d4b14e 100644 --- a/src/utils/EventUtils.js +++ b/src/utils/EventUtils.js @@ -47,6 +47,6 @@ export function isContentActionable(mxEvent) { export function canEditContent(mxEvent) { return isContentActionable(mxEvent) && - mxEvent.getContent().msgtype === "m.text" && + mxEvent.getOriginalContent().msgtype === "m.text" && mxEvent.getSender() === MatrixClientPeg.get().getUserId(); } From 76ceee0e6ca582875f560772dbf6e3eb736918e2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 13:31:26 +0100 Subject: [PATCH 21/43] silence react warning when showing edited marker, by also giving the body a key --- src/HtmlUtils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index 1032c52e32..97f547ceb4 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -541,8 +541,8 @@ export function bodyToHtml(content, highlights, opts={}) { }); return isDisplayedWithHtml ? - : - { strippedBody }; + : + { strippedBody }; } export function emojifyText(text, addAlt) { From 43c9e6d942609d70bb5a6c22178b320022fcfda8 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 17 May 2019 15:04:27 +0100 Subject: [PATCH 22/43] Reactions / editing tooltip tweaks --- res/css/views/elements/_Tooltip.scss | 3 +++ res/css/views/messages/_ReactionsRowButtonTooltip.scss | 9 ++------- res/css/views/rooms/_EventTile.scss | 5 ----- .../views/messages/ReactionsRowButtonTooltip.js | 2 +- src/components/views/messages/TextualBody.js | 4 ++-- src/i18n/strings/en_EN.json | 2 +- 6 files changed, 9 insertions(+), 16 deletions(-) diff --git a/res/css/views/elements/_Tooltip.scss b/res/css/views/elements/_Tooltip.scss index 66e8b5943f..3a6b6fb936 100644 --- a/res/css/views/elements/_Tooltip.scss +++ b/res/css/views/elements/_Tooltip.scss @@ -82,6 +82,9 @@ limitations under the License. text-align: center; border: none; border-radius: 3px; + font-size: 14px; + line-height: 1.2; + padding: 6px 8px; .mx_Tooltip_chevron::after { border-right-color: $tooltip-timeline-bg-color; diff --git a/res/css/views/messages/_ReactionsRowButtonTooltip.scss b/res/css/views/messages/_ReactionsRowButtonTooltip.scss index 95e339144f..cf4219fcec 100644 --- a/res/css/views/messages/_ReactionsRowButtonTooltip.scss +++ b/res/css/views/messages/_ReactionsRowButtonTooltip.scss @@ -14,11 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_ReactionsRowButtonTooltip { - font-size: 8px; - padding: 6px; - - .mx_ReactionsRowButtonTooltip_reactedWith { - opacity: 0.7; - } +.mx_ReactionsRowButtonTooltip_reactedWith { + opacity: 0.7; } diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index aa473ec317..8f67069c82 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -490,11 +490,6 @@ limitations under the License. } */ -.mx_EventTile_editedTooltip { - font-size: 10px; - padding: 5px 6px; -} - /* end of overrides */ .mx_MatrixChat_useCompactLayout { diff --git a/src/components/views/messages/ReactionsRowButtonTooltip.js b/src/components/views/messages/ReactionsRowButtonTooltip.js index 709c20c113..4f26cea708 100644 --- a/src/components/views/messages/ReactionsRowButtonTooltip.js +++ b/src/components/views/messages/ReactionsRowButtonTooltip.js @@ -69,7 +69,7 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent { let tooltip; if (tooltipLabel) { tooltip = ; diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 44c807e4e4..ea7f634691 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -449,8 +449,8 @@ module.exports = React.createClass({ const editEvent = this.props.mxEvent.replacingEvent(); const date = editEvent && formatDate(editEvent.getDate()); editedTooltip = ; } return ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9d54a65eda..86131645cf 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -917,7 +917,7 @@ "Failed to copy": "Failed to copy", "Add an Integration": "Add an Integration", "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?", - "Edited at %(date)s.": "Edited at %(date)s.", + "Edited at %(date)s": "Edited at %(date)s", "edited": "edited", "Removed or unknown message type": "Removed or unknown message type", "Message removed by %(userId)s": "Message removed by %(userId)s", From 9a3752c5713e2b05bfa9f9ac68b7b8f78e52971f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 15:30:07 +0100 Subject: [PATCH 23/43] show message editor in textual body instead of replacing event tile --- src/components/structures/MessagePanel.js | 7 ++----- src/components/views/messages/MessageEvent.js | 1 + src/components/views/messages/TextualBody.js | 5 +++++ src/components/views/rooms/EventTile.js | 1 + 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index dbaab57adf..001097f846 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -450,14 +450,10 @@ module.exports = React.createClass({ _getTilesForEvent: function(prevEvent, mxEv, last) { const EventTile = sdk.getComponent('rooms.EventTile'); - const MessageEditor = sdk.getComponent('elements.MessageEditor'); const DateSeparator = sdk.getComponent('messages.DateSeparator'); const ret = []; - if (this.props.editEvent && this.props.editEvent.getId() === mxEv.getId()) { - return []; - } - + const isEditing = this.props.editEvent && this.props.editEvent.getId() === mxEv.getId(); // is this a continuation of the previous message? let continuation = false; @@ -527,6 +523,7 @@ module.exports = React.createClass({ continuation={continuation} isRedacted={mxEv.isRedacted()} replacingEventId={mxEv.replacingEventId()} + isEditing={isEditing} onHeightChanged={this._onHeightChanged} readReceipts={readReceipts} readReceiptMap={this._readReceiptMap} diff --git a/src/components/views/messages/MessageEvent.js b/src/components/views/messages/MessageEvent.js index 357da1cd10..8c90ec5a46 100644 --- a/src/components/views/messages/MessageEvent.js +++ b/src/components/views/messages/MessageEvent.js @@ -90,6 +90,7 @@ module.exports = React.createClass({ tileShape={this.props.tileShape} maxImageHeight={this.props.maxImageHeight} replacingEventId={this.props.replacingEventId} + isEditing={this.props.isEditing} onHeightChanged={this.props.onHeightChanged} />; }, }); diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 44c807e4e4..c4a60c3bab 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -148,6 +148,7 @@ module.exports = React.createClass({ nextProps.replacingEventId !== this.props.replacingEventId || nextProps.highlightLink !== this.props.highlightLink || nextProps.showUrlPreview !== this.props.showUrlPreview || + nextProps.isEditing !== this.props.isEditing || nextState.links !== this.state.links || nextState.editedMarkerHovered !== this.state.editedMarkerHovered || nextState.widgetHidden !== this.state.widgetHidden); @@ -463,6 +464,10 @@ module.exports = React.createClass({ }, render: function() { + if (this.props.isEditing) { + const MessageEditor = sdk.getComponent('elements.MessageEditor'); + return ; + } const EmojiText = sdk.getComponent('elements.EmojiText'); const mxEvent = this.props.mxEvent; const content = mxEvent.getContent(); diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index f38e3c3946..9977d10395 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -780,6 +780,7 @@ module.exports = withMatrixClient(React.createClass({ Date: Fri, 17 May 2019 15:31:09 +0100 Subject: [PATCH 24/43] update design of editor to look as close to original tile (and design) the buttons below the composer are overlayed onto the previous event. In case of the last event, for now we make them not overflow, but make the tile grow. The design says it should overlay on the main composer for the last event tile, postponing that for a bit though as not sure what is the best way to do that. --- res/css/views/elements/_MessageEditor.scss | 19 +++++++++++++++---- res/css/views/rooms/_EventTile.scss | 4 ++++ src/components/views/rooms/EventTile.js | 1 + 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/res/css/views/elements/_MessageEditor.scss b/res/css/views/elements/_MessageEditor.scss index ec6d903753..15b342b436 100644 --- a/res/css/views/elements/_MessageEditor.scss +++ b/res/css/views/elements/_MessageEditor.scss @@ -16,14 +16,14 @@ limitations under the License. .mx_MessageEditor { border-radius: 4px; - background-color: $header-panel-bg-color; - padding: 11px 13px 7px 56px; + padding: 3px; + margin: -7px -7px -5px -7px; // no idea why this is working .mx_MessageEditor_editor { border-radius: 4px; border: solid 1px #e9edf1; background-color: #ffffff; - padding: 10px; + padding: 3px; white-space: pre-wrap; word-wrap: break-word; outline: none; @@ -49,7 +49,14 @@ limitations under the License. display: flex; flex-direction: row; justify-content: end; - padding: 5px 0; + padding: 5px; + position: absolute; + left: 0; + background: $header-panel-bg-color; + z-index: 100; + right: 0; + margin: 0 -110px 0 0; + padding-right: 108px; .mx_AccessibleButton { margin-left: 5px; @@ -62,3 +69,7 @@ limitations under the License. height: 0; } } + +.mx_EventTile_last .mx_MessageEditor_buttons { + position: static; +} diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index aa473ec317..93d97b2913 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -43,6 +43,10 @@ limitations under the License. padding-top: 0px ! important; } +.mx_EventTile_isEditing { + background-color: $header-panel-bg-color; +} + .mx_EventTile .mx_SenderProfile { color: $primary-fg-color; font-size: 14px; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 9977d10395..a4a9004041 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -540,6 +540,7 @@ module.exports = withMatrixClient(React.createClass({ const classes = classNames({ mx_EventTile: true, + mx_EventTile_isEditing: this.props.isEditing, mx_EventTile_info: isInfoMessage, mx_EventTile_12hr: this.props.isTwelveHour, mx_EventTile_encrypting: this.props.eventSendStatus === 'encrypting', From 62b8973e720531dba9fea1b714e739479280ee04 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 15:35:14 +0100 Subject: [PATCH 25/43] cancel the edit when pressing escape --- src/components/views/elements/MessageEditor.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/MessageEditor.js b/src/components/views/elements/MessageEditor.js index c18d1f56fd..0c249d067b 100644 --- a/src/components/views/elements/MessageEditor.js +++ b/src/components/views/elements/MessageEditor.js @@ -107,10 +107,12 @@ export default class MessageEditor extends React.Component { } else if (event.key === "Enter") { this._sendEdit(); event.preventDefault(); + } else if (event.key === "Escape") { + this._cancelEdit(); } } - _onCancelClicked = () => { + _cancelEdit = () => { dis.dispatch({action: "edit_event", event: null}); } @@ -185,7 +187,7 @@ export default class MessageEditor extends React.Component { ref={ref => this._editorRef = ref} >
- {_t("Cancel")} + {_t("Cancel")} {_t("Save")}
; From 578a183f49a9f3fe239f19bfc6a5b50d6e4426ca Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 15:35:28 +0100 Subject: [PATCH 26/43] hide the action bar while editing --- src/components/views/rooms/EventTile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index a4a9004041..39e830a228 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -618,14 +618,14 @@ module.exports = withMatrixClient(React.createClass({ } const MessageActionBar = sdk.getComponent('messages.MessageActionBar'); - const actionBar = ; + /> : undefined; const timestamp = this.props.mxEvent.getTs() ? : null; From 2544decab2af9b3d40be58828c0b1a032fef802b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 16:00:08 +0100 Subject: [PATCH 27/43] fix chrome not right aligning buttons --- res/css/views/elements/_MessageEditor.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/elements/_MessageEditor.scss b/res/css/views/elements/_MessageEditor.scss index 15b342b436..cfa62e000f 100644 --- a/res/css/views/elements/_MessageEditor.scss +++ b/res/css/views/elements/_MessageEditor.scss @@ -48,7 +48,7 @@ limitations under the License. .mx_MessageEditor_buttons { display: flex; flex-direction: row; - justify-content: end; + justify-content: flex-end; padding: 5px; position: absolute; left: 0; From 81245e9c05bb2dd0c011ab68442ec1c9195199a6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 16:00:22 +0100 Subject: [PATCH 28/43] cull editor height to 200px --- res/css/views/elements/_MessageEditor.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/css/views/elements/_MessageEditor.scss b/res/css/views/elements/_MessageEditor.scss index cfa62e000f..e48e93aa63 100644 --- a/res/css/views/elements/_MessageEditor.scss +++ b/res/css/views/elements/_MessageEditor.scss @@ -27,6 +27,8 @@ limitations under the License. white-space: pre-wrap; word-wrap: break-word; outline: none; + max-height: 200px; + overflow-x: auto; span { display: inline-block; From cf8189ed4371650293e0822db2950b2c686e08a6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 16:00:39 +0100 Subject: [PATCH 29/43] align buttons perfectly with editor edge --- res/css/views/elements/_MessageEditor.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/elements/_MessageEditor.scss b/res/css/views/elements/_MessageEditor.scss index e48e93aa63..d537584f1c 100644 --- a/res/css/views/elements/_MessageEditor.scss +++ b/res/css/views/elements/_MessageEditor.scss @@ -58,7 +58,7 @@ limitations under the License. z-index: 100; right: 0; margin: 0 -110px 0 0; - padding-right: 108px; + padding-right: 107px; .mx_AccessibleButton { margin-left: 5px; From aeea4ee83a4ef4d196327bfb456bc980421b499a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 16:00:58 +0100 Subject: [PATCH 30/43] a bit more horizontal padding for the editor --- res/css/views/elements/_MessageEditor.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/elements/_MessageEditor.scss b/res/css/views/elements/_MessageEditor.scss index d537584f1c..6a5f25532f 100644 --- a/res/css/views/elements/_MessageEditor.scss +++ b/res/css/views/elements/_MessageEditor.scss @@ -17,13 +17,13 @@ limitations under the License. .mx_MessageEditor { border-radius: 4px; padding: 3px; - margin: -7px -7px -5px -7px; // no idea why this is working + margin: -7px -10px -5px -10px; // from fiddling around in inspector .mx_MessageEditor_editor { border-radius: 4px; border: solid 1px #e9edf1; background-color: #ffffff; - padding: 3px; + padding: 3px 6px; white-space: pre-wrap; word-wrap: break-word; outline: none; From 5adae63555f31496494aeab08e054fe4edebca39 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 16:01:30 +0100 Subject: [PATCH 31/43] don't apply formatting to body when showing editor in TextualBody it throws --- src/components/views/messages/TextualBody.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index c4a60c3bab..bd37f98360 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -89,7 +89,9 @@ module.exports = React.createClass({ componentDidMount: function() { this._unmounted = false; - this._applyFormatting(); + if (!this.props.isEditing) { + this._applyFormatting(); + } }, _applyFormatting() { @@ -128,11 +130,13 @@ module.exports = React.createClass({ }, componentDidUpdate: function(prevProps) { - const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId; - if (messageWasEdited) { - this._applyFormatting(); + if (!this.props.isEditing) { + const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId; + if (messageWasEdited) { + this._applyFormatting(); + } + this.calculateUrlPreview(); } - this.calculateUrlPreview(); }, componentWillUnmount: function() { From f9462d1012d46b6f7ec0b1c2654b2c018a7ebb10 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 16:01:52 +0100 Subject: [PATCH 32/43] hide timestamp while editing --- res/css/views/rooms/_EventTile.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 93d97b2913..dfc560e670 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -76,6 +76,10 @@ limitations under the License. } } +.mx_EventTile_isEditing .mx_MessageTimestamp { + visibility: hidden !important; +} + .mx_EventTile .mx_MessageTimestamp { display: block; visibility: hidden; From aef9323f216ed74dac2aa17bbed519c7c1bb554e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 16:10:11 +0100 Subject: [PATCH 33/43] explain negative margin --- res/css/views/elements/_MessageEditor.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/res/css/views/elements/_MessageEditor.scss b/res/css/views/elements/_MessageEditor.scss index 6a5f25532f..8f3fdfb9ef 100644 --- a/res/css/views/elements/_MessageEditor.scss +++ b/res/css/views/elements/_MessageEditor.scss @@ -17,7 +17,10 @@ limitations under the License. .mx_MessageEditor { border-radius: 4px; padding: 3px; - margin: -7px -10px -5px -10px; // from fiddling around in inspector + // this is to try not make the text move but still have some + // padding around and in the editor. + // Actual values from fiddling around in inspector + margin: -7px -10px -5px -10px; .mx_MessageEditor_editor { border-radius: 4px; From 8ce8ca18ff2deaeb8362f3cbc7cbb541cff57359 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 16:10:21 +0100 Subject: [PATCH 34/43] theme-ify! --- res/css/views/elements/_MessageEditor.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/elements/_MessageEditor.scss b/res/css/views/elements/_MessageEditor.scss index 8f3fdfb9ef..40bc132edb 100644 --- a/res/css/views/elements/_MessageEditor.scss +++ b/res/css/views/elements/_MessageEditor.scss @@ -24,8 +24,8 @@ limitations under the License. .mx_MessageEditor_editor { border-radius: 4px; - border: solid 1px #e9edf1; - background-color: #ffffff; + border: solid 1px $primary-hairline-color; + background-color: $primary-bg-color; padding: 3px 6px; white-space: pre-wrap; word-wrap: break-word; From d81ab2464bb5cd1d52996a92f5d5a975a91cf5c7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 17 May 2019 16:15:06 +0100 Subject: [PATCH 35/43] better button alignment also fix sticking out at the right side when not overlaying --- res/css/views/elements/_MessageEditor.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/css/views/elements/_MessageEditor.scss b/res/css/views/elements/_MessageEditor.scss index 40bc132edb..cc5649a224 100644 --- a/res/css/views/elements/_MessageEditor.scss +++ b/res/css/views/elements/_MessageEditor.scss @@ -61,7 +61,7 @@ limitations under the License. z-index: 100; right: 0; margin: 0 -110px 0 0; - padding-right: 107px; + padding-right: 104px; .mx_AccessibleButton { margin-left: 5px; @@ -77,4 +77,5 @@ limitations under the License. .mx_EventTile_last .mx_MessageEditor_buttons { position: static; + margin-right: -103px; } From 3a405701a31f671cbcb6f96c01cc64b48480882f Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 2 May 2019 15:11:06 +0100 Subject: [PATCH 36/43] Debug: Show all events --- res/css/_components.scss | 1 + res/css/views/messages/_ViewSourceEvent.scss | 50 ++++++++++++++ src/components/structures/MessagePanel.js | 5 ++ .../views/messages/ViewSourceEvent.js | 67 +++++++++++++++++++ src/components/views/rooms/EventTile.js | 5 +- src/i18n/strings/en_EN.json | 1 + src/settings/Settings.js | 6 ++ 7 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 res/css/views/messages/_ViewSourceEvent.scss create mode 100644 src/components/views/messages/ViewSourceEvent.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 9823b4ac3d..4b8b687146 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -124,6 +124,7 @@ @import "./views/messages/_SenderProfile.scss"; @import "./views/messages/_TextualEvent.scss"; @import "./views/messages/_UnknownBody.scss"; +@import "./views/messages/_ViewSourceEvent.scss"; @import "./views/room_settings/_AliasSettings.scss"; @import "./views/room_settings/_ColorSettings.scss"; @import "./views/rooms/_AppsDrawer.scss"; diff --git a/res/css/views/messages/_ViewSourceEvent.scss b/res/css/views/messages/_ViewSourceEvent.scss new file mode 100644 index 0000000000..a15924e759 --- /dev/null +++ b/res/css/views/messages/_ViewSourceEvent.scss @@ -0,0 +1,50 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +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_EventTile_content.mx_ViewSourceEvent { + display: flex; + opacity: 0.6; + font-size: 12px; + + pre, code { + flex: 1; + } + + pre { + line-height: 1.2; + margin: 3.5px 0; + } + + .mx_ViewSourceEvent_toggle { + width: 12px; + mask-repeat: no-repeat; + mask-position: 0 center; + mask-size: auto 12px; + visibility: hidden; + background-color: $accent-color; + mask-image: url('$(res)/img/feather-customised/widget/maximise.svg'); + } + + &.mx_ViewSourceEvent_expanded .mx_ViewSourceEvent_toggle { + mask-position: 0 bottom; + margin-bottom: 7px; + mask-image: url('$(res)/img/feather-customised/widget/minimise.svg'); + } + + &:hover .mx_ViewSourceEvent_toggle { + visibility: visible; + } +} diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index dbaab57adf..562d067a7b 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -24,6 +24,7 @@ import {wantsDateSeparator} from '../../DateUtils'; import sdk from '../../index'; import MatrixClientPeg from '../../MatrixClientPeg'; +import SettingsStore from '../../settings/SettingsStore'; const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes const continuedTypes = ['m.sticker', 'm.room.message']; @@ -248,6 +249,10 @@ module.exports = React.createClass({ return false; // ignored = no show (only happens if the ignore happens after an event was received) } + if (SettingsStore.isFeatureEnabled("showHiddenEventsInTimeline")) { + return true; + } + const EventTile = sdk.getComponent('rooms.EventTile'); if (!EventTile.haveTileForEvent(mxEv)) { return false; // no tile = no show diff --git a/src/components/views/messages/ViewSourceEvent.js b/src/components/views/messages/ViewSourceEvent.js new file mode 100644 index 0000000000..717a4b0f05 --- /dev/null +++ b/src/components/views/messages/ViewSourceEvent.js @@ -0,0 +1,67 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +export default class ViewSourceEvent extends React.PureComponent { + static propTypes = { + /* the MatrixEvent to show */ + mxEvent: PropTypes.object.isRequired, + }; + + constructor(props) { + super(props); + + this.state = { + expanded: false, + }; + } + + onToggle = (ev) => { + ev.preventDefault(); + const { expanded } = this.state; + this.setState({ + expanded: !expanded, + }); + } + + render() { + const { mxEvent } = this.props; + const { expanded } = this.state; + + let content; + if (expanded) { + content =
{JSON.stringify(mxEvent, null, 4)}
; + } else { + content = {`{ "type": ${mxEvent.getType()} }`}; + } + + const classes = classNames("mx_ViewSourceEvent mx_EventTile_content", { + mx_ViewSourceEvent_expanded: expanded, + }); + + return + {content} + + ; + } + } diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index f38e3c3946..91699c0c99 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -520,7 +520,10 @@ module.exports = withMatrixClient(React.createClass({ eventType !== 'm.room.message' && eventType !== 'm.sticker' && eventType != 'm.room.create' ); - const tileHandler = getHandlerTile(this.props.mxEvent); + let tileHandler = getHandlerTile(this.props.mxEvent); + if (!tileHandler && SettingsStore.isFeatureEnabled("showHiddenEventsInTimeline")) { + tileHandler = "messages.ViewSourceEvent"; + } // This shouldn't happen: the caller should check we support this type // before trying to instantiate us if (!tileHandler) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 86131645cf..067c2bdeef 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -302,6 +302,7 @@ "Render simple counters in room header": "Render simple counters in room header", "Edit messages after they have been sent (refresh to apply changes)": "Edit messages after they have been sent (refresh to apply changes)", "React to messages with emoji (refresh to apply changes)": "React to messages with emoji (refresh to apply changes)", + "Show hidden events in timeline": "Show hidden events in timeline", "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", "Use compact timeline layout": "Use compact timeline layout", "Show a placeholder for removed messages": "Show a placeholder for removed messages", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 429030d862..5db8599112 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -130,6 +130,12 @@ export const SETTINGS = { supportedLevels: LEVELS_FEATURE, default: false, }, + "showHiddenEventsInTimeline": { + isFeature: true, + displayName: _td("Show hidden events in timeline"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "MessageComposerInput.suggestEmoji": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Enable Emoji suggestions while typing'), From 45cdf880b1305a161b73c072a8cdd3acfba90124 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 17 May 2019 17:43:08 +0100 Subject: [PATCH 37/43] Change setting style --- src/components/structures/MessagePanel.js | 2 +- src/components/views/rooms/EventTile.js | 2 +- .../views/settings/tabs/user/LabsUserSettingsTab.js | 1 + src/i18n/strings/en_EN.json | 2 +- src/settings/Settings.js | 11 +++++------ 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 562d067a7b..4d86305753 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -249,7 +249,7 @@ module.exports = React.createClass({ return false; // ignored = no show (only happens if the ignore happens after an event was received) } - if (SettingsStore.isFeatureEnabled("showHiddenEventsInTimeline")) { + if (SettingsStore.getValue("showHiddenEventsInTimeline")) { return true; } diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 91699c0c99..8269717cc7 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -521,7 +521,7 @@ module.exports = withMatrixClient(React.createClass({ ); let tileHandler = getHandlerTile(this.props.mxEvent); - if (!tileHandler && SettingsStore.isFeatureEnabled("showHiddenEventsInTimeline")) { + if (!tileHandler && SettingsStore.getValue("showHiddenEventsInTimeline")) { tileHandler = "messages.ViewSourceEvent"; } // This shouldn't happen: the caller should check we support this type diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js index c2e62044a3..d272d74d29 100644 --- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.js @@ -52,6 +52,7 @@ export default class LabsUserSettingsTab extends React.Component {
{flags} +
); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 067c2bdeef..b886e70422 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -302,7 +302,6 @@ "Render simple counters in room header": "Render simple counters in room header", "Edit messages after they have been sent (refresh to apply changes)": "Edit messages after they have been sent (refresh to apply changes)", "React to messages with emoji (refresh to apply changes)": "React to messages with emoji (refresh to apply changes)", - "Show hidden events in timeline": "Show hidden events in timeline", "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", "Use compact timeline layout": "Use compact timeline layout", "Show a placeholder for removed messages": "Show a placeholder for removed messages", @@ -334,6 +333,7 @@ "Prompt before sending invites to potentially invalid matrix IDs": "Prompt before sending invites to potentially invalid matrix IDs", "Show developer tools": "Show developer tools", "Order rooms in the room list by most important first instead of most recent": "Order rooms in the room list by most important first instead of most recent", + "Show hidden events in timeline": "Show hidden events in timeline", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading report": "Uploading report", diff --git a/src/settings/Settings.js b/src/settings/Settings.js index 5db8599112..116526b63a 100644 --- a/src/settings/Settings.js +++ b/src/settings/Settings.js @@ -130,12 +130,6 @@ export const SETTINGS = { supportedLevels: LEVELS_FEATURE, default: false, }, - "showHiddenEventsInTimeline": { - isFeature: true, - displayName: _td("Show hidden events in timeline"), - supportedLevels: LEVELS_FEATURE, - default: false, - }, "MessageComposerInput.suggestEmoji": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Enable Emoji suggestions while typing'), @@ -374,4 +368,9 @@ export const SETTINGS = { displayName: _td('Order rooms in the room list by most important first instead of most recent'), default: true, }, + "showHiddenEventsInTimeline": { + displayName: _td("Show hidden events in timeline"), + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, + default: false, + }, }; From 5144907983bbd781d03d28b71dd6ea51942c2b97 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 17 May 2019 17:44:11 +0100 Subject: [PATCH 38/43] Fix indent --- src/components/views/messages/ViewSourceEvent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/messages/ViewSourceEvent.js b/src/components/views/messages/ViewSourceEvent.js index 717a4b0f05..62cf45fb6e 100644 --- a/src/components/views/messages/ViewSourceEvent.js +++ b/src/components/views/messages/ViewSourceEvent.js @@ -63,5 +63,5 @@ export default class ViewSourceEvent extends React.PureComponent { onClick={this.onToggle} />
; - } } +} From 0c0052d06e0804d7e3e5e6ef017a3dfa73edf2d7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 20 May 2019 10:19:29 +0200 Subject: [PATCH 39/43] re-apply formatting when editor is closed --- src/components/views/messages/TextualBody.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index bd37f98360..380d04d1db 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -131,8 +131,9 @@ module.exports = React.createClass({ componentDidUpdate: function(prevProps) { if (!this.props.isEditing) { + const stoppedEditing = prevProps.isEditing && !this.props.isEditing; const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId; - if (messageWasEdited) { + if (messageWasEdited || stoppedEditing) { this._applyFormatting(); } this.calculateUrlPreview(); From 0e5f0f24cc34a24b6506a8c7b1959b8856d6e32e Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 20 May 2019 15:16:12 +0100 Subject: [PATCH 40/43] Extract MELS-style comma separated list to shared utility This allows other UI components to use the same formatting for a long list. --- .../views/elements/MemberEventListSummary.js | 38 +++---------------- src/i18n/strings/en_EN.json | 6 +-- src/utils/FormattingUtils.js | 31 +++++++++++++++ 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/components/views/elements/MemberEventListSummary.js b/src/components/views/elements/MemberEventListSummary.js index 3c58f90a2b..dc9c72df6e 100644 --- a/src/components/views/elements/MemberEventListSummary.js +++ b/src/components/views/elements/MemberEventListSummary.js @@ -1,5 +1,6 @@ /* Copyright 2016 OpenMarket Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,11 +14,13 @@ 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. */ + import React from 'react'; import PropTypes from 'prop-types'; import sdk from '../../../index'; -const MemberAvatar = require('../avatars/MemberAvatar.js'); +import MemberAvatar from '../avatars/MemberAvatar'; import { _t } from '../../../languageHandler'; +import { formatCommaSeparatedList } from '../../../utils/FormattingUtils'; module.exports = React.createClass({ displayName: 'MemberEventListSummary', @@ -105,7 +108,7 @@ module.exports = React.createClass({ ); }); - const desc = this._renderCommaSeparatedList(descs); + const desc = formatCommaSeparatedList(descs); return _t('%(nameList)s %(transitionList)s', { nameList: nameList, transitionList: desc }); }); @@ -132,7 +135,7 @@ module.exports = React.createClass({ * included before "and [n] others". */ _renderNameList: function(users) { - return this._renderCommaSeparatedList(users, this.props.summaryLength); + return formatCommaSeparatedList(users, this.props.summaryLength); }, /** @@ -283,35 +286,6 @@ module.exports = React.createClass({ return res; }, - /** - * Constructs a written English string representing `items`, with an optional limit on - * the number of items included in the result. If specified and if the length of - *`items` is greater than the limit, the string "and n others" will be appended onto - * the result. - * If `items` is empty, returns the empty string. If there is only one item, return - * it. - * @param {string[]} items the items to construct a string from. - * @param {number?} itemLimit the number by which to limit the list. - * @returns {string} a string constructed by joining `items` with a comma between each - * item, but with the last item appended as " and [lastItem]". - */ - _renderCommaSeparatedList(items, itemLimit) { - const remaining = itemLimit === undefined ? 0 : Math.max( - items.length - itemLimit, 0, - ); - if (items.length === 0) { - return ""; - } else if (items.length === 1) { - return items[0]; - } else if (remaining > 0) { - items = items.slice(0, itemLimit); - return _t("%(items)s and %(count)s others", { items: items.join(', '), count: remaining } ); - } else { - const lastItem = items.pop(); - return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem }); - } - }, - _renderAvatars: function(roomMembers) { const avatars = roomMembers.slice(0, this.props.avatarsMaxLength).map((m) => { return ( diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b886e70422..ad5cdd248d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -253,6 +253,9 @@ "This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.", "Please contact your service administrator to continue using the service.": "Please contact your service administrator to continue using the service.", "Unable to connect to Homeserver. Retrying...": "Unable to connect to Homeserver. Retrying...", + "%(items)s and %(count)s others|other": "%(items)s and %(count)s others", + "%(items)s and %(count)s others|one": "%(items)s and one other", + "%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s", "Your browser does not support the required cryptography extensions": "Your browser does not support the required cryptography extensions", "Not a valid Riot keyfile": "Not a valid Riot keyfile", "Authentication check failed: incorrect password?": "Authentication check failed: incorrect password?", @@ -1046,9 +1049,6 @@ "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)schanged their avatar", "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)schanged their avatar %(count)s times", "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)schanged their avatar", - "%(items)s and %(count)s others|other": "%(items)s and %(count)s others", - "%(items)s and %(count)s others|one": "%(items)s and one other", - "%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s", "collapse": "collapse", "expand": "expand", "Power level": "Power level", diff --git a/src/utils/FormattingUtils.js b/src/utils/FormattingUtils.js index b461d22079..1fd7d00feb 100644 --- a/src/utils/FormattingUtils.js +++ b/src/utils/FormattingUtils.js @@ -1,5 +1,6 @@ /* Copyright 2016 OpenMarket Ltd +Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,6 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { _t } from '../languageHandler'; + /** * formats numbers to fit into ~3 characters, suitable for badge counts * e.g: 999, 9.9K, 99K, 0.9M, 9.9M, 99M, 0.9B, 9.9B @@ -63,3 +66,31 @@ export function getUserNameColorClass(userId) { const colorNumber = (hashCode(userId) % 8) + 1; return `mx_Username_color${colorNumber}`; } + +/** + * Constructs a written English string representing `items`, with an optional + * limit on the number of items included in the result. If specified and if the + * length of `items` is greater than the limit, the string "and n others" will + * be appended onto the result. If `items` is empty, returns the empty string. + * If there is only one item, return it. + * @param {string[]} items the items to construct a string from. + * @param {number?} itemLimit the number by which to limit the list. + * @returns {string} a string constructed by joining `items` with a comma + * between each item, but with the last item appended as " and [lastItem]". + */ +export function formatCommaSeparatedList(items, itemLimit) { + const remaining = itemLimit === undefined ? 0 : Math.max( + items.length - itemLimit, 0, + ); + if (items.length === 0) { + return ""; + } else if (items.length === 1) { + return items[0]; + } else if (remaining > 0) { + items = items.slice(0, itemLimit); + return _t("%(items)s and %(count)s others", { items: items.join(', '), count: remaining } ); + } else { + const lastItem = items.pop(); + return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem }); + } +} From 1bc9badeacd85ac5b07a096114f3cc205c1b7e8d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 20 May 2019 16:02:10 +0100 Subject: [PATCH 41/43] Limit reaction sender tooltip to 6 people This limits the number of senders shown in the reaction sender tooltip shown when hovering a reaction to 6 people followed by "and N others" for the rest. Fixes https://github.com/vector-im/riot-web/issues/9722 --- src/components/views/messages/ReactionsRowButtonTooltip.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/ReactionsRowButtonTooltip.js b/src/components/views/messages/ReactionsRowButtonTooltip.js index 4f26cea708..e9ec58e8d0 100644 --- a/src/components/views/messages/ReactionsRowButtonTooltip.js +++ b/src/components/views/messages/ReactionsRowButtonTooltip.js @@ -21,6 +21,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg'; import sdk from '../../../index'; import { unicodeToShort } from '../../../HtmlUtils'; import { _t } from '../../../languageHandler'; +import { formatCommaSeparatedList } from '../../../utils/FormattingUtils'; export default class ReactionsRowButtonTooltip extends React.PureComponent { static propTypes = { @@ -54,7 +55,7 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent { { reactors: () => { return
- {senders.join(", ")} + {formatCommaSeparatedList(senders, 6)}
; }, reactedWith: (sub) => { From e2476acd0a0ba5c62dc5a16edf83de304eaba216 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 20 May 2019 21:44:05 +0100 Subject: [PATCH 42/43] Close copy tooltip in edge cases correctly Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/dialogs/ShareDialog.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/views/dialogs/ShareDialog.js b/src/components/views/dialogs/ShareDialog.js index 13d8e99258..bd6746a1e5 100644 --- a/src/components/views/dialogs/ShareDialog.js +++ b/src/components/views/dialogs/ShareDialog.js @@ -114,7 +114,8 @@ export default class ShareDialog extends React.Component { top: y, message: successful ? _t('Copied!') : _t('Failed to copy'), }, false); - e.target.onmouseleave = close; + // Drop a reference to this close handler for componentWillUnmount + this.closeCopiedTooltip = e.target.onmouseleave = close; } onLinkSpecificEventCheckboxClick() { @@ -131,6 +132,12 @@ export default class ShareDialog extends React.Component { } } + componentWillUnmount() { + // if the Copied tooltip is open then get rid of it, there are ways to close the modal which wouldn't close + // the tooltip otherwise, such as pressing Escape or clicking X really quickly + if (this.closeCopiedTooltip) this.closeCopiedTooltip(); + } + render() { let title; let matrixToUrl; From 868c99d140d2bb4009c0e4b45789bc0d4c65cb45 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 20 May 2019 21:53:15 +0100 Subject: [PATCH 43/43] Hide WhoIsTyping component if the MessagePanel is shaped e.g file grid Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/MessagePanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 9fd3bf508b..d61092c051 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -716,7 +716,7 @@ module.exports = React.createClass({ ); let whoIsTyping; - if (this.props.room) { + if (this.props.room && !this.props.tileShape) { whoIsTyping = (