From bd0e676b4691b0f98de7450d47cdbf1398db48b3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 6 Jun 2019 09:16:28 +0100 Subject: [PATCH 001/188] Switch ugly password boxes to Field or styled input Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../auth/_InteractiveAuthEntryComponents.scss | 10 ++++- .../dialogs/_DeactivateAccountDialog.scss | 7 ++++ .../auth/InteractiveAuthEntryComponents.js | 37 ++++++++----------- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/res/css/views/auth/_InteractiveAuthEntryComponents.scss b/res/css/views/auth/_InteractiveAuthEntryComponents.scss index e2ea7d86fb..8c4f800d19 100644 --- a/res/css/views/auth/_InteractiveAuthEntryComponents.scss +++ b/res/css/views/auth/_InteractiveAuthEntryComponents.scss @@ -55,4 +55,12 @@ limitations under the License. .mx_InteractiveAuthEntryComponents_termsPolicy { display: block; -} \ No newline at end of file +} + +.mx_InteractiveAuthEntryComponents_passwordSection { + width: 300px; +} + +.mx_InteractiveAuthEntryComponents_passwordSection input { + width: 100%; +} diff --git a/res/css/views/dialogs/_DeactivateAccountDialog.scss b/res/css/views/dialogs/_DeactivateAccountDialog.scss index dc76da5b15..9230e6b61c 100644 --- a/res/css/views/dialogs/_DeactivateAccountDialog.scss +++ b/res/css/views/dialogs/_DeactivateAccountDialog.scss @@ -21,3 +21,10 @@ limitations under the License. .mx_DeactivateAccountDialog .mx_DeactivateAccountDialog_input_section { margin-top: 60px; } + +.mx_DeactivateAccountDialog .mx_DeactivateAccountDialog_input_section input { + width: 300px; + border: 1px solid $accent-color; + border-radius: 5px; + padding: 10px; +} diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index b3687db2bd..0e866ad586 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -81,16 +81,10 @@ export const PasswordAuthEntry = React.createClass({ getInitialState: function() { return { - passwordValid: false, + password: "", }; }, - focus: function() { - if (this.refs.passwordField) { - this.refs.passwordField.focus(); - } - }, - _onSubmit: function(e) { e.preventDefault(); if (this.props.busy) return; @@ -98,23 +92,21 @@ export const PasswordAuthEntry = React.createClass({ this.props.submitAuthDict({ type: PasswordAuthEntry.LOGIN_TYPE, user: this.props.matrixClient.credentials.userId, - password: this.refs.passwordField.value, + password: this.state.password, }); }, _onPasswordFieldChange: function(ev) { // enable the submit button iff the password is non-empty this.setState({ - passwordValid: Boolean(this.refs.passwordField.value), + password: ev.target.value, }); }, render: function() { - let passwordBoxClass = null; - - if (this.props.errorText) { - passwordBoxClass = 'error'; - } + const passwordBoxClass = classnames({ + "error": this.props.errorText, + }); let submitButtonOrSpinner; if (this.props.busy) { @@ -124,7 +116,7 @@ export const PasswordAuthEntry = React.createClass({ submitButtonOrSpinner = ( ); } @@ -138,17 +130,20 @@ export const PasswordAuthEntry = React.createClass({ ); } + const Field = sdk.getComponent('elements.Field'); + return (

{ _t("To continue, please enter your password.") }

-
- - +
{ submitButtonOrSpinner } From 6881b479394e60453a787dd44da3cdd6e73f49d8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 6 Jun 2019 23:51:04 +0100 Subject: [PATCH 002/188] fix layout on password field Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/dialogs/_DeactivateAccountDialog.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/dialogs/_DeactivateAccountDialog.scss b/res/css/views/dialogs/_DeactivateAccountDialog.scss index 9230e6b61c..619310a543 100644 --- a/res/css/views/dialogs/_DeactivateAccountDialog.scss +++ b/res/css/views/dialogs/_DeactivateAccountDialog.scss @@ -22,7 +22,7 @@ limitations under the License. margin-top: 60px; } -.mx_DeactivateAccountDialog .mx_DeactivateAccountDialog_input_section input { +.mx_DeactivateAccountDialog .mx_DeactivateAccountDialog_input_section input[type=password] { width: 300px; border: 1px solid $accent-color; border-radius: 5px; From b2dfd3e84ea781499b7036c3d016bd7c8f5b43b0 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 12 Jun 2019 11:56:30 +0100 Subject: [PATCH 003/188] js-sdk rc.1 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a9be315b46..3709f4225a 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "linkifyjs": "^2.1.6", "lodash": "^4.13.1", "lolex": "2.3.2", - "matrix-js-sdk": "2.0.0", + "matrix-js-sdk": "2.0.1-rc.1", "optimist": "^0.6.1", "pako": "^1.0.5", "png-chunks-extract": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index a699e71efb..5a7fc5ddb1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4877,10 +4877,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.0.tgz#490b70e062ee24636536e3d9481e333733d00f2c" integrity sha512-3Zs9P/0zzwTob2pdgT0CHZuMbnSUSp8MB1bddfm+HDmnFWHGT4jvEZRf+2RuPoa+cjdn/z25SEt5gFTqdhvJAg== -matrix-js-sdk@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-2.0.0.tgz#1bbca0170c190a42eeda739f52f2995b446225ee" - integrity sha512-2/NV9hepV8QnX3xl1oaS7VS9tqsBKqCh9/pp+dAYFMA1A5PJACMFjE2M1KnW3JvHH+uDvag4fa7rraMwwGOK+A== +matrix-js-sdk@2.0.1-rc.1: + version "2.0.1-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-2.0.1-rc.1.tgz#2590d6741d74af6b047e362eeea1b9a76c9c2143" + integrity sha512-t4EIK3xLiGdo8HrQeXYO58iw1e/959gidMvKfup8Pw8PJbRRQWXTe4DD0/Qytzq/zgbrx+1L4NxxceOzzwiwiw== dependencies: another-json "^0.2.0" babel-runtime "^6.26.0" From 710a77548ea5e94d22fe5655a54bef06aea3a5ac Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 12 Jun 2019 12:00:15 +0100 Subject: [PATCH 004/188] Prepare changelog for v1.2.2-rc.1 --- CHANGELOG.md | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ed4db44bf..acf920aef0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,154 @@ +Changes in [1.2.2-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.2.2-rc.1) (2019-06-12) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.2.1...v1.2.2-rc.1) + + * Align message context menu to right and vertically where space available + [\#3087](https://github.com/matrix-org/matrix-react-sdk/pull/3087) + * Allow registration to submit for non-fatal errors + [\#3093](https://github.com/matrix-org/matrix-react-sdk/pull/3093) + * Clear the login busy state after .well-known discovery + [\#3092](https://github.com/matrix-org/matrix-react-sdk/pull/3092) + * Update from Weblate + [\#3091](https://github.com/matrix-org/matrix-react-sdk/pull/3091) + * Fix registration after fail-fast + [\#3090](https://github.com/matrix-org/matrix-react-sdk/pull/3090) + * Use setBusy interface of js-sdk interactive auth + [\#3085](https://github.com/matrix-org/matrix-react-sdk/pull/3085) + * Don't handle identity server failure as fatal, and use the right message + [\#3088](https://github.com/matrix-org/matrix-react-sdk/pull/3088) + * Recheck message actions on decrypt + [\#3084](https://github.com/matrix-org/matrix-react-sdk/pull/3084) + * Fix exception on logout + [\#3086](https://github.com/matrix-org/matrix-react-sdk/pull/3086) + * Remember we were trying to accept an invite + [\#3083](https://github.com/matrix-org/matrix-react-sdk/pull/3083) + * Add funding details for GitHub sponsor button + [\#3079](https://github.com/matrix-org/matrix-react-sdk/pull/3079) + * Remove highlight from reactions + [\#3081](https://github.com/matrix-org/matrix-react-sdk/pull/3081) + * Clarify that only lowercase letters are allowed + [\#3080](https://github.com/matrix-org/matrix-react-sdk/pull/3080) + * Don't handle identity server liveliness errors as fatal + [\#3082](https://github.com/matrix-org/matrix-react-sdk/pull/3082) + * truncate long display names in timeline headings + [\#3078](https://github.com/matrix-org/matrix-react-sdk/pull/3078) + * Fail more softly on homeserver liveliness errors + [\#3067](https://github.com/matrix-org/matrix-react-sdk/pull/3067) + * Fix AddressPickerDialog adding wrong entry to selected list case + [\#3076](https://github.com/matrix-org/matrix-react-sdk/pull/3076) + * change profile keybind to backtick from i due to italics conflict + [\#3077](https://github.com/matrix-org/matrix-react-sdk/pull/3077) + * Look busy whilst requesting the email token + [\#3075](https://github.com/matrix-org/matrix-react-sdk/pull/3075) + * Fix email invites address-match checking + [\#3074](https://github.com/matrix-org/matrix-react-sdk/pull/3074) + * Add license info for Twemoji + [\#3073](https://github.com/matrix-org/matrix-react-sdk/pull/3073) + * Show read receipts on top of message + [\#3072](https://github.com/matrix-org/matrix-react-sdk/pull/3072) + * Be somewhat fuzzier when matching emojis to complete on space + [\#3070](https://github.com/matrix-org/matrix-react-sdk/pull/3070) + * Restrict reactions to a single emoji + [\#3069](https://github.com/matrix-org/matrix-react-sdk/pull/3069) + * Fix live updates to reaction row buttons + [\#3068](https://github.com/matrix-org/matrix-react-sdk/pull/3068) + * Don't refresh custom status on logout + [\#3065](https://github.com/matrix-org/matrix-react-sdk/pull/3065) + * Add a logged in class to EmbeddedPage and react to MatrixClient changes + [\#3066](https://github.com/matrix-org/matrix-react-sdk/pull/3066) + * Don't show "can't redact" dialog on network error, with redaction having + local echo & queuing now. + [\#3058](https://github.com/matrix-org/matrix-react-sdk/pull/3058) + * Fix login page breaking on wrong password + [\#3062](https://github.com/matrix-org/matrix-react-sdk/pull/3062) + * Update from Weblate + [\#3064](https://github.com/matrix-org/matrix-react-sdk/pull/3064) + * Install latest JS SDK when linting + [\#3063](https://github.com/matrix-org/matrix-react-sdk/pull/3063) + * Ensure we always show read receipts even with hidden events + [\#3056](https://github.com/matrix-org/matrix-react-sdk/pull/3056) + * Advance read receipts into trailing events without tiles + [\#3059](https://github.com/matrix-org/matrix-react-sdk/pull/3059) + * Remove unused errorText prop + [\#3061](https://github.com/matrix-org/matrix-react-sdk/pull/3061) + * Remove SettingsStore reference in RoomSettingsDialog + [\#3060](https://github.com/matrix-org/matrix-react-sdk/pull/3060) + * Custom notification sounds for rooms + [\#2928](https://github.com/matrix-org/matrix-react-sdk/pull/2928) + * Fix comments in unread room tracking + [\#3054](https://github.com/matrix-org/matrix-react-sdk/pull/3054) + * Allow source tile handler for replacements + [\#3057](https://github.com/matrix-org/matrix-react-sdk/pull/3057) + * Fix linting in MessagePanel + [\#3055](https://github.com/matrix-org/matrix-react-sdk/pull/3055) + * Convert breadcrumbs from labs to real setting + [\#3053](https://github.com/matrix-org/matrix-react-sdk/pull/3053) + * Add local echo on badges in breadcrumbs + [\#3052](https://github.com/matrix-org/matrix-react-sdk/pull/3052) + * Counteract smooth scrolling on breadcrumbs + [\#3051](https://github.com/matrix-org/matrix-react-sdk/pull/3051) + * add sbix fallback twemoji font (and bump to emoji 12) + [\#3050](https://github.com/matrix-org/matrix-react-sdk/pull/3050) + * Add option to change the default country code + [\#3049](https://github.com/matrix-org/matrix-react-sdk/pull/3049) + * Accept JSX into the GenericErrorPage and expose local session vars + [\#3043](https://github.com/matrix-org/matrix-react-sdk/pull/3043) + * Don't try and low encryption info when signing out in low bandwidth mode + [\#3048](https://github.com/matrix-org/matrix-react-sdk/pull/3048) + * only capture enter if something was selected in completions + [\#3047](https://github.com/matrix-org/matrix-react-sdk/pull/3047) + * Fix: better HTML > MD conversion for editing, including lists and quotes + [\#3040](https://github.com/matrix-org/matrix-react-sdk/pull/3040) + * Native emoji require extra line-height + [\#3044](https://github.com/matrix-org/matrix-react-sdk/pull/3044) + * port over low_bandwidth mode to develop + [\#2598](https://github.com/matrix-org/matrix-react-sdk/pull/2598) + * Fix: maintain caret at current line when position is on newline part + [\#3029](https://github.com/matrix-org/matrix-react-sdk/pull/3029) + * Remove username on HS input label + [\#3042](https://github.com/matrix-org/matrix-react-sdk/pull/3042) + * Exclude chrome in ua from safari version check for colr support + [\#3038](https://github.com/matrix-org/matrix-react-sdk/pull/3038) + * fix COLR font check being racy + [\#3034](https://github.com/matrix-org/matrix-react-sdk/pull/3034) + * Override font for usercontent download link + [\#3035](https://github.com/matrix-org/matrix-react-sdk/pull/3035) + * Revert "Make the timeline less noisy for screen readers (mk II) #3019" + [\#3033](https://github.com/matrix-org/matrix-react-sdk/pull/3033) + * Hide autocomplete on Enter key press instead of sending message + [\#2968](https://github.com/matrix-org/matrix-react-sdk/pull/2968) + * Message editing: arrow key (up/down) navigation between editable events + [\#3025](https://github.com/matrix-org/matrix-react-sdk/pull/3025) + * Message editing: fix reply text appearing in edit + [\#3032](https://github.com/matrix-org/matrix-react-sdk/pull/3032) + * Do not try to request thumbnails with non-integer widths + [\#3031](https://github.com/matrix-org/matrix-react-sdk/pull/3031) + * Message editing: preserve strikethrough as well + [\#3030](https://github.com/matrix-org/matrix-react-sdk/pull/3030) + * Add some logging for COLR checks + [\#3027](https://github.com/matrix-org/matrix-react-sdk/pull/3027) + * Fixup for tab completion: take part length into account as well + [\#3026](https://github.com/matrix-org/matrix-react-sdk/pull/3026) + * Message editing: tab completion + [\#3024](https://github.com/matrix-org/matrix-react-sdk/pull/3024) + * Message editing: dont jump to next part when inserting at *start* of + uneditable part + [\#3021](https://github.com/matrix-org/matrix-react-sdk/pull/3021) + * Message editing: preserve and re-apply formatting + [\#3013](https://github.com/matrix-org/matrix-react-sdk/pull/3013) + * Fix relationship between guests, .well-known, and auth + [\#3001](https://github.com/matrix-org/matrix-react-sdk/pull/3001) + * Restore use of full mxid login + [\#2972](https://github.com/matrix-org/matrix-react-sdk/pull/2972) + * Only expose the fallback_hs_url if the homeserver is the default homeserver + [\#2971](https://github.com/matrix-org/matrix-react-sdk/pull/2971) + * Refactor "Next" button into ServerConfig components + [\#2964](https://github.com/matrix-org/matrix-react-sdk/pull/2964) + * Render underlines and tooltips on custom server names in auth pages + [\#2965](https://github.com/matrix-org/matrix-react-sdk/pull/2965) + * Use validated server config for login, registration, and password reset + [\#2941](https://github.com/matrix-org/matrix-react-sdk/pull/2941) + Changes in [1.2.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.2.1) (2019-05-31) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.2.0...v1.2.1) From 3f37fc0cbb25df4753036015037d43e12f6ec1fe Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 12 Jun 2019 12:00:15 +0100 Subject: [PATCH 005/188] v1.2.2-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3709f4225a..9af8cbf4b0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "1.2.1", + "version": "1.2.2-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From acb813372cea786d90c295485c5a3ef7c86d7e2c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 12 Jun 2019 21:32:47 +0100 Subject: [PATCH 006/188] Restore Composer History under shift-up & down Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/ComposerHistoryManager.js | 86 +++++++++++++++++++ .../views/rooms/MessageComposerInput.js | 83 ++++++++++++++++-- 2 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 src/ComposerHistoryManager.js diff --git a/src/ComposerHistoryManager.js b/src/ComposerHistoryManager.js new file mode 100644 index 0000000000..1b3fb588eb --- /dev/null +++ b/src/ComposerHistoryManager.js @@ -0,0 +1,86 @@ +//@flow +/* +Copyright 2017 Aviral Dasgupta + +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 {Value} from 'slate'; + +import _clamp from 'lodash/clamp'; + +type MessageFormat = 'rich' | 'markdown'; + +class HistoryItem { + // We store history items in their native format to ensure history is accurate + // and then convert them if our RTE has subsequently changed format. + value: Value; + format: MessageFormat = 'rich'; + + constructor(value: ?Value, format: ?MessageFormat) { + this.value = value; + this.format = format; + } + + static fromJSON(obj: Object): HistoryItem { + return new HistoryItem( + Value.fromJSON(obj.value), + obj.format, + ); + } + + toJSON(): Object { + return { + value: this.value.toJSON(), + format: this.format, + }; + } +} + +export default class ComposerHistoryManager { + history: Array = []; + prefix: string; + lastIndex: number = 0; // used for indexing the storage + currentIndex: number = 0; // used for indexing the loaded validated history Array + + constructor(roomId: string, prefix: string = 'mx_composer_history_') { + this.prefix = prefix + roomId; + + // TODO: Performance issues? + let item; + for (; item = sessionStorage.getItem(`${this.prefix}[${this.currentIndex}]`); this.currentIndex++) { + try { + this.history.push( + HistoryItem.fromJSON(JSON.parse(item)), + ); + } catch (e) { + console.warn("Throwing away unserialisable history", e); + } + } + this.lastIndex = this.currentIndex; + // reset currentIndex to account for any unserialisable history + this.currentIndex = this.history.length; + } + + save(value: Value, format: MessageFormat) { + const item = new HistoryItem(value, format); + this.history.push(item); + this.currentIndex = this.history.length; + sessionStorage.setItem(`${this.prefix}[${this.lastIndex++}]`, JSON.stringify(item.toJSON())); + } + + getItem(offset: number): ?HistoryItem { + this.currentIndex = _clamp(this.currentIndex + offset, 0, this.history.length - 1); + return this.history[this.currentIndex]; + } +} diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 939ec4d9f5..6efea9a67d 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -60,6 +60,7 @@ import ReplyThread from "../elements/ReplyThread"; import {ContentHelpers} from 'matrix-js-sdk'; import AccessibleButton from '../elements/AccessibleButton'; import {findEditableEvent} from '../../../utils/EventUtils'; +import ComposerHistoryManager from "../../../ComposerHistoryManager"; const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$'); @@ -140,6 +141,7 @@ export default class MessageComposerInput extends React.Component { client: MatrixClient; autocomplete: Autocomplete; + historyManager: ComposerHistoryManager; constructor(props, context) { super(props, context); @@ -329,6 +331,7 @@ export default class MessageComposerInput extends React.Component { componentWillMount() { this.dispatcherRef = dis.register(this.onAction); + this.historyManager = new ComposerHistoryManager(this.props.room.roomId, 'mx_slate_composer_history_'); } componentWillUnmount() { @@ -1039,6 +1042,7 @@ export default class MessageComposerInput extends React.Component { if (cmd) { if (!cmd.error) { + this.historyManager.save(editorState, this.state.isRichTextEnabled ? 'rich' : 'markdown'); this.setState({ editorState: this.createEditorState(), }, ()=>{ @@ -1116,6 +1120,8 @@ export default class MessageComposerInput extends React.Component { let sendHtmlFn = ContentHelpers.makeHtmlMessage; let sendTextFn = ContentHelpers.makeTextMessage; + this.historyManager.save(editorState, this.state.isRichTextEnabled ? 'rich' : 'markdown'); + if (commandText && commandText.startsWith('/me')) { if (replyingToEv) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); @@ -1175,7 +1181,7 @@ export default class MessageComposerInput extends React.Component { }; onVerticalArrow = (e, up) => { - if (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) { + if (e.ctrlKey || e.altKey || e.metaKey) { return; } @@ -1191,15 +1197,26 @@ export default class MessageComposerInput extends React.Component { if (up) { if (!selection.anchor.isAtStartOfNode(document)) return; - const editEvent = findEditableEvent(this.props.room, false); - if (editEvent) { - // We're selecting history, so prevent the key event from doing anything else - e.preventDefault(); - dis.dispatch({ - action: 'edit_event', - event: editEvent, - }); + if (!e.shiftKey) { + const editEvent = findEditableEvent(this.props.room, false); + if (editEvent) { + // We're selecting history, so prevent the key event from doing anything else + e.preventDefault(); + dis.dispatch({ + action: 'edit_event', + event: editEvent, + }); + } + return; } + } else { + if (!selection.anchor.isAtEndOfNode(document)) return; + } + + const selected = this.selectHistory(up); + if (selected) { + // We're selecting history, so prevent the key event from doing anything else + e.preventDefault(); } } else { this.moveAutocompleteSelection(up); @@ -1207,6 +1224,54 @@ export default class MessageComposerInput extends React.Component { } }; + selectHistory = async (up) => { + const delta = up ? -1 : 1; + + // True if we are not currently selecting history, but composing a messag + if (this.historyManager.currentIndex === this.historyManager.history.length) { + // We can't go any further - there isn't any more history, so nop. + if (!up) { + return; + } + this.setState({ + currentlyComposedEditorState: this.state.editorState, + }); + } else if (this.historyManager.currentIndex + delta === this.historyManager.history.length) { + // True when we return to the message being composed currently + this.setState({ + editorState: this.state.currentlyComposedEditorState, + }); + this.historyManager.currentIndex = this.historyManager.history.length; + return; + } + + let editorState; + const historyItem = this.historyManager.getItem(delta); + if (!historyItem) return; + + if (historyItem.format === 'rich' && !this.state.isRichTextEnabled) { + editorState = this.richToMdEditorState(historyItem.value); + } else if (historyItem.format === 'markdown' && this.state.isRichTextEnabled) { + editorState = this.mdToRichEditorState(historyItem.value); + } else { + editorState = historyItem.value; + } + + // Move selection to the end of the selected history + const change = editorState.change().moveToEndOfNode(editorState.document); + + // We don't call this.onChange(change) now, as fixups on stuff like pills + // should already have been done and persisted in the history. + editorState = change.value; + + this.suppressAutoComplete = true; + + this.setState({ editorState }, ()=>{ + this._editor.focus(); + }); + return true; + }; + onTab = async (e) => { this.setState({ someCompletions: null, From 4fda6c21de90d8afa5fa8aa9262093a342ecdc9a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 12 Jun 2019 21:58:10 +0100 Subject: [PATCH 007/188] Use overflow on MemberInfo name/mxid so that the back button stays Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/rooms/_MemberInfo.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/css/views/rooms/_MemberInfo.scss b/res/css/views/rooms/_MemberInfo.scss index c3b3ca2f7d..bb38c41581 100644 --- a/res/css/views/rooms/_MemberInfo.scss +++ b/res/css/views/rooms/_MemberInfo.scss @@ -43,6 +43,8 @@ limitations under the License. .mx_MemberInfo_name h2 { flex: 1; + overflow-x: auto; + max-height: 50px; } .mx_MemberInfo h2 { From 6c5b2053240df80c8fb7c9d1df31f875154a43ec Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Wed, 12 Jun 2019 09:52:18 +0000 Subject: [PATCH 008/188] Translated using Weblate (Albanian) Currently translated at 99.6% (1641 of 1647 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/sq/ --- src/i18n/strings/sq.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index 6a57569025..f39e8cd179 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -1953,5 +1953,9 @@ "Your Riot is misconfigured": "Riot-i juaj është i keqformësuar", "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Kërkojini përgjegjësit të Riot-it tuaj të kontrollojë formësimin tuaj për zëra të pasaktë ose të përsëdytur.", "Unexpected error resolving identity server configuration": "Gabim i papritur teksa ftillohej formësimi i shërbyesit të identiteteve", - "Use lowercase letters, numbers, dashes and underscores only": "Përdorni vetëm shkronja të vogla, numra, vija ndarëse dhe nënvija" + "Use lowercase letters, numbers, dashes and underscores only": "Përdorni vetëm shkronja të vogla, numra, vija ndarëse dhe nënvija", + "Cannot reach identity server": "S’kapet dot shërbyesi i identiteteve", + "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Mund të regjistroheni, por disa veçori do të jenë të papërdorshme, derisa shërbyesi i identiteteve të jetë sërish në linjë. Nëse vazhdoni ta shihni këtë sinjalizim, kontrolloni formësimin tuaj ose lidhuni me një përgjegjës të shërbyesit.", + "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Mund të ricaktoni fjalëkalimin, por disa veçori do të jenë të papërdorshme, derisa shërbyesi i identiteteve të jetë sërish në linjë. Nëse vazhdoni ta shihni këtë sinjalizim, kontrolloni formësimin tuaj ose lidhuni me një përgjegjës të shërbyesit.", + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Mund të bëni hyrjen, por disa veçori do të jenë të papërdorshme, derisa shërbyesi i identiteteve të jetë sërish në linjë. Nëse vazhdoni ta shihni këtë sinjalizim, kontrolloni formësimin tuaj ose lidhuni me një përgjegjës të shërbyesit." } From abb77de78b11a9da169b9e3291ccc076956e91f3 Mon Sep 17 00:00:00 2001 From: Osoitz Date: Thu, 13 Jun 2019 09:16:26 +0000 Subject: [PATCH 009/188] Translated using Weblate (Basque) Currently translated at 100.0% (1647 of 1647 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/eu/ --- src/i18n/strings/eu.json | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/eu.json b/src/i18n/strings/eu.json index 43e0d25d51..3b5602b307 100644 --- a/src/i18n/strings/eu.json +++ b/src/i18n/strings/eu.json @@ -1878,7 +1878,7 @@ "Reason: %(reason)s": "Arrazoia: %(reason)s", "Forget this room": "Ahaztu gela hau", "Re-join": "Berriro elkartu", - "You were banned from %(roomName)s by %(memberName)s": "%(roomName)s gelan sartzea debekatu dizu %(memberName) erabiltzaileak", + "You were banned from %(roomName)s by %(memberName)s": "%(roomName)s gelan sartzea debekatu dizu %(memberName)s erabiltzaileak", "Something went wrong with your invite to %(roomName)s": "Arazo bat egon da zure %(roomName)s gelarako gonbidapenarekin", "%(errcode)s was returned while trying to valide your invite. You could try to pass this information on to a room admin.": "%(errcode)s jaso da zure gonbidapena balioztatzean. Saiatu informazio hau gelako administratzaileari ematen.", "You can only join it with a working invite.": "Elkartzeko baliozko gonbidapen bat behar duzu.", @@ -1942,5 +1942,22 @@ "Your profile": "Zure profila", "Your Matrix account on ": "Zure zerbitzariko Matrix kontua", "Homeserver URL does not appear to be a valid Matrix homeserver": "Hasiera-zerbitzariaren URL-a ez dirudi baliozko hasiera-zerbitzari batena", - "Identity server URL does not appear to be a valid identity server": "Identitate-zerbitzariaren URL-a ez dirudi baliozko identitate-zerbitzari batena" + "Identity server URL does not appear to be a valid identity server": "Identitate-zerbitzariaren URL-a ez dirudi baliozko identitate-zerbitzari batena", + "Cannot reach identity server": "Ezin izan da identitate-zerbitzaria atzitu", + "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Izena eman dezakezu, baina ezaugarri batzuk ez dira eskuragarri izango identitate-zerbitzaria berriro eskuragarri egon arte. Abisu hau ikusten jarraitzen baduzu, egiaztatu zure konfigurazioa edo kontaktatu zerbitzariaren administratzailea.", + "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Zure pasahitza berrezarri dezakezu, baina ezaugarri batzuk ez dira eskuragarri izango identitate-zerbitzaria berriro eskuragarri egon arte. Abisu hau ikusten jarraitzen baduzu, egiaztatu zure konfigurazioa edo kontaktatu zerbitzariaren administratzailea.", + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Saioa hasi dezakezu, baina ezaugarri batzuk ez dira eskuragarri izango identitate-zerbitzaria berriro eskuragarri egon arte. Abisu hau ikusten jarraitzen baduzu, egiaztatu zure konfigurazioa edo kontaktatu zerbitzariaren administratzailea.", + "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Ezin izan da gonbidapena baliogabetu. Zerbitzariak une bateko arazoren bat izan lezake edo agian ez duzu gonbidapena baliogabetzeko baimen nahiko.", + "reacted with %(shortName)s": " erabiltzaileak %(shortName)s batekin erreakzionatu du", + "If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.": "Arazoa ikertzen lagundu gaitzakeen testuinguru gehiago badago, esaterako gertatutakoan zer egiten ari zinen, gelaren ID-a, erabiltzaile ID-ak eta abar, mesedez jarri horiek hemen.", + "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Saioaren datu batzuk, zifratutako mezuen gakoak barne, falta dira. Amaitu saioa eta hasi saioa berriro hau konpontzeko, gakoak babes-kopiatik berreskuratuz.", + "Your browser likely removed this data when running low on disk space.": "Ziur asko zure nabigatzaileak kendu ditu datu hauek diskoan leku gutxi zuelako.", + "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "Fitxategi hau handiegia da igo ahal izateko. Fitxategiaren tamaina-muga %(limit)s da, baina fitxategi honen tamaina %(sizeOfThisFile)s da.", + "These files are too large to upload. The file size limit is %(limit)s.": "Fitxategi hauek handiegiak dira igotzeko. Fitxategien tamaina-muga %(limit)s da.", + "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Fitxategi batzuk handiegiak dira igotzeko. Fitxategien tamaina-muga %(limit)s da.", + "A widget located at %(widgetUrl)s would like to verify your identity. By allowing this, the widget will be able to verify your user ID, but not perform actions as you.": "%(widgetUrl)s helbidean kokatutako trepeta batek zure identitatea egiaztatu nahi du. Hau baimentzen baduzu, trepetak zure erabiltzaile ID-a egiaztatu ahal izango du, baina ez zure izenean ekintzarik egin.", + "Riot failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "Riot-ek huts egin du zure hasiera-zerbitzariaren protokoloen zerrenda eskuratzean. Agian hasiera-zerbitzaria zaharregia da hirugarrengoen sareak onartzeko.", + "Failed to get autodiscovery configuration from server": "Huts egin du aurkikuntza automatikoaren konfigurazioa zerbitzaritik eskuratzean", + "Invalid base_url for m.homeserver": "Baliogabeko base_url m.homeserver zerbitzariarentzat", + "Invalid base_url for m.identity_server": "Baliogabeko base_url m.identity_server zerbitzariarentzat" } From acaadaba1174fb93750e54f477ed6628d9a6441b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20Luke=C5=A1?= Date: Tue, 11 Jun 2019 19:39:30 +0000 Subject: [PATCH 010/188] Translated using Weblate (Czech) Currently translated at 99.9% (1646 of 1647 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/cs/ --- src/i18n/strings/cs.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index d4c0d8e799..44b991c0c9 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -1905,5 +1905,9 @@ "Your Riot is misconfigured": "Riot je špatně nakonfigurován", "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Zeptejte se svého administrátora, jestli by vám nezkontrolovat konfiguraci Riotu, asi obsahuje chyby nebo duplicity.", "Unexpected error resolving identity server configuration": "Chyba při hledání konfigurace serveru identity", - "Use lowercase letters, numbers, dashes and underscores only": "Používejte pouze malá písmena, čísla, pomlčky a podtržítka" + "Use lowercase letters, numbers, dashes and underscores only": "Používejte pouze malá písmena, čísla, pomlčky a podtržítka", + "Cannot reach identity server": "Nelze se připojit k serveru identity", + "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Můžete se zaregistrovat, ale některé funkce nebudou dostupné dokud nezačne server identity fungovat. Pokud se vám toto varování zobrazuje pořád, tak zkontrolujte svojí konfiguraci a nebo kontaktujte administrátora serveru.", + "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Můžete si změnit heslo, ale některé funkce nebudou dostupné dokud nezačne server identity fungovat. Pokud se vám toto varování zobrazuje pořád, tak zkontrolujte svojí konfiguraci a nebo kontaktujte administrátora serveru.", + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Můžete se přihlásit, ale některé funkce nebudou dostupné dokud nezačne server identity fungovat. Pokud se vám toto varování zobrazuje pořád, tak zkontrolujte svojí konfiguraci a nebo kontaktujte administrátora serveru." } From 357f8b5893a1615874136ad66a9f1b7f38722722 Mon Sep 17 00:00:00 2001 From: random Date: Wed, 12 Jun 2019 10:19:14 +0000 Subject: [PATCH 011/188] Translated using Weblate (Italian) Currently translated at 100.0% (1647 of 1647 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index bc85aaa387..4f10d82009 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -1876,7 +1876,7 @@ "Try again later, or ask a room admin to check if you have access.": "Riprova più tardi, o chiedi ad un admin della stanza di controllare se hai l'accesso.", "%(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.": "%(errcode)s si è verificato tentando di accedere alla stanza. Se pensi che tu stia vedendo questo messaggio per errore, invia una segnalazione di errore.", "This room has already been upgraded.": "Questa stanza è già stata aggiornata.", - "Agree or Disagree": "", + "Agree or Disagree": "D'accordo o Non d'accordo", "reacted with %(shortName)s": "ha reagito con %(shortName)s", "Edited at %(date)s": "Modificato il %(date)s", "edited": "modificato", @@ -1942,5 +1942,16 @@ "Notification sound": "Suoni di notifica", "Reset": "Ripristina", "Set a new custom sound": "Imposta un nuovo suono personalizzato", - "Browse": "Sfoglia" + "Browse": "Sfoglia", + "Cannot reach homeserver": "Impossibile raggiungere l'homeserver", + "Ensure you have a stable internet connection, or get in touch with the server admin": "Assicurati di avere una connessione internet stabile, o contatta l'amministratore del server", + "Your Riot is misconfigured": "Il tuo Riot è configurato male", + "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Chiedi al tuo amministratore di Riot di controllare la tua configurazione per voci non valide o doppie.", + "Cannot reach identity server": "Impossibile raggiungere il server identità", + "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Puoi registrarti, ma alcune funzioni non saranno disponibili finchè il server identità non sarà tornato online. Se continui a vedere questo avviso, controlla la tua configurazione o contatta un amministratore del server.", + "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Puoi ripristinare la password, ma alcune funzioni non saranno disponibili finchè il server identità non sarà tornato online. Se continui a vedere questo avviso, controlla la tua configurazione o contatta un amministratore del server.", + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Puoi accedere, ma alcune funzioni non saranno disponibili finchè il server identità non sarà tornato online. Se continui a vedere questo avviso, controlla la tua configurazione o contatta un amministratore del server.", + "Unexpected error resolving identity server configuration": "Errore inaspettato risolvendo la configurazione del server identità", + "Like or Dislike": "Piace o Non piace", + "Use lowercase letters, numbers, dashes and underscores only": "Usa solo minuscole, numeri, trattini e trattini bassi" } From 1090b7d9124b3e0d51dc14d07325443a144bb48d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 13 Jun 2019 23:48:47 +0100 Subject: [PATCH 012/188] Use flex: 1 for mx_Field to replace all the calc(100% - 20px) and more Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/_components.scss | 1 - res/css/views/auth/_AuthBody.scss | 2 -- res/css/views/auth/_ServerConfig.scss | 1 - res/css/views/dialogs/_BugReportDialog.scss | 25 ------------------- res/css/views/dialogs/_DevtoolsDialog.scss | 7 ++++-- res/css/views/dialogs/_SetPasswordDialog.scss | 1 - res/css/views/elements/_EditableItemList.scss | 8 +----- res/css/views/elements/_Field.scss | 1 + res/css/views/elements/_PowerSelector.scss | 1 - res/css/views/settings/_EmailAddresses.scss | 6 ----- res/css/views/settings/_PhoneNumbers.scss | 6 ----- res/css/views/settings/_ProfileSettings.scss | 5 ---- .../tabs/room/_GeneralRoomSettingsTab.scss | 4 --- .../tabs/user/_GeneralUserSettingsTab.scss | 18 +------------ .../user/_PreferencesUserSettingsTab.scss | 8 ------ .../tabs/user/_VoiceUserSettingsTab.scss | 5 ---- 16 files changed, 8 insertions(+), 91 deletions(-) delete mode 100644 res/css/views/dialogs/_BugReportDialog.scss diff --git a/res/css/_components.scss b/res/css/_components.scss index 2a91f08ee4..843f314bd1 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -50,7 +50,6 @@ @import "./views/context_menus/_TopLeftMenu.scss"; @import "./views/dialogs/_AddressPickerDialog.scss"; @import "./views/dialogs/_Analytics.scss"; -@import "./views/dialogs/_BugReportDialog.scss"; @import "./views/dialogs/_ChangelogDialog.scss"; @import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss"; @import "./views/dialogs/_ConfirmUserActionDialog.scss"; diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index 16ac876869..cce3b5dbf5 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -72,7 +72,6 @@ limitations under the License. } .mx_Field input { - width: 100%; box-sizing: border-box; } @@ -110,7 +109,6 @@ limitations under the License. .mx_AuthBody_fieldRow > .mx_Field { margin: 0 5px; - flex: 1; } .mx_AuthBody_fieldRow > .mx_Field:first-child { diff --git a/res/css/views/auth/_ServerConfig.scss b/res/css/views/auth/_ServerConfig.scss index fe96da2019..a31feb75d7 100644 --- a/res/css/views/auth/_ServerConfig.scss +++ b/res/css/views/auth/_ServerConfig.scss @@ -20,7 +20,6 @@ limitations under the License. } .mx_ServerConfig_fields .mx_Field { - flex: 1; margin: 0 5px; } diff --git a/res/css/views/dialogs/_BugReportDialog.scss b/res/css/views/dialogs/_BugReportDialog.scss deleted file mode 100644 index 90ef55b945..0000000000 --- a/res/css/views/dialogs/_BugReportDialog.scss +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2017 OpenMarket Ltd - -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_BugReportDialog .mx_Field { - flex: 1; -} - -.mx_BugReportDialog_field_input { - // TODO: We should really apply this to all .mx_Field inputs. - // See https://github.com/vector-im/riot-web/issues/9344. - flex: 1; -} diff --git a/res/css/views/dialogs/_DevtoolsDialog.scss b/res/css/views/dialogs/_DevtoolsDialog.scss index 1f5d36b57a..8e669acd10 100644 --- a/res/css/views/dialogs/_DevtoolsDialog.scss +++ b/res/css/views/dialogs/_DevtoolsDialog.scss @@ -23,7 +23,11 @@ limitations under the License. cursor: default !important; } -.mx_DevTools_RoomStateExplorer_button, .mx_DevTools_ServersInRoomList_button, .mx_DevTools_RoomStateExplorer_query { +.mx_DevTools_RoomStateExplorer_query { + margin-bottom: 10px; +} + +.mx_DevTools_RoomStateExplorer_button, .mx_DevTools_ServersInRoomList_button { margin-bottom: 10px; width: 100%; } @@ -75,7 +79,6 @@ limitations under the License. max-width: 684px; min-height: 250px; padding: 10px; - width: 100%; } .mx_DevTools_content .mx_Field_input { diff --git a/res/css/views/dialogs/_SetPasswordDialog.scss b/res/css/views/dialogs/_SetPasswordDialog.scss index 28a8b7c9d7..325ff6c6ed 100644 --- a/res/css/views/dialogs/_SetPasswordDialog.scss +++ b/res/css/views/dialogs/_SetPasswordDialog.scss @@ -21,7 +21,6 @@ limitations under the License. color: $primary-fg-color; background-color: $primary-bg-color; font-size: 15px; - width: 100%; max-width: 280px; margin-bottom: 10px; } diff --git a/res/css/views/elements/_EditableItemList.scss b/res/css/views/elements/_EditableItemList.scss index be96d811d3..51fa4c4423 100644 --- a/res/css/views/elements/_EditableItemList.scss +++ b/res/css/views/elements/_EditableItemList.scss @@ -42,12 +42,6 @@ limitations under the License. margin-right: 5px; } -.mx_EditableItemList_newItem .mx_Field input { - // Use 100% of the space available for the input, but don't let the 10px - // padding on either side of the input to push it out of alignment. - width: calc(100% - 20px); -} - .mx_EditableItemList_label { margin-bottom: 5px; -} \ No newline at end of file +} diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index 147bb3b471..f9cbf8c541 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -42,6 +42,7 @@ limitations under the License. padding: 8px 9px; color: $primary-fg-color; background-color: $primary-bg-color; + flex: 1; } .mx_Field select { diff --git a/res/css/views/elements/_PowerSelector.scss b/res/css/views/elements/_PowerSelector.scss index 69f3a8eebb..799f6f246e 100644 --- a/res/css/views/elements/_PowerSelector.scss +++ b/res/css/views/elements/_PowerSelector.scss @@ -20,6 +20,5 @@ limitations under the License. .mx_PowerSelector .mx_Field select, .mx_PowerSelector .mx_Field input { - width: 100%; box-sizing: border-box; } diff --git a/res/css/views/settings/_EmailAddresses.scss b/res/css/views/settings/_EmailAddresses.scss index eef804a33b..4f9541af2c 100644 --- a/res/css/views/settings/_EmailAddresses.scss +++ b/res/css/views/settings/_EmailAddresses.scss @@ -35,9 +35,3 @@ limitations under the License. .mx_ExistingEmailAddress_confirmBtn { margin-right: 5px; } - -.mx_EmailAddresses_new .mx_Field input { - // Use 100% of the space available for the input, but don't let the 10px - // padding on either side of the input to push it out of alignment. - width: calc(100% - 20px); -} diff --git a/res/css/views/settings/_PhoneNumbers.scss b/res/css/views/settings/_PhoneNumbers.scss index 2f54babd6f..a3891882c2 100644 --- a/res/css/views/settings/_PhoneNumbers.scss +++ b/res/css/views/settings/_PhoneNumbers.scss @@ -36,12 +36,6 @@ limitations under the License. margin-right: 5px; } -.mx_PhoneNumbers_new .mx_Field input { - // Use 100% of the space available for the input, but don't let the 10px - // padding on either side of the input to push it out of alignment. - width: calc(100% - 20px); -} - .mx_PhoneNumbers_input { display: flex; align-items: center; diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss index b2e449ac34..a972162618 100644 --- a/res/css/views/settings/_ProfileSettings.scss +++ b/res/css/views/settings/_ProfileSettings.scss @@ -22,11 +22,6 @@ limitations under the License. flex-grow: 1; } -.mx_ProfileSettings_controls .mx_Field #profileDisplayName, -.mx_ProfileSettings_controls .mx_Field #profileTopic { - width: calc(100% - 20px); // subtract 10px padding on left and right -} - .mx_ProfileSettings_controls .mx_Field #profileTopic { height: 4em; } diff --git a/res/css/views/settings/tabs/room/_GeneralRoomSettingsTab.scss b/res/css/views/settings/tabs/room/_GeneralRoomSettingsTab.scss index 91d7ed2c7d..af55820d66 100644 --- a/res/css/views/settings/tabs/room/_GeneralRoomSettingsTab.scss +++ b/res/css/views/settings/tabs/room/_GeneralRoomSettingsTab.scss @@ -17,7 +17,3 @@ limitations under the License. .mx_GeneralRoomSettingsTab_profileSection { margin-top: 10px; } - -.mx_GeneralRoomSettingsTab .mx_AliasSettings .mx_Field select { - width: 100%; -} diff --git a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss index bec013674a..091c98ffb8 100644 --- a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss @@ -14,33 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_GeneralUserSettingsTab_changePassword, -.mx_GeneralUserSettingsTab_themeSection { - display: block; -} - .mx_GeneralUserSettingsTab_changePassword .mx_Field, .mx_GeneralUserSettingsTab_themeSection .mx_Field { - display: block; margin-right: 100px; // Align with the other fields on the page } -.mx_GeneralUserSettingsTab_changePassword .mx_Field input { - display: block; - width: calc(100% - 20px); // subtract 10px padding on left and right -} - .mx_GeneralUserSettingsTab_changePassword .mx_Field:first-child { margin-top: 0; } -.mx_GeneralUserSettingsTab_themeSection .mx_Field select { - display: block; - width: 100%; -} - .mx_GeneralUserSettingsTab_accountSection > .mx_EmailAddresses, .mx_GeneralUserSettingsTab_accountSection > .mx_PhoneNumbers, .mx_GeneralUserSettingsTab_languageInput { margin-right: 100px; // Align with the other fields on the page -} \ No newline at end of file +} diff --git a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss index f447221b7a..b3430f47af 100644 --- a/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_PreferencesUserSettingsTab.scss @@ -17,11 +17,3 @@ limitations under the License. .mx_PreferencesUserSettingsTab .mx_Field { margin-right: 100px; // Align with the rest of the controls } - -.mx_PreferencesUserSettingsTab .mx_Field input { - display: block; - - // Subtract 10px padding on left and right - // This is to keep the input aligned with the rest of the tab's controls. - width: calc(100% - 20px); -} diff --git a/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss index f5dba9831e..36c8cfd896 100644 --- a/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_VoiceUserSettingsTab.scss @@ -14,11 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_VoiceUserSettingsTab .mx_Field select { - width: 100%; - max-width: 100%; -} - .mx_VoiceUserSettingsTab .mx_Field { margin-right: 100px; // align with the rest of the fields } From 8232553e36dbd0007ffba73c578be2a1ae99da2c Mon Sep 17 00:00:00 2001 From: Le Dang Trung Date: Fri, 14 Jun 2019 08:02:55 +0000 Subject: [PATCH 013/188] Added translation using Weblate (Vietnamese) --- src/i18n/strings/vi.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/i18n/strings/vi.json diff --git a/src/i18n/strings/vi.json b/src/i18n/strings/vi.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/src/i18n/strings/vi.json @@ -0,0 +1 @@ +{} From 0b17812b9cfc412a7a2cde11bcffab6722d1c414 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Jun 2019 11:01:34 +0200 Subject: [PATCH 014/188] allow editing emotes --- src/utils/EventUtils.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/utils/EventUtils.js b/src/utils/EventUtils.js index 219b53bc5e..ffc47e2277 100644 --- a/src/utils/EventUtils.js +++ b/src/utils/EventUtils.js @@ -46,9 +46,12 @@ export function isContentActionable(mxEvent) { } export function canEditContent(mxEvent) { - return mxEvent.status !== EventStatus.CANCELLED && - mxEvent.getType() === 'm.room.message' && - mxEvent.getOriginalContent().msgtype === "m.text" && + if (mxEvent.status === EventStatus.CANCELLED || mxEvent.getType() !== "m.room.message") { + return false; + } + const content = mxEvent.getOriginalContent(); + const {msgtype} = content; + return (msgtype === "m.text" || msgtype === "m.emote") && mxEvent.getSender() === MatrixClientPeg.get().getUserId(); } From aecfbce55cb70fd6acad41847c95e75b97c04afb Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Jun 2019 11:01:52 +0200 Subject: [PATCH 015/188] prepend "/me " to emotes when parsing them to edit --- src/editor/deserialize.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/editor/deserialize.js b/src/editor/deserialize.js index 48625cba5f..2d98bbc41a 100644 --- a/src/editor/deserialize.js +++ b/src/editor/deserialize.js @@ -207,12 +207,13 @@ function parseHtmlMessage(html, room, client) { export function parseEvent(event, room, client) { const content = event.getContent(); + let parts; if (content.format === "org.matrix.custom.html") { - return parseHtmlMessage(content.formatted_body || "", room, client); + parts = parseHtmlMessage(content.formatted_body || "", room, client); } else { const body = content.body || ""; const lines = body.split("\n"); - const parts = lines.reduce((parts, line, i) => { + parts = lines.reduce((parts, line, i) => { const isLast = i === lines.length - 1; const text = new PlainPart(line); const newLine = !isLast && new NewlinePart("\n"); @@ -222,6 +223,9 @@ export function parseEvent(event, room, client) { return parts.concat(text); } }, []); - return parts; } + if (content.msgtype === "m.emote") { + parts.unshift(new PlainPart("/me ")); + } + return parts; } From 3cfdd518ee51598d6fdbf3a359d86c08cfb88cd1 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Jun 2019 11:02:20 +0200 Subject: [PATCH 016/188] detect emote when sending (and trim "/me " for content) --- src/components/views/elements/MessageEditor.js | 18 +++++++++++++++--- src/editor/model.js | 8 ++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/components/views/elements/MessageEditor.js b/src/components/views/elements/MessageEditor.js index 0aff6781ee..9faae4588b 100644 --- a/src/components/views/elements/MessageEditor.js +++ b/src/components/views/elements/MessageEditor.js @@ -150,16 +150,28 @@ export default class MessageEditor extends React.Component { dis.dispatch({action: 'focus_composer'}); } + _isEmote() { + const firstPart = this.model.parts[0]; + return firstPart && firstPart.type === "plain" && firstPart.text.startsWith("/me "); + } + _sendEdit = () => { + const isEmote = this._isEmote(); + let model = this.model; + if (isEmote) { + // trim "/me " + model = model.clone(); + model.removeText({index: 0, offset: 0}, 4); + } const newContent = { - "msgtype": "m.text", - "body": textSerialize(this.model), + "msgtype": isEmote ? "m.emote" : "m.text", + "body": textSerialize(model), }; const contentBody = { msgtype: newContent.msgtype, body: ` * ${newContent.body}`, }; - const formattedBody = htmlSerializeIfNeeded(this.model); + const formattedBody = htmlSerializeIfNeeded(model); if (formattedBody) { newContent.format = "org.matrix.custom.html"; newContent.formatted_body = formattedBody; diff --git a/src/editor/model.js b/src/editor/model.js index 04a56ab65b..7cc6041044 100644 --- a/src/editor/model.js +++ b/src/editor/model.js @@ -27,6 +27,10 @@ export default class EditorModel { this._updateCallback = updateCallback; } + clone() { + return new EditorModel(this._parts, this._partCreator, this._updateCallback); + } + _insertPart(index, part) { this._parts.splice(index, 0, part); if (this._activePartIdx >= index) { @@ -91,7 +95,7 @@ export default class EditorModel { const position = this.positionForOffset(diff.at, caret.atNodeEnd); let removedOffsetDecrease = 0; if (diff.removed) { - removedOffsetDecrease = this._removeText(position, diff.removed.length); + removedOffsetDecrease = this.removeText(position, diff.removed.length); } let addedLen = 0; if (diff.added) { @@ -177,7 +181,7 @@ export default class EditorModel { * @return {Number} how many characters before pos were also removed, * usually because of non-editable parts that can only be removed in their entirety. */ - _removeText(pos, len) { + removeText(pos, len) { let {index, offset} = pos; let removedOffsetDecrease = 0; while (len > 0) { From c2a03893dc83ecee08e28c5a9b486836ba118b07 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Fri, 14 Jun 2019 08:31:44 +0000 Subject: [PATCH 017/188] Translated using Weblate (Albanian) Currently translated at 99.6% (1644 of 1650 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/sq/ --- src/i18n/strings/sq.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index f39e8cd179..a6f03bb6e0 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -1957,5 +1957,8 @@ "Cannot reach identity server": "S’kapet dot shërbyesi i identiteteve", "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Mund të regjistroheni, por disa veçori do të jenë të papërdorshme, derisa shërbyesi i identiteteve të jetë sërish në linjë. Nëse vazhdoni ta shihni këtë sinjalizim, kontrolloni formësimin tuaj ose lidhuni me një përgjegjës të shërbyesit.", "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Mund të ricaktoni fjalëkalimin, por disa veçori do të jenë të papërdorshme, derisa shërbyesi i identiteteve të jetë sërish në linjë. Nëse vazhdoni ta shihni këtë sinjalizim, kontrolloni formësimin tuaj ose lidhuni me një përgjegjës të shërbyesit.", - "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Mund të bëni hyrjen, por disa veçori do të jenë të papërdorshme, derisa shërbyesi i identiteteve të jetë sërish në linjë. Nëse vazhdoni ta shihni këtë sinjalizim, kontrolloni formësimin tuaj ose lidhuni me një përgjegjës të shërbyesit." + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Mund të bëni hyrjen, por disa veçori do të jenë të papërdorshme, derisa shërbyesi i identiteteve të jetë sërish në linjë. Nëse vazhdoni ta shihni këtë sinjalizim, kontrolloni formësimin tuaj ose lidhuni me një përgjegjës të shërbyesit.", + "Log in to your new account.": "Bëni hyrjen te llogaria juaj e re.", + "You can now close this window or log in to your new account.": "Tani mund ta mbyllni këtë dritare ose të bëni hyrjen në llogarinë tuaj të re.", + "Registration Successful": "Regjistrim i Suksesshëm" } From 9b16216931ea08239ac4bd78e7a274094f83d854 Mon Sep 17 00:00:00 2001 From: Osoitz Date: Fri, 14 Jun 2019 07:30:53 +0000 Subject: [PATCH 018/188] Translated using Weblate (Basque) Currently translated at 100.0% (1650 of 1650 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/eu/ --- src/i18n/strings/eu.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/eu.json b/src/i18n/strings/eu.json index 3b5602b307..5a4b8901e9 100644 --- a/src/i18n/strings/eu.json +++ b/src/i18n/strings/eu.json @@ -1959,5 +1959,8 @@ "Riot failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "Riot-ek huts egin du zure hasiera-zerbitzariaren protokoloen zerrenda eskuratzean. Agian hasiera-zerbitzaria zaharregia da hirugarrengoen sareak onartzeko.", "Failed to get autodiscovery configuration from server": "Huts egin du aurkikuntza automatikoaren konfigurazioa zerbitzaritik eskuratzean", "Invalid base_url for m.homeserver": "Baliogabeko base_url m.homeserver zerbitzariarentzat", - "Invalid base_url for m.identity_server": "Baliogabeko base_url m.identity_server zerbitzariarentzat" + "Invalid base_url for m.identity_server": "Baliogabeko base_url m.identity_server zerbitzariarentzat", + "Log in to your new account.": "Hasi saioa zure kontu berrian.", + "You can now close this window or log in to your new account.": "Itxi leiho hau edo hasi saioa zure kontu berrian.", + "Registration Successful": "Ongi erregistratuta" } From 965985619e08621e6e92740336e4f8e5aad40d02 Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Fri, 14 Jun 2019 05:44:46 +0000 Subject: [PATCH 019/188] Translated using Weblate (Bulgarian) Currently translated at 100.0% (1650 of 1650 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/bg/ --- src/i18n/strings/bg.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/bg.json b/src/i18n/strings/bg.json index 83e12507ca..dd513abf82 100644 --- a/src/i18n/strings/bg.json +++ b/src/i18n/strings/bg.json @@ -1971,5 +1971,18 @@ "Invalid base_url for m.homeserver": "Невалиден base_url в m.homeserver", "Homeserver URL does not appear to be a valid Matrix homeserver": "Homeserver адресът не изглежда да е валиден Matrix сървър", "Invalid base_url for m.identity_server": "Невалиден base_url в m.identity_server", - "Identity server URL does not appear to be a valid identity server": "Адресът на сървърът за самоличност не изглежда да е валиден сървър за самоличност" + "Identity server URL does not appear to be a valid identity server": "Адресът на сървърът за самоличност не изглежда да е валиден сървър за самоличност", + "Cannot reach homeserver": "Неуспешна връзка със сървъра", + "Ensure you have a stable internet connection, or get in touch with the server admin": "Уверете се, че интернет връзката ви е стабилна, или се свържете с администратора на сървъра", + "Your Riot is misconfigured": "Riot не е конфигуриран правилно", + "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Попитайте Riot администратора да провери конфигурацията ви за неправилни или дублирани записи.", + "Cannot reach identity server": "Неуспешна връзка със сървъра за самоличност", + "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Може да се регистрирате, но някои функции няма да са достъпни докато сървъра за самоличност е офлайн. Ако продължавате да виждате това предупреждение, проверете конфигурацията или се свържете с администратора на сървъра.", + "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Може да възстановите паролата си, но някои функции няма да са достъпни докато сървъра за самоличност е офлайн. Ако продължавате да виждате това предупреждение, проверете конфигурацията или се свържете с администратора на сървъра.", + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Може да влезете в профила си, но някои функции няма да са достъпни докато сървъра за самоличност е офлайн. Ако продължавате да виждате това предупреждение, проверете конфигурацията или се свържете с администратора на сървъра.", + "Unexpected error resolving identity server configuration": "Неочаквана грешка при откриване на конфигурацията на сървъра за самоличност", + "Use lowercase letters, numbers, dashes and underscores only": "Използвайте само малки букви, цифри, тирета и подчерта", + "Log in to your new account.": "Влезте в новия си акаунт.", + "You can now close this window or log in to your new account.": "Можете да затворите този прозорец или да влезете в новия си акаунт.", + "Registration Successful": "Успешна регистрация" } From 18afdf9d07d9b75a8ee29d67b08ce7716ca2456e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Fri, 14 Jun 2019 09:01:09 +0000 Subject: [PATCH 020/188] Translated using Weblate (French) Currently translated at 100.0% (1650 of 1650 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index c5b010b1e4..cbd43fafc0 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -1999,5 +1999,8 @@ "Cannot reach identity server": "Impossible de joindre le serveur d’identité", "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Vous pouvez vous inscrire, mais certaines fonctionnalités ne seront pas disponibles jusqu’au retour du serveur d’identité. Si vous continuez à voir cet avertissement, vérifiez votre configuration ou contactez un administrateur du serveur.", "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Vous pouvez réinitialiser votre mot de passe, mais certaines fonctionnalités ne seront pas disponibles jusqu’au retour du serveur d’identité. Si vous continuez à voir cet avertissement, vérifiez votre configuration ou contactez un administrateur du serveur.", - "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Vous pouvez vous connecter, mais certaines fonctionnalités ne seront pas disponibles jusqu’au retour du serveur d’identité. Si vous continuez à voir cet avertissement, vérifiez votre configuration ou contactez un administrateur du serveur." + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Vous pouvez vous connecter, mais certaines fonctionnalités ne seront pas disponibles jusqu’au retour du serveur d’identité. Si vous continuez à voir cet avertissement, vérifiez votre configuration ou contactez un administrateur du serveur.", + "Log in to your new account.": "Connectez-vous à votre nouveau compte.", + "You can now close this window or log in to your new account.": "Vous pouvez à présent fermer cette fenêtre ou vous connecter à votre nouveau compte.", + "Registration Successful": "Inscription réussie" } From 15d949b405d2f3eda926229403ae3912ccfaffba Mon Sep 17 00:00:00 2001 From: Karol Kosek Date: Thu, 13 Jun 2019 17:54:26 +0000 Subject: [PATCH 021/188] Translated using Weblate (Polish) Currently translated at 74.4% (1228 of 1650 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/pl/ --- src/i18n/strings/pl.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/pl.json b/src/i18n/strings/pl.json index 77a058a048..71cb0087a2 100644 --- a/src/i18n/strings/pl.json +++ b/src/i18n/strings/pl.json @@ -907,7 +907,7 @@ "Enable email notifications": "Włącz powiadomienia e-mailowe", "Event Type": "Typ wydarzenia", "Download this file": "Pobierz plik", - "Pin Message": "Przypnij Wiadomość", + "Pin Message": "Przypnij wiadomość", "Failed to change settings": "Nie udało się zmienić ustawień", "View Community": "Pokaż społeczność", "%(count)s Members|one": "%(count)s Członek", @@ -1461,5 +1461,9 @@ "Developer options": "Opcje programistyczne", "Change main address for the room": "Zmienianie głównego adresu pokoju", "Modify widgets": "Modyfikowanie widżetów", - "Invite users": "Zapraszanie użytkowników" + "Invite users": "Zapraszanie użytkowników", + "There was an error joining the room": "Wystąpił błąd dołączając do pokoju", + "Joining room …": "Dołączanie do pokoju…", + "edited": "edytowane", + "Edit message": "Edytuj wiadomość" } From a311374985fe9cc3fdba181d77dff2760698b788 Mon Sep 17 00:00:00 2001 From: Le Dang Trung Date: Fri, 14 Jun 2019 08:03:37 +0000 Subject: [PATCH 022/188] Translated using Weblate (Vietnamese) Currently translated at 0.4% (7 of 1650 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/vi/ --- src/i18n/strings/vi.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/vi.json b/src/i18n/strings/vi.json index 0967ef424b..d3cfea2f49 100644 --- a/src/i18n/strings/vi.json +++ b/src/i18n/strings/vi.json @@ -1 +1,9 @@ -{} +{ + "This email address is already in use": "Email này hiện đã được sử dụng", + "This phone number is already in use": "Số điện thoại này hiện đã được sử dụng", + "Failed to verify email address: make sure you clicked the link in the email": "Xác thực email thất bại: hãy đảm bảo bạn nhấp đúng đường dẫn đã gửi vào email", + "The platform you're on": "Nền tảng bạn đang tham gia", + "The version of Riot.im": "Phiên bản của Riot.im", + "Your language of choice": "Ngôn ngữ bạn chọn", + "Your homeserver's URL": "Đường dẫn Homeserver của bạn" +} From 6a10f0068dcc071cc200164448c711385f516a74 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 14 Jun 2019 12:26:52 +0100 Subject: [PATCH 023/188] Use Alt-UP/DOWN for Composer History instead of random room change Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/LoggedInView.js | 10 ---------- src/components/views/rooms/MessageComposerInput.js | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 6c05b45111..cd752fc2ce 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -292,16 +292,6 @@ const LoggedInView = React.createClass({ const ctrlCmdOnly = isOnlyCtrlOrCmdKeyEvent(ev); switch (ev.keyCode) { - case KeyCode.UP: - case KeyCode.DOWN: - if (ev.altKey && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) { - const action = ev.keyCode == KeyCode.UP ? - 'view_prev_room' : 'view_next_room'; - dis.dispatch({action: action}); - handled = true; - } - break; - case KeyCode.PAGE_UP: case KeyCode.PAGE_DOWN: if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 95b59e048d..09dd12e494 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -1241,7 +1241,7 @@ export default class MessageComposerInput extends React.Component { selectHistory = async (up) => { const delta = up ? -1 : 1; - // True if we are not currently selecting history, but composing a messag + // True if we are not currently selecting history, but composing a message if (this.historyManager.currentIndex === this.historyManager.history.length) { // We can't go any further - there isn't any more history, so nop. if (!up) { From 8fa50b26a614ba4bc548aea95592edf0325a1ee3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 14 Jun 2019 15:31:19 +0100 Subject: [PATCH 024/188] Fix welcome user https://github.com/matrix-org/matrix-react-sdk/pull/3101 meant we don't get logged straight in after registering if using an email address, but this was the point at which we made a chat with the welcome user. Instead, set a flag in memory that we should try & make a chat with the welcome user for that user ID if we get a session for them. Of course, if the user logs in on both tabs, this would mean each would make a chat with the welcome user (although actually this was a problem with the old code too). Check our m.direct to see if we've started a chat with the welcome user before making one (which also means we have to make sure the cached sync is up to date... see comments). --- src/MatrixClientPeg.js | 26 +++++++ src/components/structures/MatrixChat.js | 74 ++++++++++++++----- .../structures/auth/Registration.js | 3 + 3 files changed, 85 insertions(+), 18 deletions(-) diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index 574f05bf85..07499a3a87 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -51,6 +51,7 @@ interface MatrixClientCreds { class MatrixClientPeg { constructor() { this.matrixClient = null; + this._justRegisteredUserId = null; // These are the default options used when when the // client is started in 'start'. These can be altered @@ -85,6 +86,31 @@ class MatrixClientPeg { MatrixActionCreators.stop(); } + /* + * If we've registered a user ID we set this to the ID of the + * user we've just registered. If they then go & log in, we + * can send them to the welcome user (obviously this doesn't + * guarentee they'll get a chat with the welcome user). + * + * @param {string} uid The user ID of the user we've just registered + */ + setJustRegisteredUserId(uid) { + this._justRegisteredUserId = uid; + } + + /* + * Returns true if the current user has just been registered by this + * client as determined by setJustRegisteredUserId() + * + * @returns {bool} True if user has just been registered + */ + currentUserIsJustRegistered() { + return ( + this.matrixClient && + this.matrixClient.credentials.userId === this._justRegisteredUserId + ); + } + /** * Replace this MatrixClientPeg's client with a client instance that has * homeserver / identity server URLs and active credentials diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 15a244b50e..aa579ea8f9 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -53,6 +53,7 @@ import { messageForSyncError } from '../../utils/ErrorUtils'; import ResizeNotifier from "../../utils/ResizeNotifier"; import { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils"; import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils"; +import DMRoomMap from '../../utils/DMRoomMap'; // Disable warnings for now: we use deprecated bluebird functions // and need to migrate, but they spam the console with warnings. @@ -887,6 +888,7 @@ export default React.createClass({ } return; } + MatrixClientPeg.setJustRegisteredUserId(credentials.user_id); this.onRegistered(credentials); }, onDifferentServerClicked: (ev) => { @@ -1134,26 +1136,65 @@ export default React.createClass({ /** * Called when a new logged in session has started */ - _onLoggedIn: async function() { + _onLoggedIn: function() { this.setStateForNewView({ view: VIEWS.LOGGED_IN }); - if (this._is_registered) { - this._is_registered = false; + if (MatrixClientPeg.currentUserIsJustRegistered()) { + MatrixClientPeg.setJustRegisteredUserId(null); if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) { - const roomId = await createRoom({ - dmUserId: this.props.config.welcomeUserId, - // Only view the welcome user if we're NOT looking at a room - andView: !this.state.currentRoomId, - }); - // if successful, return because we're already - // viewing the welcomeUserId room - // else, if failed, fall through to view_home_page - if (roomId) { - return; + // We can end up with multiple tabs post-registration where the user + // might then end up with a session and we don't want them all making + // a chat with the welcome user: try to de-dupe. + // We need to wait for the first sync to complete for this to + // work though. + let waitFor; + if (!this.firstSyncComplete) { + waitFor = this.firstSyncPromise.promise; + } else { + waitFor = Promise.resolve(); } + waitFor.then(async () => { + const welcomeUserRooms = DMRoomMap.shared().getDMRoomsForUserId( + this.props.config.welcomeUserId, + ); + if (welcomeUserRooms.length === 0) { + const roomId = await createRoom({ + dmUserId: this.props.config.welcomeUserId, + // Only view the welcome user if we're NOT looking at a room + andView: !this.state.currentRoomId, + }); + // This is a bit of a hack, but since the deduplication relies + // on m.direct being up to date, we need to force a sync + // of the database, otherwise if the user goes to the other + // tab before the next save happens (a few minutes), the + // saved sync will be restored from the db and this code will + // run without the update to m.direct, making another welcome + // user room. + const saveWelcomeUser = (ev) => { + if ( + ev.getType() == 'm.direct' && + ev.getContent() && + ev.getContent()[this.props.config.welcomeUserId] + ) { + MatrixClientPeg.get().store.save(true); + MatrixClientPeg.get().removeListener( + "accountData", saveWelcomeUser, + ); + } + }; + MatrixClientPeg.get().on("accountData", saveWelcomeUser); + + // if successful, return because we're already + // viewing the welcomeUserId room + // else, if failed, fall through to view_home_page + if (roomId) { + return; + } + } + // The user has just logged in after registering + dis.dispatch({action: 'view_home_page'}); + }); } - // The user has just logged in after registering - dis.dispatch({action: 'view_home_page'}); } else { this._showScreenAfterLogin(); } @@ -1694,9 +1735,6 @@ export default React.createClass({ return MatrixClientPeg.get(); } } - // XXX: This should be in state or ideally store(s) because we risk not - // rendering the most up-to-date view of state otherwise. - this._is_registered = true; return Lifecycle.setLoggedIn(credentials); }, diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 3103ee41df..1957275505 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -29,6 +29,7 @@ import * as ServerType from '../../views/auth/ServerTypeSelector'; import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; import classNames from "classnames"; import * as Lifecycle from '../../../Lifecycle'; +import MatrixClientPeg from "../../../MatrixClientPeg"; // Phases // Show controls to configure server details @@ -287,6 +288,8 @@ module.exports = React.createClass({ return; } + MatrixClientPeg.setJustRegisteredUserId(response.user_id); + const newState = { doingUIAuth: false, }; From 929e9dc6533a8513f218060fdd4440a4c4d205a6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 14 Jun 2019 16:04:09 +0100 Subject: [PATCH 025/188] Don't forget to show the homepage if no welcome user --- src/components/structures/MatrixChat.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index aa579ea8f9..0b15b727ec 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1191,9 +1191,14 @@ export default React.createClass({ return; } } - // The user has just logged in after registering + // We didn't rediret to the welcome user room, so show + // the homepage. dis.dispatch({action: 'view_home_page'}); }); + } else { + // The user has just logged in after registering, + // so show the homepage. + dis.dispatch({action: 'view_home_page'}); } } else { this._showScreenAfterLogin(); From 30726d6cf95be8a1b7e913102665a5fdc3bb6fd6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 14 Jun 2019 16:29:26 +0100 Subject: [PATCH 026/188] Pull out welcome user chat code to a separate function also expand on comment --- src/components/structures/MatrixChat.js | 111 +++++++++++++----------- 1 file changed, 59 insertions(+), 52 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 0b15b727ec..4fc64b91bb 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1133,68 +1133,75 @@ export default React.createClass({ } }, + /** + * Starts a chat with the welcome user, if the user doesn't already have one + * @returns {string} The room ID of the new room, or null if no room was created + */ + async _startWelcomeUserChat() { + // We can end up with multiple tabs post-registration where the user + // might then end up with a session and we don't want them all making + // a chat with the welcome user: try to de-dupe. + // We need to wait for the first sync to complete for this to + // work though. + let waitFor; + if (!this.firstSyncComplete) { + waitFor = this.firstSyncPromise.promise; + } else { + waitFor = Promise.resolve(); + } + await waitFor; + + const welcomeUserRooms = DMRoomMap.shared().getDMRoomsForUserId( + this.props.config.welcomeUserId, + ); + if (welcomeUserRooms.length === 0) { + const roomId = await createRoom({ + dmUserId: this.props.config.welcomeUserId, + // Only view the welcome user if we're NOT looking at a room + andView: !this.state.currentRoomId, + }); + // This is a bit of a hack, but since the deduplication relies + // on m.direct being up to date, we need to force a sync + // of the database, otherwise if the user goes to the other + // tab before the next save happens (a few minutes), the + // saved sync will be restored from the db and this code will + // run without the update to m.direct, making another welcome + // user room (it doesn't wait for new data from the server, just + // the saved sync to be loaded). + const saveWelcomeUser = (ev) => { + if ( + ev.getType() == 'm.direct' && + ev.getContent() && + ev.getContent()[this.props.config.welcomeUserId] + ) { + MatrixClientPeg.get().store.save(true); + MatrixClientPeg.get().removeListener( + "accountData", saveWelcomeUser, + ); + } + }; + MatrixClientPeg.get().on("accountData", saveWelcomeUser); + + return roomId; + } + return null; + }, + /** * Called when a new logged in session has started */ - _onLoggedIn: function() { + _onLoggedIn: async function() { this.setStateForNewView({ view: VIEWS.LOGGED_IN }); - if (MatrixClientPeg.currentUserIsJustRegistered()) { + if (true || MatrixClientPeg.currentUserIsJustRegistered()) { MatrixClientPeg.setJustRegisteredUserId(null); if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) { - // We can end up with multiple tabs post-registration where the user - // might then end up with a session and we don't want them all making - // a chat with the welcome user: try to de-dupe. - // We need to wait for the first sync to complete for this to - // work though. - let waitFor; - if (!this.firstSyncComplete) { - waitFor = this.firstSyncPromise.promise; - } else { - waitFor = Promise.resolve(); - } - waitFor.then(async () => { - const welcomeUserRooms = DMRoomMap.shared().getDMRoomsForUserId( - this.props.config.welcomeUserId, - ); - if (welcomeUserRooms.length === 0) { - const roomId = await createRoom({ - dmUserId: this.props.config.welcomeUserId, - // Only view the welcome user if we're NOT looking at a room - andView: !this.state.currentRoomId, - }); - // This is a bit of a hack, but since the deduplication relies - // on m.direct being up to date, we need to force a sync - // of the database, otherwise if the user goes to the other - // tab before the next save happens (a few minutes), the - // saved sync will be restored from the db and this code will - // run without the update to m.direct, making another welcome - // user room. - const saveWelcomeUser = (ev) => { - if ( - ev.getType() == 'm.direct' && - ev.getContent() && - ev.getContent()[this.props.config.welcomeUserId] - ) { - MatrixClientPeg.get().store.save(true); - MatrixClientPeg.get().removeListener( - "accountData", saveWelcomeUser, - ); - } - }; - MatrixClientPeg.get().on("accountData", saveWelcomeUser); - - // if successful, return because we're already - // viewing the welcomeUserId room - // else, if failed, fall through to view_home_page - if (roomId) { - return; - } - } + const welcomeUserRoom = await this._startWelcomeUserChat(); + if (welcomeUserRoom === null) { // We didn't rediret to the welcome user room, so show // the homepage. dis.dispatch({action: 'view_home_page'}); - }); + } } else { // The user has just logged in after registering, // so show the homepage. From 794b04b89a0eb79c3f61626506374935729cd21d Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 14 Jun 2019 16:41:30 +0100 Subject: [PATCH 027/188] take the debugging out --- src/components/structures/MatrixChat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 4fc64b91bb..789649c220 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1192,7 +1192,7 @@ export default React.createClass({ */ _onLoggedIn: async function() { this.setStateForNewView({ view: VIEWS.LOGGED_IN }); - if (true || MatrixClientPeg.currentUserIsJustRegistered()) { + if (MatrixClientPeg.currentUserIsJustRegistered()) { MatrixClientPeg.setJustRegisteredUserId(null); if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) { From df7a0c6d45d12cb501f606ad025170eddc74aa36 Mon Sep 17 00:00:00 2001 From: Nathan Follens Date: Fri, 14 Jun 2019 09:30:53 +0000 Subject: [PATCH 028/188] Translated using Weblate (Dutch) Currently translated at 100.0% (1650 of 1650 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/nl/ --- src/i18n/strings/nl.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index bcc4cbe75e..e982fffca8 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -1898,5 +1898,12 @@ "Your Riot is misconfigured": "Uw Riot is verkeerd geconfigureerd", "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Vraag uw Riot-beheerder om uw configuratie na te kijken op onjuiste of duplicate items.", "Unexpected error resolving identity server configuration": "Onverwachte fout bij het oplossen van de identiteitsserverconfiguratie", - "Use lowercase letters, numbers, dashes and underscores only": "Gebruik enkel letters, cijfers, streepjes en underscores" + "Use lowercase letters, numbers, dashes and underscores only": "Gebruik enkel letters, cijfers, streepjes en underscores", + "Cannot reach identity server": "Kan identiteitsserver niet bereiken", + "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "U kunt zich registreren, maar sommige functies zullen pas beschikbaar zijn wanneer de identiteitsserver weer online is. Als u deze waarschuwing blijft zien, controleer dan uw configuratie of neem contact op met een serverbeheerder.", + "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "U kunt uw wachtwoord opnieuw instellen, maar sommige functies zullen pas beschikbaar zijn wanneer de identiteitsserver weer online is. Als u deze waarschuwing blijft zien, controleer dan uw configuratie of neem contact op met een serverbeheerder.", + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "U kunt zich aanmelden, maar sommige functies zullen pas beschikbaar zijn wanneer de identiteitsserver weer online is. Als u deze waarschuwing blijft zien, controleer dan uw configuratie of neem contact op met een systeembeheerder.", + "Log in to your new account.": "Meld u aan met uw nieuwe account.", + "You can now close this window or log in to your new account.": "U kunt dit venster nu sluiten, of u aanmelden met uw nieuwe account.", + "Registration Successful": "Registratie geslaagd" } From ad1cd085d5ea5a9bf9ac4e2927ea910d4278e403 Mon Sep 17 00:00:00 2001 From: Nathan Follens Date: Fri, 14 Jun 2019 09:31:13 +0000 Subject: [PATCH 029/188] Translated using Weblate (West Flemish) Currently translated at 100.0% (1650 of 1650 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/vls/ --- src/i18n/strings/vls.json | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/i18n/strings/vls.json b/src/i18n/strings/vls.json index b06f5b9fdd..d208812832 100644 --- a/src/i18n/strings/vls.json +++ b/src/i18n/strings/vls.json @@ -166,7 +166,7 @@ "You are no longer ignoring %(userId)s": "Je negeert %(userId)s nie mi", "Define the power level of a user": "Bepoal ’t machtsniveau van e gebruuker", "Deops user with given id": "Ountmachtigt de gebruuker me de gegeevn ID", - "Opens the Developer Tools dialog": "Opent ’t dialoogvenster me ’t ountwikkeloarsgereedschap", + "Opens the Developer Tools dialog": "Opent de dialoogveinster me ’t ountwikkeloarsgereedschap", "Adds a custom widget by URL to the room": "Voegt met een URL een angepaste widget toe an ’t gesprek", "Please supply a https:// or http:// widget URL": "Gift een https://- of http://-widget-URL in", "You cannot modify widgets in this room.": "J’en kut de widgets in dit gesprek nie anpassn.", @@ -577,7 +577,7 @@ "Labs": "Experimenteel", "Notifications": "Meldiengn", "Start automatically after system login": "Automatisch startn achter systeemanmeldienge", - "Close button should minimize window to tray": "Venster minimaliseern noa ’t systeemvak by ’t sluutn", + "Close button should minimize window to tray": "Veinster minimaliseern noa ’t systeemvak by ’t sluutn", "Preferences": "Instelliengn", "Composer": "Ipsteller", "Timeline": "Tydslyn", @@ -622,7 +622,7 @@ "Developer options": "Ontwikkeloarsopties", "Open Devtools": "Ontwikkelgereedschap openn", "Room Addresses": "Gespreksadressn", - "Publish this room to the public in %(domain)s's room directory?": "Dit gesprek openboar moakn in de gesprekscatalogus van %(domain)s?", + "Publish this room to the public in %(domain)s's room directory?": "Dit gesprek openboar moakn in de gesprekscataloog van %(domain)s?", "URL Previews": "URL-voorvertoniengn", "Change room avatar": "Gespreksavatar wyzign", "Change room name": "Gespreksnoame wyzign", @@ -984,7 +984,7 @@ "Minimize apps": "Apps minimaliseern", "Maximize apps": "Apps maximaliseern", "Reload widget": "Widget herloadn", - "Popout widget": "Widget in nieuw venster openn", + "Popout widget": "Widget in e nieuwe veinster openn", "Picture": "Fotootje", "Revoke widget access": "Widget-toegank intrekkn", "Create new room": "E nieuw gesprek anmoakn", @@ -1192,7 +1192,7 @@ "Refresh": "Herloadn", "Unable to restore session": "’t En is nie meuglik van de sessie t’herstelln", "We encountered an error trying to restore your previous session.": "’t Is e foute ipgetreedn by ’t herstelln van je vorige sessie.", - "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.": "A j’al e ki gebruuk gemakt èt van e recentere versie van Riot, is je sessie meugliks ounverenigboar me deze versie. Sluut dit venster en goa were noa de recentere versie.", + "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.": "A j’al e ki gebruuk gemakt èt van e recentere versie van Riot, is je sessie meugliks ounverenigboar me deze versie. Sluut deze veinster en goa were noa de recentere versie.", "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "’t Legen van den ipslag van je browser goa ’t probleem misschiens verhelpn, mo goa joun ook afmeldn en gans je versleuterde gespreksgeschiedenisse ounleesboar moakn.", "Verification Pending": "Verificoatie in afwachtienge", "Please check your email and click on the link it contains. Once this is done, click continue.": "Bekyk jen e-mails en klikt ip de koppelienge derin. Klikt van zodra da je da gedoan èt ip ‘Verdergoan’.", @@ -1308,7 +1308,7 @@ "This homeserver would like to make sure you are not a robot.": "Deze thuusserver wil geirn weetn of da je gy geen robot zyt.", "Custom Server Options": "Angepaste serverinstelliengn", "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.": "Je kut de angepaste serveropties gebruukn vo jen an te meldn by andere Matrix-servers, deur een andere thuusserver-URL in te geevn. Dit biedt je de meuglikheid vo deze toepassienge te gebruukn met een bestoande Matrix-account ip een andere thuusserver.", - "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.": "Je kut ook een angepaste identiteitsserver instelln, ma je goa geen gebruukers kunn uutnodign via e-mail, of zelve via e-mail uutgenodigd wirden.", + "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.": "Je kut ook een angepasten identiteitsserver instelln, ma je goa geen gebruukers kunn uutnodign via e-mail, of zelve via e-mail uutgenodigd wirden.", "To continue, please enter your password.": "Gif je paswoord in vo verder te goan.", "Password:": "Paswoord:", "Please review and accept all of the homeserver's policies": "Gelieve ’t beleid van de thuusserver te leezn en ’anveirdn", @@ -1646,5 +1646,12 @@ "Your Riot is misconfigured": "Je Riot is verkeerd geconfigureerd gewist", "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Vroagt an je Riot-beheerder van je configuroatie noa te kykn ip verkeerde of duplicoate items.", "Unexpected error resolving identity server configuration": "Ounverwachte foute by ’t iplossn van d’identiteitsserverconfiguroatie", - "Use lowercase letters, numbers, dashes and underscores only": "Gebruukt alleene moa letters, cyfers, streeptjes en underscores" + "Use lowercase letters, numbers, dashes and underscores only": "Gebruukt alleene moa letters, cyfers, streeptjes en underscores", + "Cannot reach identity server": "Kostege den identiteitsserver nie bereikn", + "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Je ku je registreern, moa sommige functies goan pas beschikboar zyn wanneer da den identiteitsserver were online is. A je deze woarschuwienge te zien bluft krygn, controleert tan je configuroatie of nimt contact ip met e serverbeheerder.", + "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Je ku je paswoord herinstelln, moa sommige functies goan pas beschikboar zyn wanneer da den identiteitsserver were online is. A je deze woarschuwienge te zien bluft krygn, controleert tan je configuroatie of nimt contact ip met e serverbeheerder.", + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Je ku jen anmeldn, moa sommige functies goan pas beschikboar zyn wanneer da den identiteitsserver were online is. A je deze woarschuwienge te zien bluft krygn, controleert tan je configuroatie of nimt contact ip met e serverbeheerder.", + "Log in to your new account.": "Meldt jen eigen an me je nieuwen account.", + "You can now close this window or log in to your new account.": "Je kut deze veinster nu sluutn, of jen eigen anmeldn me je nieuwen account.", + "Registration Successful": "Registroatie gesloagd" } From 4c036c98ee9bdff4f4f8a75aa1cd614aefb8ca2e Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 14 Jun 2019 17:21:07 +0100 Subject: [PATCH 030/188] Fix double-spinner On registering, we showed a spinner, and then another spinner on top of the spinner, which led to an interesting spinner-in-box effect. Suppress the second type of spinner when we know we already have one. --- src/components/structures/MatrixChat.js | 1 + src/createRoom.js | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 789649c220..fb35ab548b 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1159,6 +1159,7 @@ export default React.createClass({ dmUserId: this.props.config.welcomeUserId, // Only view the welcome user if we're NOT looking at a room andView: !this.state.currentRoomId, + spinner: false, // we're already showing one: we don't need another one }); // This is a bit of a hack, but since the deduplication relies // on m.direct being up to date, we need to force a sync diff --git a/src/createRoom.js b/src/createRoom.js index 39b634a0ef..120043247d 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -30,12 +30,15 @@ import {getAddressType} from "./UserAddress"; * @param {object=} opts parameters for creating the room * @param {string=} opts.dmUserId If specified, make this a DM room for this user and invite them * @param {object=} opts.createOpts set of options to pass to createRoom call. + * @param {bool=} opts.spinner True to show a modal spinner while the room is created. + * Default: True * * @returns {Promise} which resolves to the room id, or null if the * action was aborted or failed. */ function createRoom(opts) { opts = opts || {}; + if (opts.spinner === undefined) opts.spinner = true; const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const Loader = sdk.getComponent("elements.Spinner"); @@ -87,11 +90,12 @@ function createRoom(opts) { }, ]; - const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner'); + let modal; + if (opts.spinner) modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner'); let roomId; return client.createRoom(createOpts).finally(function() { - modal.close(); + if (modal) modal.close(); }).then(function(res) { roomId = res.room_id; if (opts.dmUserId) { From eb4ff50c3cfa39463e3318167e12ac308d3956a6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Jun 2019 12:16:34 +0200 Subject: [PATCH 031/188] do parts creation only in PartCreator to not scatter dependencies --- .../views/elements/MessageEditor.js | 2 +- src/editor/autocomplete.js | 26 +++---- src/editor/deserialize.js | 71 +++++++++--------- src/editor/parts.js | 74 +++++++++++-------- 4 files changed, 89 insertions(+), 84 deletions(-) diff --git a/src/components/views/elements/MessageEditor.js b/src/components/views/elements/MessageEditor.js index 9faae4588b..9f5265cfd3 100644 --- a/src/components/views/elements/MessageEditor.js +++ b/src/components/views/elements/MessageEditor.js @@ -233,7 +233,7 @@ export default class MessageEditor extends React.Component { parts = editState.getSerializedParts().map(p => partCreator.deserializePart(p)); } else { // otherwise, parse the body of the event - parts = parseEvent(editState.getEvent(), room, this.context.matrixClient); + parts = parseEvent(editState.getEvent(), partCreator); } return new EditorModel( diff --git a/src/editor/autocomplete.js b/src/editor/autocomplete.js index 6cb5974729..4d35904976 100644 --- a/src/editor/autocomplete.js +++ b/src/editor/autocomplete.js @@ -15,22 +15,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {UserPillPart, RoomPillPart, PlainPart} from "./parts"; - export default class AutocompleteWrapperModel { - constructor(updateCallback, getAutocompleterComponent, updateQuery, room, client) { + constructor(updateCallback, getAutocompleterComponent, updateQuery, partCreator) { this._updateCallback = updateCallback; this._getAutocompleterComponent = getAutocompleterComponent; this._updateQuery = updateQuery; + this._partCreator = partCreator; this._query = null; - this._room = room; - this._client = client; } onEscape(e) { this._getAutocompleterComponent().onEscape(e); this._updateCallback({ - replacePart: new PlainPart(this._queryPart.text), + replacePart: this._partCreator.plain(this._queryPart.text), caretOffset: this._queryOffset, close: true, }); @@ -93,21 +90,18 @@ export default class AutocompleteWrapperModel { } _partForCompletion(completion) { - const firstChr = completion.completionId && completion.completionId[0]; + const {completionId} = completion; + const text = completion.completion; + const firstChr = completionId && completionId[0]; switch (firstChr) { case "@": { - const displayName = completion.completion; - const userId = completion.completionId; - const member = this._room.getMember(userId); - return new UserPillPart(userId, displayName, member); - } - case "#": { - const displayAlias = completion.completionId; - return new RoomPillPart(displayAlias, this._client); + return this._partCreator.userPill(text, completionId); } + case "#": + return this._partCreator.roomPill(completionId); // also used for emoji completion default: - return new PlainPart(completion.completion); + return this._partCreator.plain(text); } } } diff --git a/src/editor/deserialize.js b/src/editor/deserialize.js index 2d98bbc41a..e08c1d59d9 100644 --- a/src/editor/deserialize.js +++ b/src/editor/deserialize.js @@ -16,73 +16,68 @@ limitations under the License. */ import { MATRIXTO_URL_PATTERN } from '../linkify-matrix'; -import { PlainPart, UserPillPart, RoomPillPart, NewlinePart } from "./parts"; import { walkDOMDepthFirst } from "./dom"; const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN); -function parseLink(a, room, client) { +function parseLink(a, partCreator) { const {href} = a; const pillMatch = REGEX_MATRIXTO.exec(href) || []; const resourceId = pillMatch[1]; // The room/user ID const prefix = pillMatch[2]; // The first character of prefix switch (prefix) { case "@": - return new UserPillPart( - resourceId, - a.textContent, - room.getMember(resourceId), - ); + return partCreator.userPill(a.textContent, resourceId); case "#": - return new RoomPillPart(resourceId, client); + return partCreator.roomPill(resourceId); default: { if (href === a.textContent) { - return new PlainPart(a.textContent); + return partCreator.plain(a.textContent); } else { - return new PlainPart(`[${a.textContent}](${href})`); + return partCreator.plain(`[${a.textContent}](${href})`); } } } } -function parseCodeBlock(n) { +function parseCodeBlock(n, partCreator) { const parts = []; const preLines = ("```\n" + n.textContent + "```").split("\n"); preLines.forEach((l, i) => { - parts.push(new PlainPart(l)); + parts.push(partCreator.plain(l)); if (i < preLines.length - 1) { - parts.push(new NewlinePart("\n")); + parts.push(partCreator.newline()); } }); return parts; } -function parseElement(n, room, client) { +function parseElement(n, partCreator) { switch (n.nodeName) { case "A": - return parseLink(n, room, client); + return parseLink(n, partCreator); case "BR": - return new NewlinePart("\n"); + return partCreator.newline(); case "EM": - return new PlainPart(`*${n.textContent}*`); + return partCreator.plain(`*${n.textContent}*`); case "STRONG": - return new PlainPart(`**${n.textContent}**`); + return partCreator.plain(`**${n.textContent}**`); case "PRE": - return parseCodeBlock(n); + return parseCodeBlock(n, partCreator); case "CODE": - return new PlainPart(`\`${n.textContent}\``); + return partCreator.plain(`\`${n.textContent}\``); case "DEL": - return new PlainPart(`${n.textContent}`); + return partCreator.plain(`${n.textContent}`); case "LI": if (n.parentElement.nodeName === "OL") { - return new PlainPart(` 1. `); + return partCreator.plain(` 1. `); } else { - return new PlainPart(` - `); + return partCreator.plain(` - `); } default: // don't textify block nodes we'll decend into if (!checkDecendInto(n)) { - return new PlainPart(n.textContent); + return partCreator.plain(n.textContent); } } } @@ -125,22 +120,22 @@ function checkIgnored(n) { return true; } -function prefixQuoteLines(isFirstNode, parts) { +function prefixQuoteLines(isFirstNode, parts, partCreator) { const PREFIX = "> "; // a newline (to append a > to) wouldn't be added to parts for the first line // if there was no content before the BLOCKQUOTE, so handle that if (isFirstNode) { - parts.splice(0, 0, new PlainPart(PREFIX)); + parts.splice(0, 0, partCreator.plain(PREFIX)); } for (let i = 0; i < parts.length; i += 1) { if (parts[i].type === "newline") { - parts.splice(i + 1, 0, new PlainPart(PREFIX)); + parts.splice(i + 1, 0, partCreator.plain(PREFIX)); i += 1; } } } -function parseHtmlMessage(html, room, client) { +function parseHtmlMessage(html, partCreator) { // no nodes from parsing here should be inserted in the document, // as scripts in event handlers, etc would be executed then. // we're only taking text, so that is fine @@ -159,13 +154,13 @@ function parseHtmlMessage(html, room, client) { const newParts = []; if (lastNode && (checkBlockNode(lastNode) || checkBlockNode(n))) { - newParts.push(new NewlinePart("\n")); + newParts.push(partCreator.newline()); } if (n.nodeType === Node.TEXT_NODE) { - newParts.push(new PlainPart(n.nodeValue)); + newParts.push(partCreator.plain(n.nodeValue)); } else if (n.nodeType === Node.ELEMENT_NODE) { - const parseResult = parseElement(n, room, client); + const parseResult = parseElement(n, partCreator); if (parseResult) { if (Array.isArray(parseResult)) { newParts.push(...parseResult); @@ -177,14 +172,14 @@ function parseHtmlMessage(html, room, client) { if (newParts.length && inQuote) { const isFirstPart = parts.length === 0; - prefixQuoteLines(isFirstPart, newParts); + prefixQuoteLines(isFirstPart, newParts, partCreator); } parts.push(...newParts); // extra newline after quote, only if there something behind it... if (lastNode && lastNode.nodeName === "BLOCKQUOTE") { - parts.push(new NewlinePart("\n")); + parts.push(partCreator.newline()); } lastNode = null; return checkDecendInto(n); @@ -205,18 +200,18 @@ function parseHtmlMessage(html, room, client) { return parts; } -export function parseEvent(event, room, client) { +export function parseEvent(event, partCreator) { const content = event.getContent(); let parts; if (content.format === "org.matrix.custom.html") { - parts = parseHtmlMessage(content.formatted_body || "", room, client); + parts = parseHtmlMessage(content.formatted_body || "", partCreator); } else { const body = content.body || ""; const lines = body.split("\n"); parts = lines.reduce((parts, line, i) => { const isLast = i === lines.length - 1; - const text = new PlainPart(line); - const newLine = !isLast && new NewlinePart("\n"); + const text = partCreator.plain(line); + const newLine = !isLast && partCreator.newline(); if (newLine) { return parts.concat(text, newLine); } else { @@ -225,7 +220,7 @@ export function parseEvent(event, room, client) { }, []); } if (content.msgtype === "m.emote") { - parts.unshift(new PlainPart("/me ")); + parts.unshift(partCreator.plain("/me ")); } return parts; } diff --git a/src/editor/parts.js b/src/editor/parts.js index a122c7ab7a..7305fb1232 100644 --- a/src/editor/parts.js +++ b/src/editor/parts.js @@ -107,7 +107,7 @@ class BasePart { } } -export class PlainPart extends BasePart { +class PlainPart extends BasePart { acceptsInsertion(chr) { return chr !== "@" && chr !== "#" && chr !== ":" && chr !== "\n"; } @@ -199,7 +199,7 @@ class PillPart extends BasePart { } } -export class NewlinePart extends BasePart { +class NewlinePart extends BasePart { acceptsInsertion(chr, i) { return (this.text.length + i) === 0 && chr === "\n"; } @@ -235,20 +235,10 @@ export class NewlinePart extends BasePart { } } -export class RoomPillPart extends PillPart { - constructor(displayAlias, client) { +class RoomPillPart extends PillPart { + constructor(displayAlias, room) { super(displayAlias, displayAlias); - this._room = this._findRoomByAlias(displayAlias, client); - } - - _findRoomByAlias(alias, client) { - if (alias[0] === '#') { - return client.getRooms().find((r) => { - return r.getAliases().includes(alias); - }); - } else { - return client.getRoom(alias); - } + this._room = room; } setAvatar(node) { @@ -270,7 +260,7 @@ export class RoomPillPart extends PillPart { } } -export class UserPillPart extends PillPart { +class UserPillPart extends PillPart { constructor(userId, displayName, member) { super(userId, displayName); this._member = member; @@ -311,7 +301,7 @@ export class UserPillPart extends PillPart { } -export class PillCandidatePart extends PlainPart { +class PillCandidatePart extends PlainPart { constructor(text, autoCompleteCreator) { super(text); this._autoCompleteCreator = autoCompleteCreator; @@ -351,8 +341,7 @@ export class PartCreator { updateCallback, getAutocompleterComponent, updateQuery, - room, - client, + this, ); }; } @@ -362,7 +351,7 @@ export class PartCreator { case "#": case "@": case ":": - return new PillCandidatePart("", this._autoCompleteCreator); + return this.pillCandidate(""); case "\n": return new NewlinePart(); default: @@ -371,24 +360,51 @@ export class PartCreator { } createDefaultPart(text) { - return new PlainPart(text); + return this.plain(text); } deserializePart(part) { switch (part.type) { case "plain": - return new PlainPart(part.text); + return this.plain(part.text); case "newline": - return new NewlinePart(part.text); + return this.newline(); case "pill-candidate": - return new PillCandidatePart(part.text, this._autoCompleteCreator); + return this.pillCandidate(part.text); case "room-pill": - return new RoomPillPart(part.text, this._client); - case "user-pill": { - const member = this._room.getMember(part.userId); - return new UserPillPart(part.userId, part.text, member); - } + return this.roomPill(part.text); + case "user-pill": + return this.userPill(part.text, part.userId); } } + + plain(text) { + return new PlainPart(text); + } + + newline() { + return new NewlinePart("\n"); + } + + pillCandidate(text) { + return new PillCandidatePart(text, this._autoCompleteCreator); + } + + roomPill(alias) { + let room; + if (alias[0] === '#') { + room = this._client.getRooms().find((r) => { + return r.getAliases().includes(alias); + }); + } else { + room = this._client.getRoom(alias); + } + return new RoomPillPart(alias, room); + } + + userPill(displayName, userId) { + const member = this._room.getMember(userId); + return new UserPillPart(userId, displayName, member); + } } From dfec5058c58ff7c4cfcd959eab9946b29f6cdef8 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Jun 2019 18:24:18 +0200 Subject: [PATCH 032/188] support creating @room pills in partcreator --- src/editor/parts.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/editor/parts.js b/src/editor/parts.js index 7305fb1232..d13a94253f 100644 --- a/src/editor/parts.js +++ b/src/editor/parts.js @@ -260,6 +260,13 @@ class RoomPillPart extends PillPart { } } +class AtRoomPillPart extends RoomPillPart { + get type() { + return "at-room-pill"; + } +} + + class UserPillPart extends PillPart { constructor(userId, displayName, member) { super(userId, displayName); @@ -402,6 +409,10 @@ export class PartCreator { return new RoomPillPart(alias, room); } + atRoomPill(text) { + return new AtRoomPillPart(text, this._room); + } + userPill(displayName, userId) { const member = this._room.getMember(userId); return new UserPillPart(userId, displayName, member); From 63b11f50015cc327dd735230e3a269dd2e363d67 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Jun 2019 18:24:36 +0200 Subject: [PATCH 033/188] (de)serialize at-room-pills just like pill-candidate (no html needed) --- src/editor/serialize.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/editor/serialize.js b/src/editor/serialize.js index 73fbbe5d01..876130074c 100644 --- a/src/editor/serialize.js +++ b/src/editor/serialize.js @@ -24,6 +24,7 @@ export function mdSerialize(model) { return html + "\n"; case "plain": case "pill-candidate": + case "at-room-pill": return html + part.text; case "room-pill": case "user-pill": @@ -47,6 +48,7 @@ export function textSerialize(model) { return text + "\n"; case "plain": case "pill-candidate": + case "at-room-pill": return text + part.text; case "room-pill": case "user-pill": @@ -58,13 +60,11 @@ export function textSerialize(model) { export function requiresHtml(model) { return model.parts.some(part => { switch (part.type) { - case "newline": - case "plain": - case "pill-candidate": - return false; case "room-pill": case "user-pill": return true; + default: + return false; } }); } From 65d56d1490e3adc0f93d1fb0eb0d74f84a6e20b9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Jun 2019 18:25:02 +0200 Subject: [PATCH 034/188] transform @room to AtRoomPill while deserializing html to md --- src/editor/deserialize.js | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/editor/deserialize.js b/src/editor/deserialize.js index e08c1d59d9..200f3eb885 100644 --- a/src/editor/deserialize.js +++ b/src/editor/deserialize.js @@ -20,6 +20,21 @@ import { walkDOMDepthFirst } from "./dom"; const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN); +function parseAtRoomMentions(text, partCreator) { + const ATROOM = "@room"; + const parts = []; + text.split(ATROOM).forEach((textPart, i, arr) => { + if (textPart.length) { + parts.push(partCreator.plain(textPart)); + } + const isLast = i === arr.length - 1; + if (!isLast) { + parts.push(partCreator.atRoomPill(ATROOM)); + } + }); + return parts; +} + function parseLink(a, partCreator) { const {href} = a; const pillMatch = REGEX_MATRIXTO.exec(href) || []; @@ -158,7 +173,7 @@ function parseHtmlMessage(html, partCreator) { } if (n.nodeType === Node.TEXT_NODE) { - newParts.push(partCreator.plain(n.nodeValue)); + newParts.push(...parseAtRoomMentions(n.nodeValue, partCreator)); } else if (n.nodeType === Node.ELEMENT_NODE) { const parseResult = parseElement(n, partCreator); if (parseResult) { @@ -210,13 +225,11 @@ export function parseEvent(event, partCreator) { const lines = body.split("\n"); parts = lines.reduce((parts, line, i) => { const isLast = i === lines.length - 1; - const text = partCreator.plain(line); - const newLine = !isLast && partCreator.newline(); - if (newLine) { - return parts.concat(text, newLine); - } else { - return parts.concat(text); + const newParts = parseAtRoomMentions(line, partCreator); + if (!isLast) { + newParts.push(partCreator.newline()); } + return parts.concat(newParts); }, []); } if (content.msgtype === "m.emote") { From 78971f168f6212853956b0e8f0db3135ca4bef90 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Jun 2019 18:25:43 +0200 Subject: [PATCH 035/188] create AtRoomPill when autocomplete returns @room --- src/editor/autocomplete.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/editor/autocomplete.js b/src/editor/autocomplete.js index 4d35904976..2aedf8d7f5 100644 --- a/src/editor/autocomplete.js +++ b/src/editor/autocomplete.js @@ -95,7 +95,11 @@ export default class AutocompleteWrapperModel { const firstChr = completionId && completionId[0]; switch (firstChr) { case "@": { - return this._partCreator.userPill(text, completionId); + if (completionId === "@room") { + return this._partCreator.atRoomPill(completionId); + } else { + return this._partCreator.userPill(text, completionId); + } } case "#": return this._partCreator.roomPill(completionId); From 497ba1ecd443619246c2764ba9bd828a4e62ac8d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Jun 2019 18:26:01 +0200 Subject: [PATCH 036/188] prevent @room pills being applied multiple times when rerendering --- src/components/views/messages/TextualBody.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 6f480b8d3c..d76956d193 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -214,7 +214,13 @@ module.exports = React.createClass({ // update the current node with one that's now taken its place node = pillContainer; } - } else if (node.nodeType === Node.TEXT_NODE) { + } else if ( + node.nodeType === Node.TEXT_NODE && + // as applying pills happens outside of react, make sure we're not doubly + // applying @room pills here, as a rerender with the same content won't touch the DOM + // to clear the pills from the last run of pillifyLinks + !node.parentElement.classList.contains("mx_AtRoomPill") + ) { const Pill = sdk.getComponent('elements.Pill'); let currentTextNode = node; From d9e62b54fce0c7f2ef8c1ca264dc1c0ff236451d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 14 Jun 2019 18:42:30 +0200 Subject: [PATCH 037/188] also support deserializing at-room-pill when transferring editor state --- src/editor/parts.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/editor/parts.js b/src/editor/parts.js index d13a94253f..67d36e6660 100644 --- a/src/editor/parts.js +++ b/src/editor/parts.js @@ -376,6 +376,8 @@ export class PartCreator { return this.plain(part.text); case "newline": return this.newline(); + case "at-room-pill": + return this.atRoomPill(part.text); case "pill-candidate": return this.pillCandidate(part.text); case "room-pill": From 03c37821f6784b350a1160db2d15bb2c192b3d81 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sat, 15 Jun 2019 15:10:34 +0100 Subject: [PATCH 038/188] clean up onVerticalArrow Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../views/rooms/MessageComposerInput.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 09dd12e494..7684e1dced 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -1206,15 +1206,22 @@ export default class MessageComposerInput extends React.Component { onVerticalArrow = (e, up) => { if (e.ctrlKey || e.shiftKey || e.metaKey) return; - // Select history - const selection = this.state.editorState.selection; + if (e.altKey) { + // Try select composer history + const selected = this.selectHistory(up); + if (selected) { + // We're selecting history, so prevent the key event from doing anything else + e.preventDefault(); + } + } else if (!e.altKey && up) { + // Try edit the latest message + const selection = this.state.editorState.selection; - // selection must be collapsed - if (!selection.isCollapsed) return; - const document = this.state.editorState.document; + // selection must be collapsed + if (!selection.isCollapsed) return; + const document = this.state.editorState.document; - // and we must be at the edge of the document (up=start, down=end) - if (up) { + // and we must be at the edge of the document (up=start, down=end) if (!selection.anchor.isAtStartOfNode(document)) return; if (!e.altKey) { @@ -1227,15 +1234,8 @@ export default class MessageComposerInput extends React.Component { event: editEvent, }); } - return; } } - - const selected = this.selectHistory(up); - if (selected) { - // We're selecting history, so prevent the key event from doing anything else - e.preventDefault(); - } }; selectHistory = async (up) => { From fbe8d1c89b2968d7cc02c119e3e8b9b9ebf70aa3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 16 Jun 2019 10:42:46 +0100 Subject: [PATCH 039/188] Switch DeactivateAccountDialog to Field and cleanups Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- .../auth/_InteractiveAuthEntryComponents.scss | 4 ---- .../views/dialogs/_DeactivateAccountDialog.scss | 5 +---- .../views/auth/InteractiveAuthEntryComponents.js | 1 + .../views/dialogs/DeactivateAccountDialog.js | 14 +++++++++----- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/res/css/views/auth/_InteractiveAuthEntryComponents.scss b/res/css/views/auth/_InteractiveAuthEntryComponents.scss index 8c4f800d19..972acdc2ab 100644 --- a/res/css/views/auth/_InteractiveAuthEntryComponents.scss +++ b/res/css/views/auth/_InteractiveAuthEntryComponents.scss @@ -60,7 +60,3 @@ limitations under the License. .mx_InteractiveAuthEntryComponents_passwordSection { width: 300px; } - -.mx_InteractiveAuthEntryComponents_passwordSection input { - width: 100%; -} diff --git a/res/css/views/dialogs/_DeactivateAccountDialog.scss b/res/css/views/dialogs/_DeactivateAccountDialog.scss index 619310a543..192917b2d0 100644 --- a/res/css/views/dialogs/_DeactivateAccountDialog.scss +++ b/res/css/views/dialogs/_DeactivateAccountDialog.scss @@ -22,9 +22,6 @@ limitations under the License. margin-top: 60px; } -.mx_DeactivateAccountDialog .mx_DeactivateAccountDialog_input_section input[type=password] { +.mx_DeactivateAccountDialog .mx_DeactivateAccountDialog_input_section .mx_Field { width: 300px; - border: 1px solid $accent-color; - border-radius: 5px; - padding: 10px; } diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index 0e866ad586..b52dac44a9 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -137,6 +137,7 @@ export const PasswordAuthEntry = React.createClass({

{ _t("To continue, please enter your password.") }

: _t('Deactivate Account'); - const okEnabled = this.state.confirmButtonEnabled && !this.state.busy; + const okEnabled = this.state.password && !this.state.busy; let cancelButton = null; if (!this.state.busy) { @@ -113,6 +113,8 @@ export default class DeactivateAccountDialog extends React.Component { ; } + const Field = sdk.getComponent('elements.Field'); + return (

{ _t("To continue, please enter your password:") }

- {this._passwordField = e;}} className={passwordBoxClass} /> From 2fc2e32e60160ee170c663ba1f763bf45966b0ec Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Sun, 16 Jun 2019 11:43:13 +0100 Subject: [PATCH 040/188] Add Upload All button to UploadConfirmDialog Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/ContentMessages.js | 26 ++++++++++++------- .../views/dialogs/UploadConfirmDialog.js | 15 ++++++++++- src/i18n/strings/en_EN.json | 1 + 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/ContentMessages.js b/src/ContentMessages.js index ee3e8f1390..2d58622db8 100644 --- a/src/ContentMessages.js +++ b/src/ContentMessages.js @@ -425,19 +425,25 @@ export default class ContentMessages { } const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog"); + let uploadAll = false; for (let i = 0; i < okFiles.length; ++i) { const file = okFiles[i]; - const shouldContinue = await new Promise((resolve) => { - Modal.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, { - file, - currentIndex: i, - totalFiles: okFiles.length, - onFinished: (shouldContinue) => { - resolve(shouldContinue); - }, + if (!uploadAll) { + const shouldContinue = await new Promise((resolve) => { + Modal.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, { + file, + currentIndex: i, + totalFiles: okFiles.length, + onFinished: (shouldContinue, shouldUploadAll) => { + if (shouldUploadAll) { + uploadAll = true; + } + resolve(shouldContinue); + }, + }); }); - }); - if (!shouldContinue) break; + if (!shouldContinue) break; + } this._sendContentToRoom(file, roomId, matrixClient); } } diff --git a/src/components/views/dialogs/UploadConfirmDialog.js b/src/components/views/dialogs/UploadConfirmDialog.js index e7b22950d6..7e682a8301 100644 --- a/src/components/views/dialogs/UploadConfirmDialog.js +++ b/src/components/views/dialogs/UploadConfirmDialog.js @@ -49,6 +49,10 @@ export default class UploadConfirmDialog extends React.Component { this.props.onFinished(true); } + _onUploadAllClick = () => { + this.props.onFinished(true, true); + } + render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); @@ -85,6 +89,13 @@ export default class UploadConfirmDialog extends React.Component {
; } + let uploadAllButton; + if (this.props.currentIndex + 1 < this.props.totalFiles) { + uploadAllButton = ; + } + return ( + > + {uploadAllButton} + ); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 53fd82f6f2..e167659621 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1249,6 +1249,7 @@ "Upload files (%(current)s of %(total)s)": "Upload files (%(current)s of %(total)s)", "Upload files": "Upload files", "Upload": "Upload", + "Upload all": "Upload all", "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.", "These files are too large to upload. The file size limit is %(limit)s.": "These files are too large to upload. The file size limit is %(limit)s.", "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Some files are too large to be uploaded. The file size limit is %(limit)s.", From c68074f53293d1b6b2f7391ccd0df7be61dd139c Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Sun, 16 Jun 2019 21:52:25 +0200 Subject: [PATCH 041/188] Fix display of canonicalAlias in group room info Signed-off-by: Luca Weiss --- src/components/views/groups/GroupRoomInfo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/groups/GroupRoomInfo.js b/src/components/views/groups/GroupRoomInfo.js index db060218d4..7296b25344 100644 --- a/src/components/views/groups/GroupRoomInfo.js +++ b/src/components/views/groups/GroupRoomInfo.js @@ -224,7 +224,7 @@ module.exports = React.createClass({
- { this.state.groupRoom.canonical_alias } + { this.state.groupRoom.canonicalAlias }
From 4ffca5945653e3a4b1abd8b0e31adf1bc750f2f4 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Sat, 15 Jun 2019 12:36:37 +0000 Subject: [PATCH 042/188] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (1650 of 1650 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 31e370abed..a4c5914392 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -1993,5 +1993,8 @@ "Cannot reach identity server": "無法連線至身份識別伺服器", "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "您可以註冊,但有些功能在身份識別伺服器重新上線前會沒辦法運作。如果您一直看到這個警告,請檢查您的設定或聯絡伺服器管理員。", "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "您可以重設密碼,但有些功能在身份識別伺服器重新上線前會沒辦法運作。如果您一直看到這個警告,請檢查您的設定或聯絡伺服器管理員。", - "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "您可以登入,但有些功能在身份識別伺服器重新上線前會沒辦法運作。如果您一直看到這個警告,請檢查您的設定或聯絡伺服器管理員。" + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "您可以登入,但有些功能在身份識別伺服器重新上線前會沒辦法運作。如果您一直看到這個警告,請檢查您的設定或聯絡伺服器管理員。", + "Log in to your new account.": "登入到您的新帳號。", + "You can now close this window or log in to your new account.": "您現在可以關閉此視窗或登入到您的新帳號了。", + "Registration Successful": "註冊成功" } From 34702d7214dee7777e5f56dec050dbfbf897800f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 15 Jun 2019 21:12:48 +0000 Subject: [PATCH 043/188] Translated using Weblate (German) Currently translated at 87.1% (1437 of 1650 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 38d42e2993..0aaff02aed 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -1808,5 +1808,6 @@ "Could not load user profile": "Konnte Nutzerprofil nicht laden", "Your Matrix account": "Dein Matrixkonto", "Your Matrix account on %(serverName)s": "Dein Matrixkonto auf %(serverName)s", - "Show recent room avatars above the room list": "Zeige die letzten Avatare über der Raumliste an (neu laden um Änderungen zu übernehmen)" + "Show recent room avatars above the room list": "Zeige die letzten Avatare über der Raumliste an (neu laden um Änderungen zu übernehmen)", + "Email, name or Matrix ID": "E-Mail, Name oder Matrix-ID" } From ac201a8aff4ea9e9e3e2e8a198276ecc9b5d17dc Mon Sep 17 00:00:00 2001 From: Vitaly Lipatov Date: Sat, 15 Jun 2019 07:53:41 +0000 Subject: [PATCH 044/188] Translated using Weblate (Russian) Currently translated at 100.0% (1650 of 1650 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 129 +++++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 61 deletions(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 7a873c5210..686960467e 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -296,7 +296,7 @@ "No more results": "Больше никаких результатов", "No results": "Нет результатов", "OK": "OK", - "Only people who have been invited": "Только приглашенные участники", + "Only people who have been invited": "Только приглашённые участники", "Passwords can't be empty": "Пароли не могут быть пустыми", "%(senderName)s placed a %(callType)s call.": "%(senderName)s начал(а) %(callType)s-звонок.", "Please check your email and click on the link it contains. Once this is done, click continue.": "Проверьте свою электронную почту и нажмите на содержащуюся ссылку. После этого нажмите кнопку Продолжить.", @@ -336,7 +336,7 @@ "The main address for this room is": "Основной адрес этой комнаты", "This email address is already in use": "Этот email уже используется", "This email address was not found": "Этот адрес электронной почты не найден", - "The email address linked to your account must be entered.": "Необходимо ввести адрес электронной почты, связанный с вашей учетной записью.", + "The email address linked to your account must be entered.": "Необходимо ввести адрес электронной почты, связанный с вашей учётной записью.", "The file '%(fileName)s' failed to upload": "Не удалось отправить файл '%(fileName)s'", "The remote side failed to pick up": "Собеседник не ответил на ваш звонок", "This room has no local addresses": "У этой комнаты нет адресов на вашем сервере", @@ -365,7 +365,7 @@ "numbullet": "элемент нумерованного списка", "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Не удается подключиться к домашнему серверу через HTTP, так как в адресной строке браузера указан URL HTTPS. Используйте HTTPS или либо включите небезопасные сценарии.", "Dismiss": "Отклонить", - "Custom Server Options": "Выбор другого сервера", + "Custom Server Options": "Параметры другого сервера", "Mute": "Приглушить", "Operation failed": "Сбой операции", "powered by Matrix": "основано на Matrix", @@ -389,7 +389,7 @@ "Guest access is disabled on this Home Server.": "Гостевой доступ отключен на этом сервере.", "Guests cannot join this room even if explicitly invited.": "Посторонние не смогут войти в эту комнату, даже если они будут приглашены.", "Missing Media Permissions, click here to request.": "Отсутствуют разрешения, нажмите для запроса.", - "No media permissions": "Нет разрешенных носителей", + "No media permissions": "Нет разрешённых носителей", "You may need to manually permit Riot to access your microphone/webcam": "Вам необходимо предоставить Riot доступ к микрофону или веб-камере вручную", "Anyone": "Все", "Are you sure you want to leave the room '%(roomName)s'?": "Вы уверены, что хотите покинуть '%(roomName)s'?", @@ -517,7 +517,7 @@ "Error decrypting image": "Ошибка расшифровки изображения", "Error decrypting video": "Ошибка расшифровки видео", "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?": "Вы будете перенаправлены на внешний сайт, чтобы войти в свою учетную запись для использования с %(integrationsUrl)s. Продолжить?", + "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?": "Вы будете перенаправлены на внешний сайт, чтобы войти в свою учётную запись для использования с %(integrationsUrl)s. Продолжить?", "Removed or unknown message type": "Сообщение удалено или имеет неизвестный тип", "URL Previews": "Предпросмотр содержимого ссылок", "Drop file here to upload": "Перетащите файл сюда для отправки", @@ -552,8 +552,8 @@ "Username available": "Имя пользователя доступно", "Username not available": "Имя пользователя недоступно", "Something went wrong!": "Что-то пошло не так!", - "This will be your account name on the homeserver, or you can pick a different server.": "Это будет имя вашей учетной записи на домашнем сервере, или вы можете выбрать другой сервер.", - "If you already have a Matrix account you can log in instead.": "Если у вас уже есть учетная запись Matrix, вы можете войти.", + "This will be your account name on the homeserver, or you can pick a different server.": "Это будет имя вашей учётной записи на домашнем сервере, или вы можете выбрать другой сервер.", + "If you already have a Matrix account you can log in instead.": "Если у вас уже есть учётная запись Matrix, вы можете войти.", "Home": "Начало", "Accept": "Принять", "Active call (%(roomName)s)": "Текущий вызов (%(roomName)s)", @@ -707,9 +707,9 @@ "Failed to invite the following users to %(groupId)s:": "Не удалось пригласить этих пользователей в %(groupId)s:", "Failed to remove '%(roomName)s' from %(groupId)s": "Не удалось убрать '%(roomName)s' из %(groupId)s", "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Вы действительно хотите убрать '%(roomName)s' из %(groupId)s?", - "Jump to read receipt": "Перейти к последнему прочтенному им сообщению", + "Jump to read receipt": "Перейти к последнему прочтённому им сообщению", "Disable big emoji in chat": "Отключить большие Emoji в чате", - "Message Pinning": "Закрепленные сообщения", + "Message Pinning": "Закреплённые сообщения", "Remove avatar": "Удалить аватар", "Failed to invite users to %(groupId)s": "Не удалось пригласить пользователей в %(groupId)s", "Unable to reject invite": "Невозможно отклонить приглашение", @@ -721,15 +721,15 @@ "Failed to add the following users to the summary of %(groupId)s:": "Не удалось добавить следующих пользователей в сводку %(groupId)s:", "Which rooms would you like to add to this summary?": "Какие комнаты вы хотите добавить в эту сводку?", "Room name or alias": "Название комнаты или псевдоним", - "Pinned Messages": "Закрепленные сообщения", - "%(senderName)s changed the pinned messages for the room.": "%(senderName)s изменил(а) закрепленные в этой комнате сообщения.", + "Pinned Messages": "Закреплённые сообщения", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s изменил(а) закреплённые в этой комнате сообщения.", "Failed to add the following rooms to the summary of %(groupId)s:": "Не удалось добавить следующие комнаты в сводку %(groupId)s:", "Failed to remove the room from the summary of %(groupId)s": "Не удалось удалить комнату из сводки %(groupId)s", "The room '%(roomName)s' could not be removed from the summary.": "Комнату '%(roomName)s' не удалось удалить из сводки.", "Failed to remove a user from the summary of %(groupId)s": "Не удалось удалить пользователя из сводки %(groupId)s", "The user '%(displayName)s' could not be removed from the summary.": "Пользователя '%(displayName)s' не удалось удалить из сводки.", "Light theme": "Светлая тема", - "Dark theme": "Темная тема", + "Dark theme": "Тёмная тема", "Unknown": "Неизвестно", "Failed to add the following rooms to %(groupId)s:": "Не удалось добавить эти комнаты в %(groupId)s:", "Matrix ID": "Matrix ID", @@ -739,7 +739,7 @@ "You have entered an invalid address.": "Введен неправильный адрес.", "Unpin Message": "Открепить сообщение", "Jump to message": "Перейти к сообщению", - "No pinned messages.": "Нет прикрепленных сообщений.", + "No pinned messages.": "Нет прикреплённых сообщений.", "Loading...": "Загрузка...", "Unnamed room": "Комната без названия", "World readable": "Открыта для чтения", @@ -1013,7 +1013,7 @@ "Advanced notification settings": "Дополнительные параметры уведомлений", "Failed to send logs: ": "Не удалось отправить журналы: ", "delete the alias.": "удалить псевдоним.", - "To return to your account in future you need to set a password": "Чтобы вы могли вернуться в свою учетную запись в будущем, вам необходимо задать пароль", + "To return to your account in future you need to set a password": "Чтобы вы могли вернуться в свою учётную запись в будущем, вам необходимо задать пароль", "Forget": "Забыть", "#example": "#пример", "Hide panel": "Скрыть панель", @@ -1046,16 +1046,16 @@ "Failed to get protocol list from Home Server": "Не удалось получить список протоколов с домашнего сервера", "Collecting app version information": "Сбор информации о версии приложения", "Delete the room alias %(alias)s and remove %(name)s from the directory?": "Удалить псевдоним комнаты %(alias)s и удалить %(name)s из каталога?", - "This will allow you to return to your account after signing out, and sign in on other devices.": "Это позволит вам вернуться к учетной записи после выхода из системы и войти на других устройствах.", + "This will allow you to return to your account after signing out, and sign in on other devices.": "Это позволит вам вернуться к учётной записи после выхода из системы и войти на других устройствах.", "Keywords": "Ключевые слова", - "Enable notifications for this account": "Включить уведомления для этой учетной записи", + "Enable notifications for this account": "Включить уведомления для этой учётной записи", "Directory": "Каталог", "Invite to this community": "Пригласить в это сообщество", "Search for a room": "Поиск комнаты", - "Messages containing keywords": "Сообщения, содержащие определенные ключевые слова", + "Messages containing keywords": "Сообщения, содержащие определённые ключевые слова", "View Source": "Просмотр источника", "Tuesday": "Вторник", - "Enter keywords separated by a comma:": "Введите ключевые слова, разделенные запятой:", + "Enter keywords separated by a comma:": "Введите ключевые слова, разделённые запятой:", "Search…": "Поиск…", "You have successfully set a password and an email address!": "Вы успешно установили пароль и email!", "Remove %(name)s from the directory?": "Удалить %(name)s из каталога?", @@ -1122,7 +1122,7 @@ "Riot does not know how to join a room on this network": "Riot не знает, как присоединиться к комнате, принадлежащей к этой сети", "Mentions only": "Только при упоминаниях", "Wednesday": "Среда", - "You can now return to your account after signing out, and sign in on other devices.": "Теперь вы сможете вернуться к своей учетной записи после выхода из системы и войти на других устройствах.", + "You can now return to your account after signing out, and sign in on other devices.": "Теперь вы сможете вернуться к своей учётной записи после выхода из системы и войти на других устройствах.", "Enable email notifications": "Включить уведомления на email", "Event Type": "Тип мероприятия", "Download this file": "Скачать файл", @@ -1159,17 +1159,17 @@ "Enable widget screenshots on supported widgets": "Включить скриншоты виджета в поддерживаемых виджетах", "Collapse Reply Thread": "Ответить с цитированием", "Send analytics data": "Отправить данные аналитики", - "Muted Users": "Приглушенные пользователи", + "Muted Users": "Приглушённые пользователи", "Warning: This widget might use cookies.": "Внимание: этот виджет может использовать cookie.", "Terms and Conditions": "Условия и положения", "To continue using the %(homeserverDomain)s homeserver you must review and agree to our terms and conditions.": "Для продолжения использования сервера %(homeserverDomain)s вы должны ознакомиться и принять условия и положения.", "Review terms and conditions": "Просмотр условий и положений", "e.g. %(exampleValue)s": "напр. %(exampleValue)s", "Failed to indicate account erasure": "Не удается удалить учетную запись", - "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "Это навсегда сделает вашу учетную запись невозможной для использования. Вы не сможете войти в систему, и никто не сможет перерегистрировать тот же идентификатор пользователя. Это приведет к тому, что ваша учетная запись выйдет из всех комнат, в которые она входит, и будут удалены данные вашей учетной записи с сервера идентификации. Это действие необратимо.", - "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "По умолчанию деактивация вашей учетной записи не приведет к удалению всех ваших сообщений. Если вы хотите, чтобы мы удалили ваши сообщения, поставьте отметку в поле ниже.", + "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "Это навсегда сделает вашу учётную запись невозможной для использования. Вы не сможете войти в систему, и никто не сможет перерегистрировать тот же идентификатор пользователя. Это приведёт к тому, что ваша учётная запись выйдет из всех комнат, в которые она входит, и будут удалены данные вашей учётной записи с сервера идентификации. Это действие необратимо.", + "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "По умолчанию деактивация вашей учётной записи не приведёт к удалению всех ваших сообщений. Если вы хотите, чтобы мы удалили ваши сообщения, поставьте отметку в поле ниже.", "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Видимость сообщений в Matrix похожа на электронную почту. Удаление ваших сообщений означает, что отправленные вами сообщения не будут видны новым или незарегистрированным пользователям, но зарегистрированные пользователи, у которых уже есть доступ к этим сообщениям, по-прежнему будут иметь доступ к своей копии.", - "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Пожалуйста, удалите все сообщения, которые я отправил, после деактивации учетной записи. (Внимание: будущие пользователи увидят неполный вид разговоров)", + "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Пожалуйста, удалите все сообщения, которые я отправил, после деактивации учётной записи. (Внимание: будущие пользователи увидят неполный вид разговоров)", "To continue, please enter your password:": "Чтобы продолжить, введите пароль:", "password": "пароль", "Please help improve Riot.im by sending anonymous usage data. This will use a cookie (please see our Cookie Policy).": "Пожалуйста, помогите улучшить Riot.im, отправляя анонимные данные использования. При этом будут использоваться cookie (ознакомьтесь с нашейПолитикой cookie).", @@ -1281,10 +1281,10 @@ "Sequences like abc or 6543 are easy to guess": "Последовательности типа abc или 6543 легко угадываемы", "Recent years are easy to guess": "Последние года легко угадываемы", "Dates are often easy to guess": "Даты часто легко угадать", - "This is a top-10 common password": "Это топ-10 распространенных паролей", - "This is a top-100 common password": "Это топ-100 распространенных паролей", - "This is a very common password": "Это очень распространенный пароль", - "This is similar to a commonly used password": "Это похоже на распространенный пароль", + "This is a top-10 common password": "Это топ-10 распространённых паролей", + "This is a top-100 common password": "Это топ-100 распространённых паролей", + "This is a very common password": "Это очень распространённый пароль", + "This is similar to a commonly used password": "Это похоже на распространённый пароль", "A word by itself is easy to guess": "Общеупотребительные слова легко угадываемы", "Names and surnames by themselves are easy to guess": "Имена и фамилии легко угадываемые", "Common names and surnames are easy to guess": "Распространённые имена и фамилии легко угадываемы", @@ -1345,7 +1345,7 @@ "Theme": "Тема", "2018 theme": "Тема 2018 года", "Account management": "Управление аккаунтом", - "Deactivating your account is a permanent action - be careful!": "Деактивация вашей учетной записи — это необратимое действие. Будьте осторожны!", + "Deactivating your account is a permanent action - be careful!": "Деактивация вашей учётной записи — это необратимое действие. Будьте осторожны!", "Chat with Riot Bot": "Чат с ботом Riot", "Help & About": "Помощь & о программе", "FAQ": "Часто задаваемые вопросы", @@ -1353,12 +1353,12 @@ "Lazy loading is not supported by your current homeserver.": "Ленивая подгрузка не поддерживается вашим сервером.", "Preferences": "Параметры", "Room list": "Список комнат", - "Timeline": "Временная шкала", + "Timeline": "Временна́я шкала", "Autocomplete delay (ms)": "Задержка автодополнения (мс)", "Roles & Permissions": "Роли и права", "Security & Privacy": "Безопасность и конфиденциальность", "Encryption": "Шифрование", - "Encrypted": "Зашифрованно", + "Encrypted": "Зашифровано", "Ignored users": "Игнорируемые пользователи", "Key backup": "Резервное копирование ключей", "Voice & Video": "Голос и видео", @@ -1392,7 +1392,7 @@ "All keys backed up": "Все ключи сохранены", "Developer options": "Параметры разработчика", "General": "Общий", - "Set a new account password...": "Установить новый пароль учетной записи...", + "Set a new account password...": "Установить новый пароль учётной записи...", "Legal": "Законный", "At this time it is not possible to reply with an emote.": "В настоящее время невозможно ответить с помощью эмоции.", "Room avatar": "Аватар комнаты", @@ -1435,7 +1435,7 @@ "Copy to clipboard": "Скопировать в буфер обмена", "Download": "Скачать", "Your Recovery Key": "Ваш ключ восстановления", - "Create your account": "Создать учетную запись", + "Create your account": "Создать учётную запись", "Your account": "Ваша учетная запись", "Username": "Имя пользователя", "Not sure of your password? Set a new one": "Не уверены в пароле? Установите новый", @@ -1612,7 +1612,7 @@ "Please confirm that you'd like to go forward with upgrading this room from to .": "Пожалуйста, подтвердите, что вы хотите перейти к обновлению этой комнаты с на .", "Upgrade": "Обновление", "Changes your avatar in this current room only": "Меняет ваш аватар только в этой комнате", - "Unbans user with given ID": "Разблокировать пользователя с заданным ID", + "Unbans user with given ID": "Разблокирует пользователя с заданным ID", "Adds a custom widget by URL to the room": "Добавляет пользовательский виджет по URL-адресу в комнате", "Please supply a https:// or http:// widget URL": "Пожалуйста, укажите https:// или http:// адрес URL виджета", "You cannot modify widgets in this room.": "Вы не можете изменять виджеты в этой комнате.", @@ -1662,12 +1662,12 @@ "inline-code": "встроенный код", "block-quote": "цитата", "bulleted-list": "маркированный список", - "numbered-list": "пронумерованный-лист", + "numbered-list": "нумерованный-список", "This room has been replaced and is no longer active.": "Эта комната была заменена и больше не активна.", "Joining room …": "Вступление в комнату …", "Loading …": "Загрузка…", "Rejecting invite …": "Отказ от приглашения …", - "Join the conversation with an account": "Присоединиться к разговору с учетной записью", + "Join the conversation with an account": "Присоединиться к разговору с учётной записью", "Sign Up": "Подписаться", "Sign In": "Войти в систему", "You were kicked from %(roomName)s by %(memberName)s": "Вы были выгнаны %(memberName)s из %(roomName)s", @@ -1678,11 +1678,11 @@ "Something went wrong with your invite to %(roomName)s": "Что-то пошло не так с вашим приглашением в %(roomName)s", "%(errcode)s was returned while trying to valide your invite. You could try to pass this information on to a room admin.": "%(errcode)s был возвращен при попытке подтвердить ваше приглашение. Вы можете попытаться передать эту информацию администратору комнаты.", "You can only join it with a working invite.": "Вы можете присоединиться к ней только с рабочим приглашением.", - "You can still join it because this is a public room.": "Вы все еще можете присоединиться к ней, потому что это общественная комната.", + "You can still join it because this is a public room.": "Вы всё ещё можете присоединиться к ней, потому что это публичная комната.", "Join the discussion": "Присоединяйтесь к обсуждению", "Try to join anyway": "Постарайся присоединиться в любом случае", "This invite to %(roomName)s wasn't sent to your account": "Это приглашение к %(roomName)s не было отправлено на ваш аккаунт", - "Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.": "Войдите в систему с другой учетной записью, запросите новое приглашение или добавьте email %(email)s к этой учетной записи.", + "Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.": "Войдите в систему с другой учётной записью, запросите новое приглашение или добавьте email %(email)s к этой учётной записи.", "Do you want to chat with %(user)s?": "Хотите пообщаться с %(user)s?", "Do you want to join %(roomName)s?": "Хотите присоединиться к %(roomName)s?", " invited you": " пригласил тебя", @@ -1695,17 +1695,17 @@ "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Сообщения в этой комнате защищены сквозным шифрованием. Только вы и получатель(и) имеют ключи для чтения этих сообщений.", "Securely back up your keys to avoid losing them. Learn more.": "Надежно сохраните резервную копию ключей, чтобы не потерять их. Подробнее", "Don't ask me again": "Не спрашивай меня больше", - "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Обновление этой комнаты отключит текущий экземпляр комнаты и создаст обновленную комнату с тем же именем.", + "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Обновление этой комнаты отключит текущий экземпляр комнаты и создаст обновлённую комнату с тем же именем.", "This room has already been upgraded.": "Эта комната уже была обновлена.", "This room is running room version , which this homeserver has marked as unstable.": "Эта комната работает под управлением версии комнаты , которую этот сервер пометил как unstable.", "Add some now": "Добавить сейчас", "Failed to revoke invite": "Не удалось отменить приглашение", - "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Не удалось отозвать приглашение. Возможно, на сервере возникла временная проблема или у вас недостаточно прав для отзыва приглашения.", + "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Не удалось отозвать приглашение. Возможно, на сервере возникла вре́менная проблема или у вас недостаточно прав для отзыва приглашения.", "Revoke invite": "Отозвать приглашение", "Invited by %(sender)s": "Приглашен %(sender)s", "There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "При обновлении основного адреса комнаты произошла ошибка. Возможно, это не разрешено сервером или произошел временный сбой.", "Error creating alias": "Ошибка при создании псевдонима", - "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.": "Произошла ошибка при создании этого псевдонима. Возможно, это не разрешено сервером или произошел временный сбой.", + "There was an error creating that alias. It may not be allowed by the server or a temporary failure occurred.": "Произошла ошибка при создании этого псевдонима. Возможно, это не разрешено сервером или произошёл временный сбой.", "Error removing alias": "Ошибка удаления псевдонима", "There was an error removing that alias. It may no longer exist or a temporary error occurred.": "Произошла ошибка при удалении этого псевдонима. Возможно, он больше не существует или произошла временная ошибка.", "Error updating flair": "Ошибка обновления стиля", @@ -1722,11 +1722,11 @@ "Rotate clockwise": "Повернуть по часовой стрелке", "Edit message": "Редактировать сообщение", "Power level": "Уровень мощности", - "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Не удалось найти профили для Matrix ID, перечисленных ниже. Вы все равно хотите их пригласить?", + "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Не удалось найти профили для Matrix ID, перечисленных ниже. Вы всё равно хотите их пригласить?", "Invite anyway": "Пригласить в любом случае", "GitHub issue": "GitHub вопрос", "Notes": "Заметка", - "If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.": "Если есть дополнительный контекст, который может помочь в анализе проблемы, такой как то, что вы делали в то время, ID комнат, ID пользователей и т. Д., пожалуйста, включите эти данные.", + "If there is additional context that would help in analysing the issue, such as what you were doing at the time, room IDs, user IDs, etc., please include those things here.": "Если есть дополнительный контекст, который может помочь в анализе проблемы, такой как то, что вы делали в то время, ID комнат, ID пользователей и т. д., пожалуйста, включите эти данные.", "Unable to load commit detail: %(msg)s": "Невозможно загрузить детали: %(msg)s", "To avoid losing your chat history, you must export your room keys before logging out. You will need to go back to the newer version of Riot to do this": "Чтобы не потерять историю чата, вы должны экспортировать ключи от комнаты перед выходом из системы. Вам нужно будет вернуться к более новой версии Riot, чтобы сделать это", "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ": "Ранее вы использовали более новую версию Riot на %(host)s. Чтобы снова использовать эту версию с сквозным шифрованием, вам нужно выйти и снова войти. ", @@ -1738,7 +1738,7 @@ "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Проверьте этого пользователя, чтобы отметить его как доверенного. Доверенные пользователи дают вам дополнительное спокойствие при использовании сквозного шифрования сообщений.", "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Проверка этого пользователя пометит его устройство как доверенное, а также пометит ваше устройство как доверенное.", "Waiting for partner to confirm...": "В ожидании партнера, чтобы подтвердить ...", - "You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "Ранее вы использовали Riot на %(host)s с отложенной загрузкой участников. В этой версии отложенная загрузка отключена. Поскольку локальный кеш не совместим между этими двумя настройками, Riot необходимо повторно синхронизировать вашу учетную запись.", + "You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "Ранее вы использовали Riot на %(host)s с отложенной загрузкой участников. В этой версии отложенная загрузка отключена. Поскольку локальный кеш не совместим между этими двумя настройками, Riot необходимо повторно синхронизировать вашу учётную запись.", "If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Если другая версия Riot все еще открыта на другой вкладке, закройте ее, так как использование Riot на том же хосте с включенной и отключенной ленивой загрузкой одновременно вызовет проблемы.", "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot теперь использует в 3-5 раз меньше памяти, загружая информацию только о других пользователях, когда это необходимо. Пожалуйста, подождите, пока мы ресинхронизируемся с сервером!", "I don't want my encrypted messages": "Я не хочу, мои зашифрованные сообщения", @@ -1786,13 +1786,13 @@ "Set a new status...": "Установка нового статуса...", "Hide": "Скрыть", "This homeserver would like to make sure you are not a robot.": "Этот сервер хотел бы убедиться, что вы не робот.", - "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.": "Пользовательские параметры сервера можно использовать для входа на другие серверы Matrix, указав другой URL-адрес сервера. Это позволяет использовать это приложение с существующей учетной записью Matrix на другом домашнем сервере.", + "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.": "Пользовательские параметры сервера можно использовать для входа на другие серверы Matrix, указав другой URL-адрес сервера. Это позволяет использовать это приложение с существующей учётной записью Matrix на другом домашнем сервере.", "Please review and accept all of the homeserver's policies": "Пожалуйста, просмотрите и примите все правила сервера", "Please review and accept the policies of this homeserver:": "Пожалуйста, просмотрите и примите политику этого сервера:", "Unable to validate homeserver/identity server": "Невозможно проверить сервер/сервер идентификации", "Enter the location of your Modular homeserver. It may use your own domain name or be a subdomain of modular.im.": "Введите местоположение вашего Modula homeserver. Он может использовать ваше собственное доменное имя или быть поддоменом modular.im.", - "Sign in to your Matrix account on %(serverName)s": "Войдите в свою учетную запись Matrix на %(serverName)s", - "Sign in to your Matrix account on ": "Войдите в свою учетную запись Matrix с помощью ", + "Sign in to your Matrix account on %(serverName)s": "Войдите в свою учётную запись Matrix на %(serverName)s", + "Sign in to your Matrix account on ": "Войдите в свою учётную запись Matrix с помощью ", "Change": "Изменить", "Use an email address to recover your account": "Используйте email, чтобы восстановить свой аккаунт", "Enter email address (required on this homeserver)": "Введите адрес электронной почты (требуется для этого сервера)", @@ -1808,9 +1808,9 @@ "Use letters, numbers, dashes and underscores only": "Используйте только буквы, цифры, тире и подчеркивание", "Enter username": "Введите имя пользователя", "Some characters not allowed": "Некоторые символы не разрешены", - "Create your Matrix account on %(serverName)s": "Создайте свою учетную запись Matrix на %(serverName)s", - "Create your Matrix account on ": "Создайте свою учетную запись Matrix на ", - "Use an email address to recover your account.": "Для восстановления учетной записи используйте email.", + "Create your Matrix account on %(serverName)s": "Создайте свою учётную запись Matrix на %(serverName)s", + "Create your Matrix account on ": "Создайте свою учётную запись Matrix на ", + "Use an email address to recover your account.": "Для восстановления учётной записи используйте email.", "Other users can invite you to rooms using your contact details.": "Другие пользователи могут пригласить вас в комнаты, используя ваши контактные данные.", "Enter custom server URLs What does this mean?": "Введите пользовательские URL-адреса сервера Что это значит?", "Join millions for free on the largest public server": "Присоединяйтесь бесплатно к миллионам на крупнейшем общедоступном сервере", @@ -1824,7 +1824,7 @@ "Want more than a community? Get your own server": "Хотите больше, чем просто сообщество? Получите свой собственный сервер", "This homeserver does not support communities": "Этот сервер не поддерживает сообщества", "You are logged in to another account": "Вы вошли в другой аккаунт", - "Thank you for verifying your email! The account you're logged into here (%(sessionUserId)s) appears to be different from the account you've verified an email for (%(verifiedUserId)s). If you would like to log in to %(verifiedUserId2)s, please log out first.": "Спасибо за подтверждение вашей электронной почты! Учетная запись, в которую вы вошли (%(sessionUserId)s), похоже отличается от учетной записи, для которой вы подтвердили адрес электронной почты (%(VerifiedUserId)s). Если вы хотите войти в %(VerifiedUserId2)s, сначала выйдите из системы.", + "Thank you for verifying your email! The account you're logged into here (%(sessionUserId)s) appears to be different from the account you've verified an email for (%(verifiedUserId)s). If you would like to log in to %(verifiedUserId2)s, please log out first.": "Спасибо за подтверждение вашей электронной почты! Учётная запись, в которую вы вошли (%(sessionUserId)s), похоже отличается от учётной записи, для которой вы подтвердили адрес электронной почты (%(verifiedUserId)s). Если вы хотите войти в %(verifiedUserId2)s, сначала выйдите из системы.", "Riot failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "Riot не смог получить список протоколов с сервера.Сервер может быть слишком старым для поддержки сетей сторонних производителей.", "Riot failed to get the public room list.": "Riot не смог получить список публичных комнат.", "The homeserver may be unavailable or overloaded.": "Сервер может быть недоступен или перегружен.", @@ -1836,8 +1836,8 @@ "Your profile": "Ваш профиль", "Could not load user profile": "Не удалось загрузить профиль пользователя", "Changing your password will reset any end-to-end encryption keys on all of your devices, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another device before resetting your password.": "Смена пароля приведет к сбросу любых сквозных ключей шифрования на всех ваших устройствах, что сделает историю зашифрованного чата нечитаемой. Настройте Резервное копирование ключей или экспортируйте ключи от вашей комнаты с другого устройства перед сбросом пароля.", - "Your Matrix account on %(serverName)s": "Ваша учетная запись Matrix на %(serverName)s", - "Your Matrix account on ": "Ваша учетная запись Matrix на ", + "Your Matrix account on %(serverName)s": "Ваша учётная запись Matrix на %(serverName)s", + "Your Matrix account on ": "Ваша учётная запись Matrix на ", "A verification email will be sent to your inbox to confirm setting your new password.": "Письмо с подтверждением будет отправлено на ваш почтовый ящик, чтобы подтвердить установку нового пароля.", "Sign in instead": "Войдите, вместо этого", "Your password has been reset.": "Ваш пароль был сброшен.", @@ -1845,7 +1845,7 @@ "Set a new password": "Установить новый пароль", "Invalid homeserver discovery response": "Неверное обнаружения сервера", "Failed to get autodiscovery configuration from server": "Не удалось получить конфигурацию автообнаружения с сервера", - "Show recently visited rooms above the room list": "Показать недавно посещенные комнаты над списком комнат", + "Show recently visited rooms above the room list": "Показать недавно посещённые комнаты над списком комнат", "Invalid base_url for m.homeserver": "Неверный base_url для m.homeserver", "Homeserver URL does not appear to be a valid Matrix homeserver": "URL-адрес сервера не является действительным URL-адресом сервера Матрица", "Invalid identity server discovery response": "Неверный ответ на запрос идентификации сервера", @@ -1855,12 +1855,12 @@ "This homeserver does not support login using email address.": "Этот сервер не поддерживает вход с использованием адреса электронной почты.", "Failed to perform homeserver discovery": "Не удалось выполнить обнаружение сервера", "Sign in with single sign-on": "Войдите в систему с единой регистрацией", - "Create account": "Создать учетную запись", + "Create account": "Создать учётную запись", "Registration has been disabled on this homeserver.": "Регистрация на этом сервере отключена.", "Unable to query for supported registration methods.": "Невозможно запросить поддерживаемые методы регистрации.", "Great! This passphrase looks strong enough.": "Отлично! Этот пароль выглядит достаточно сильной.", "We'll store an encrypted copy of your keys on our server. Protect your backup with a passphrase to keep it secure.": "Мы будем хранить зашифрованную копию ваших ключей на нашем сервере. Защитите свою резервную копию паролем, чтобы сохранить ее в безопасности.", - "For maximum security, this should be different from your account password.": "Для максимальной безопасности это должно отличаться от пароля вашей учетной записи.", + "For maximum security, this should be different from your account password.": "Для максимальной безопасности это должно отличаться от пароля вашей учётной записи.", "Enter a passphrase...": "Введите пароль....", "Set up with a Recovery Key": "Настройка с ключом восстановления", "That matches!": "Это совпадает!", @@ -1891,14 +1891,21 @@ "Unable to create key backup": "Невозможно создать резервную копию ключа", "Retry": "Попробуйте снова", "Without setting up Secure Message Recovery, you'll lose your secure message history when you log out.": "Без настройки безопасного восстановления сообщений при выходе из системы вы потеряете историю защищенных сообщений.", - "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Если вы не установили новый метод восстановления, злоумышленник может попытаться получить доступ к вашей учетной записи. Измените пароль учетной записи и сразу установите новый способ восстановления в настройках.", + "If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Если вы не установили новый метод восстановления, злоумышленник может попытаться получить доступ к вашей учётной записи. Измените пароль учётной записи и сразу установите новый способ восстановления в настройках.", "This device has detected that your recovery passphrase and key for Secure Messages have been removed.": "Это устройство обнаружило, что ваш пароль восстановления и ключ для безопасных сообщений были удалены.", "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.": "Если вы сделали это случайно, вы можете настроить безопасные сообщения на этом устройстве, которые будут повторно зашифровать историю сообщений этого устройства с новым методом восстановления.", - "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.": "Если вы не удалили метод восстановления, злоумышленник может попытаться получить доступ к вашей учетной записи. Измените пароль учетной записи и сразу установите новый способ восстановления в настройках.", - "Cannot reach homeserver": "Не удается связаться с сервером", + "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.": "Если вы не удалили метод восстановления, злоумышленник может попытаться получить доступ к вашей учётной записи. Измените пароль учётной записи и сразу установите новый способ восстановления в настройках.", + "Cannot reach homeserver": "Не удаётся связаться с сервером", "Ensure you have a stable internet connection, or get in touch with the server admin": "Убедитесь, что у вас есть стабильное подключение к интернету, или свяжитесь с администратором сервера", - "Your Riot is misconfigured": "Твой Riot неправильно настроен", + "Your Riot is misconfigured": "Ваш Riot неправильно настроен", "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Попросите администратора Riot проверить конфигурационный файл на наличие неправильных или повторяющихся записей.", - "Unexpected error resolving identity server configuration": "Неопределённая ошибка при разборе опции сервера идентификации", - "Use lowercase letters, numbers, dashes and underscores only": "Используйте только строчные буквы, цифры, тире и подчеркивания" + "Unexpected error resolving identity server configuration": "Неопределённая ошибка при разборе параметра сервера идентификации", + "Use lowercase letters, numbers, dashes and underscores only": "Используйте только строчные буквы, цифры, тире и подчеркивания", + "Cannot reach identity server": "Не удаётся связаться с сервером идентификации", + "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Вы можете зарегистрироваться, но некоторые возможности не будет доступны, пока сервер идентификации не станет доступным. Если вы продолжаете видеть это предупреждение, проверьте вашу конфигурацию или свяжитесь с администратором сервера.", + "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Вы можете сбросить пароль, но некоторые возможности не будет доступны, пока сервер идентификации не станет доступным. Если вы продолжаете видеть это предупреждение, проверьте вашу конфигурацию или свяжитесь с администратором сервера.", + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Вы можете войти в систему, но некоторые возможности не будет доступны, пока сервер идентификации не станет доступным. Если вы продолжаете видеть это предупреждение, проверьте вашу конфигурацию или свяжитесь с администратором сервера.", + "Log in to your new account.": "Войти в вашу новую учётную запись.", + "You can now close this window or log in to your new account.": "Вы можете закрыть это окно или войти в вашу новую учётную запись.", + "Registration Successful": "Регистрация успешно завершена" } From 8c538f2faf1c7177e4b14ef5d9f83b918885acbd Mon Sep 17 00:00:00 2001 From: BenjaminVettori Date: Sun, 16 Jun 2019 21:50:51 +0000 Subject: [PATCH 045/188] Translated using Weblate (German) Currently translated at 87.3% (1440 of 1650 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 0aaff02aed..66541deb04 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -1809,5 +1809,7 @@ "Your Matrix account": "Dein Matrixkonto", "Your Matrix account on %(serverName)s": "Dein Matrixkonto auf %(serverName)s", "Show recent room avatars above the room list": "Zeige die letzten Avatare über der Raumliste an (neu laden um Änderungen zu übernehmen)", - "Email, name or Matrix ID": "E-Mail, Name oder Matrix-ID" + "Email, name or Matrix ID": "E-Mail, Name oder Matrix-ID", + "Name or Matrix ID": "Name oder Matrix ID", + "Your Riot is misconfigured": "Dein Riot ist falsch konfiguriert" } From 4a0471eba71fd62e60ff9b1853a34e25014bfb74 Mon Sep 17 00:00:00 2001 From: natowi Date: Sun, 16 Jun 2019 22:14:04 +0000 Subject: [PATCH 046/188] Translated using Weblate (German) Currently translated at 87.3% (1440 of 1650 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 66541deb04..a3ad566d9e 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -1811,5 +1811,6 @@ "Show recent room avatars above the room list": "Zeige die letzten Avatare über der Raumliste an (neu laden um Änderungen zu übernehmen)", "Email, name or Matrix ID": "E-Mail, Name oder Matrix-ID", "Name or Matrix ID": "Name oder Matrix ID", - "Your Riot is misconfigured": "Dein Riot ist falsch konfiguriert" + "Your Riot is misconfigured": "Dein Riot ist falsch konfiguriert", + "You cannot modify widgets in this room.": "Du kannst in diesem Raum keine Widgets verändern" } From e3bf4a0b8e9208725502f179f79f1badd5678acf Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 17 Jun 2019 16:27:35 +0100 Subject: [PATCH 047/188] Re-enable register button on change to working HS Register button disabling is done via serverErrorIsFatal so we need to reset this on a successful validation. https://github.com/vector-im/riot-web/issues/10029 --- src/components/structures/auth/Registration.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 1957275505..20b13e4da9 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -180,7 +180,10 @@ module.exports = React.createClass({ serverConfig.hsUrl, serverConfig.isUrl, ); - this.setState({serverIsAlive: true}); + this.setState({ + serverIsAlive: true, + serverErrorIsFatal: false, + }); } catch (e) { this.setState({ busy: false, From 3f6bb0c7cad6fa788ae5019f8993e1db981f8cd8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 17 Jun 2019 16:57:13 +0100 Subject: [PATCH 048/188] Use default cursor for disabled submit button --- res/css/structures/auth/_Login.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss index 9bcd79a357..68e5f5c19f 100644 --- a/res/css/structures/auth/_Login.scss +++ b/res/css/structures/auth/_Login.scss @@ -30,6 +30,7 @@ limitations under the License. .mx_Login_submit:disabled { opacity: 0.3; + cursor: default; } .mx_AuthBody a.mx_Login_sso_link:link, From 872c1acdea0126243866f815d0374d58caa145cb Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 17 Jun 2019 18:29:03 +0200 Subject: [PATCH 049/188] keep mx_Field stretching --- res/css/views/elements/_Field.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/css/views/elements/_Field.scss b/res/css/views/elements/_Field.scss index f9cbf8c541..a6ac680116 100644 --- a/res/css/views/elements/_Field.scss +++ b/res/css/views/elements/_Field.scss @@ -18,6 +18,8 @@ limitations under the License. .mx_Field { display: flex; + flex: 1; + min-width: 0; position: relative; margin: 1em 0; border-radius: 4px; From a35c9ea5853820e1d97c58a84b796bce0c7f390d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 17 Jun 2019 19:36:52 +0200 Subject: [PATCH 050/188] provide default for missing device labels --- src/CallMediaHandler.js | 2 -- .../views/settings/tabs/user/VoiceUserSettingsTab.js | 12 +++++++++++- src/i18n/strings/en_EN.json | 3 +++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/CallMediaHandler.js b/src/CallMediaHandler.js index 9a1c9d70b8..55b8764a9e 100644 --- a/src/CallMediaHandler.js +++ b/src/CallMediaHandler.js @@ -26,8 +26,6 @@ export default { const audioinput = []; const videoinput = []; - if (devices.some((device) => !device.label)) return false; - devices.forEach((device) => { switch (device.kind) { case 'audiooutput': audiooutput.push(device); break; diff --git a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js index 84d70a48d4..31a11b13ea 100644 --- a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js @@ -100,7 +100,17 @@ export default class VoiceUserSettingsTab extends React.Component { }; _renderDeviceOptions(devices, category) { - return devices.map((d) => ); + return devices.map((d) => { + let label = d.label; + if (!label) { + switch (d.kind) { + case "audioinput": label = _t("Unnamed microphone"); break; + case "audiooutput": label = _t("Unnamed audio output"); break; + case "videoinput": label = _t("Unnamed camera"); break; + } + } + return (); + }); } render() { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 53fd82f6f2..07f362441c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -610,6 +610,9 @@ "Learn more about how we use analytics.": "Learn more about how we use analytics.", "No media permissions": "No media permissions", "You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam", + "Unnamed microphone": "Unnamed microphone", + "Unnamed audio output": "Unnamed audio output", + "Unnamed camera": "Unnamed camera", "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", "Request media permissions": "Request media permissions", "No Audio Outputs detected": "No Audio Outputs detected", From 10f6abfe1795e292d1742c52abdaad2d470d8d48 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 17 Jun 2019 18:47:20 +0100 Subject: [PATCH 051/188] Allow changing server if validation has failed Show the server config section if there's an error and fix an if case where we forgot to un-set the busy flag --- src/components/structures/auth/Registration.js | 6 +++++- src/components/views/auth/ServerConfig.js | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 20b13e4da9..662444379f 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -168,6 +168,8 @@ module.exports = React.createClass({ _replaceClient: async function(serverConfig) { this.setState({ errorText: null, + serverDeadError: null, + serverErrorIsFatal: false, // busy while we do liveness check (we need to avoid trying to render // the UI auth component while we don't have a matrix client) busy: true, @@ -429,7 +431,9 @@ module.exports = React.createClass({ // If we're on a different phase, we only show the server type selector, // which is always shown if we allow custom URLs at all. - if (PHASES_ENABLED && this.state.phase !== PHASE_SERVER_DETAILS) { + // (if there's a fatal server error, we need to show the full server + // config as the user may need to change servers to resolve the error). + if (PHASES_ENABLED && this.state.phase !== PHASE_SERVER_DETAILS && !this.state.serverErrorIsFatal) { return
Date: Mon, 17 Jun 2019 13:38:23 -0600 Subject: [PATCH 052/188] Convert IntegrationsManager to a class --- .eslintignore.errorfiles | 1 - .../views/settings/IntegrationsManager.js | 57 +++++++++---------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index a89c083518..9ecd39ffc2 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -47,7 +47,6 @@ src/components/views/rooms/UserTile.js src/components/views/settings/ChangeAvatar.js src/components/views/settings/ChangePassword.js src/components/views/settings/DevicesPanel.js -src/components/views/settings/IntegrationsManager.js src/components/views/settings/Notifications.js src/GroupAddressPicker.js src/HtmlUtils.js diff --git a/src/components/views/settings/IntegrationsManager.js b/src/components/views/settings/IntegrationsManager.js index a517771f1d..2e292b63fc 100644 --- a/src/components/views/settings/IntegrationsManager.js +++ b/src/components/views/settings/IntegrationsManager.js @@ -1,5 +1,6 @@ /* Copyright 2015, 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,50 +15,48 @@ See the License for the specific language governing permissions and limitations under the License. */ -'use strict'; +import React from 'react'; +import PropTypes from 'prop-types'; +import sdk from '../../../index'; +import MatrixClientPeg from '../../../MatrixClientPeg'; +import { _t } from '../../../languageHandler'; +import Modal from '../../../Modal'; +import dis from '../../../dispatcher'; -const React = require('react'); -const sdk = require('../../../index'); -const MatrixClientPeg = require('../../../MatrixClientPeg'); -const dis = require('../../../dispatcher'); +export default class IntegrationsManager extends React.Component { + static propTypes = { + // the source of the integration manager being embedded + src: PropTypes.string.isRequired, -module.exports = React.createClass({ - displayName: 'IntegrationsManager', + // callback when the manager is dismissed + onFinished: PropTypes.func.isRequired, + }; - propTypes: { - src: React.PropTypes.string.isRequired, // the source of the integration manager being embedded - onFinished: React.PropTypes.func.isRequired, // callback when the lightbox is dismissed - }, - - // XXX: keyboard shortcuts for managing dialogs should be done by the modal - // dialog base class somehow, surely... - componentDidMount: function() { + componentDidMount() { this.dispatcherRef = dis.register(this.onAction); document.addEventListener("keydown", this.onKeyDown); - }, + } - componentWillUnmount: function() { + componentWillUnmount() { dis.unregister(this.dispatcherRef); document.removeEventListener("keydown", this.onKeyDown); - }, + } - onKeyDown: function(ev) { - if (ev.keyCode == 27) { // escape + onKeyDown = (ev) => { + if (ev.keyCode === 27) { // escape ev.stopPropagation(); ev.preventDefault(); this.props.onFinished(); } - }, + }; - onAction: function(payload) { + onAction = (payload) => { if (payload.action === 'close_scalar') { this.props.onFinished(); } - }, + }; - render: function() { - return ( - - ); - }, -}); + render() { + return ; + } +} \ No newline at end of file From 6cc443cd018af9b416e5470b60cddf80d2267334 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 17 Jun 2019 15:27:35 -0600 Subject: [PATCH 053/188] spelling --- src/CallHandler.js | 2 +- src/IntegrationManager.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CallHandler.js b/src/CallHandler.js index e47209eebe..5b58400ae6 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -344,7 +344,7 @@ function _onAction(payload) { } async function _startCallApp(roomId, type) { - // check for a working intgrations manager. Technically we could put + // check for a working integrations manager. Technically we could put // the state event in anyway, but the resulting widget would then not // work for us. Better that the user knows before everyone else in the // room sees it. diff --git a/src/IntegrationManager.js b/src/IntegrationManager.js index 165ee6390d..4ca7fc57dc 100644 --- a/src/IntegrationManager.js +++ b/src/IntegrationManager.js @@ -44,7 +44,7 @@ export default class IntegrationManager { } /** - * Launch the integrations manager on the stickers integration page + * Launch the integrations manager on the specified integration page * @param {string} integName integration / widget type * @param {string} integId integration / widget ID * @param {function} onFinished Callback to invoke on integration manager close From a5f296457f53bd264c8e392c0d54b31c465eb508 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 17 Jun 2019 15:29:19 -0600 Subject: [PATCH 054/188] Make the Manage Integrations Button defer scalar auth to the manager This moves the responsibility of creating a URL to open from the button (and other components) to the integrations manager dialog itself. By doing this, we also cut down on scalar API calls because we don't pick up on account information until the user opens the dialog. --- .../views/settings/_IntegrationsManager.scss | 13 +++ src/ScalarAuthClient.js | 11 ++- .../views/elements/ManageIntegsButton.js | 87 +++---------------- .../views/settings/IntegrationsManager.js | 86 ++++++++++++++++-- src/i18n/strings/en_EN.json | 8 +- 5 files changed, 125 insertions(+), 80 deletions(-) diff --git a/res/css/views/settings/_IntegrationsManager.scss b/res/css/views/settings/_IntegrationsManager.scss index 93ee0e20fe..c5769d3645 100644 --- a/res/css/views/settings/_IntegrationsManager.scss +++ b/res/css/views/settings/_IntegrationsManager.scss @@ -29,3 +29,16 @@ limitations under the License. width: 100%; height: 100%; } + +.mx_IntegrationsManager_loading h3 { + text-align: center; +} + +.mx_IntegrationsManager_error { + text-align: center; + padding-top: 20px; +} + +.mx_IntegrationsManager_error h3 { + color: $warning-color; +} \ No newline at end of file diff --git a/src/ScalarAuthClient.js b/src/ScalarAuthClient.js index 24979aff65..27d8f0d0da 100644 --- a/src/ScalarAuthClient.js +++ b/src/ScalarAuthClient.js @@ -29,6 +29,14 @@ class ScalarAuthClient { this.scalarToken = null; } + /** + * Determines if setting up a ScalarAuthClient is even possible + * @returns {boolean} true if possible, false otherwise. + */ + static isPossible() { + return SdkConfig.get()['integrations_rest_url'] && SdkConfig.get()['integrations_ui_url']; + } + connect() { return this.getScalarToken().then((tok) => { this.scalarToken = tok; @@ -41,7 +49,8 @@ class ScalarAuthClient { // Returns a scalar_token string getScalarToken() { - const token = window.localStorage.getItem("mx_scalar_token"); + let token = this.scalarToken; + if (!token) token = window.localStorage.getItem("mx_scalar_token"); if (!token) { return this.registerForToken(); diff --git a/src/components/views/elements/ManageIntegsButton.js b/src/components/views/elements/ManageIntegsButton.js index 165cd20eb5..f5d087e452 100644 --- a/src/components/views/elements/ManageIntegsButton.js +++ b/src/components/views/elements/ManageIntegsButton.js @@ -1,5 +1,6 @@ /* Copyright 2017 New Vector 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. @@ -17,10 +18,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import sdk from '../../../index'; -import classNames from 'classnames'; -import SdkConfig from '../../../SdkConfig'; import ScalarAuthClient from '../../../ScalarAuthClient'; -import ScalarMessaging from '../../../ScalarMessaging'; import Modal from "../../../Modal"; import { _t } from '../../../languageHandler'; import AccessibleButton from './AccessibleButton'; @@ -28,85 +26,28 @@ import AccessibleButton from './AccessibleButton'; export default class ManageIntegsButton extends React.Component { constructor(props) { super(props); - - this.state = { - scalarError: null, - }; - - this.onManageIntegrations = this.onManageIntegrations.bind(this); } - componentWillMount() { - ScalarMessaging.startListening(); - this.scalarClient = null; - - if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { - this.scalarClient = new ScalarAuthClient(); - this.scalarClient.connect().done(() => { - this.forceUpdate(); - }, (err) => { - this.setState({scalarError: err}); - console.error('Error whilst initialising scalarClient for ManageIntegsButton', err); - }); - } - } - - componentWillUnmount() { - ScalarMessaging.stopListening(); - } - - onManageIntegrations(ev) { + onManageIntegrations = (ev) => { ev.preventDefault(); - if (this.state.scalarError && !this.scalarClient.hasCredentials()) { - return; - } + const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); - this.scalarClient.connect().done(() => { - Modal.createDialog(IntegrationsManager, { - src: (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? - this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room) : - null, - }, "mx_IntegrationsManager"); - }, (err) => { - this.setState({scalarError: err}); - console.error('Error ensuring a valid scalar_token exists', err); - }); - } + Modal.createDialog(IntegrationsManager, { + room: this.props.room, + }, "mx_IntegrationsManager"); + }; render() { let integrationsButton =
; - let integrationsWarningTriangle =
; - let integrationsErrorPopup =
; - if (this.scalarClient !== null) { - const integrationsButtonClasses = classNames({ - mx_RoomHeader_button: true, - mx_RoomHeader_manageIntegsButton: true, - mx_ManageIntegsButton_error: !!this.state.scalarError, - }); - - if (this.state.scalarError && !this.scalarClient.hasCredentials()) { - integrationsWarningTriangle = ; - // Popup shown when hovering over integrationsButton_error (via CSS) - integrationsErrorPopup = ( - - { _t('Could not connect to the integration server') } - - ); - } - + if (ScalarAuthClient.isPossible()) { + const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); integrationsButton = ( - - { integrationsWarningTriangle } - { integrationsErrorPopup } - - ); + /> + ) } return integrationsButton; diff --git a/src/components/views/settings/IntegrationsManager.js b/src/components/views/settings/IntegrationsManager.js index 2e292b63fc..d9bf6351e9 100644 --- a/src/components/views/settings/IntegrationsManager.js +++ b/src/components/views/settings/IntegrationsManager.js @@ -18,20 +18,68 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import sdk from '../../../index'; -import MatrixClientPeg from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; -import Modal from '../../../Modal'; import dis from '../../../dispatcher'; +import ScalarAuthClient from '../../../ScalarAuthClient'; export default class IntegrationsManager extends React.Component { static propTypes = { - // the source of the integration manager being embedded - src: PropTypes.string.isRequired, + // the room object where the integrations manager should be opened in + room: PropTypes.object.isRequired, + + // the screen name to open + screen: PropTypes.string, + + // the integration ID to open + integrationId: PropTypes.string, // callback when the manager is dismissed onFinished: PropTypes.func.isRequired, }; + constructor(props) { + super(props); + + this.state = { + loading: true, + configured: ScalarAuthClient.isPossible(), + connected: false, // true if a `src` is set and able to be connected to + src: null, // string for where to connect to + }; + } + + componentWillMount() { + if (!this.state.configured) return; + + const scalarClient = new ScalarAuthClient(); + scalarClient.connect().then(() => { + const hasCredentials = scalarClient.hasCredentials(); + if (!hasCredentials) { + this.setState({ + connected: false, + loading: false, + }); + } else { + const src = scalarClient.getScalarInterfaceUrlForRoom( + this.props.room, + this.props.screen, + this.props.integrationId, + ); + this.setState({ + loading: false, + connected: true, + src: src, + }); + } + }).catch(err => { + console.error(err); + this.setState({ + loading: false, + connected: false, + }); + }) + } + componentDidMount() { this.dispatcherRef = dis.register(this.onAction); document.addEventListener("keydown", this.onKeyDown); @@ -57,6 +105,34 @@ export default class IntegrationsManager extends React.Component { }; render() { - return ; + if (!this.state.configured) { + return ( +
+

{_t("No integrations server configured")}

+

{_t("This Riot instance does not have an integrations server configured.")}

+
+ ); + } + + if (this.state.loading) { + const Spinner = sdk.getComponent("elements.Spinner"); + return ( +
+

{_t("Connecting to integrations server...")}

+ +
+ ); + } + + if (!this.state.connected) { + return ( +
+

{_t("Cannot connect to integrations server")}

+

{_t("The integrations server is offline or it cannot reach your homeserver.")}

+
+ ); + } + + return ; } } \ No newline at end of file diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 53fd82f6f2..c1fd3662ee 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -483,6 +483,11 @@ "Email Address": "Email Address", "Disable Notifications": "Disable Notifications", "Enable Notifications": "Enable Notifications", + "No integrations server configured": "No integrations server configured", + "This Riot instance does not have an integrations server configured.": "This Riot instance does not have an integrations server configured.", + "Connecting to integrations server...": "Connecting to integrations server...", + "Cannot connect to integrations server": "Cannot connect to integrations server", + "The integrations server is offline or it cannot reach your homeserver.": "The integrations server is offline or it cannot reach your homeserver.", "Delete Backup": "Delete Backup", "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "Are you sure? You will lose your encrypted messages if your keys are not backed up properly.", "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", @@ -864,6 +869,8 @@ "This Room": "This Room", "All Rooms": "All Rooms", "Search…": "Search…", + "Failed to connect to integrations server": "Failed to connect to integrations server", + "No integrations server is configured to manage stickers with": "No integrations server is configured to manage stickers with", "You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled", "Add some now": "Add some now", "Stickerpack": "Stickerpack", @@ -1017,7 +1024,6 @@ "Rotate Right": "Rotate Right", "Rotate clockwise": "Rotate clockwise", "Download this file": "Download this file", - "Integrations Error": "Integrations Error", "Manage Integrations": "Manage Integrations", "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times", From ebabc5238d068509e3c8dd9d40c6d21ccd8c1218 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 17 Jun 2019 15:30:01 -0600 Subject: [PATCH 055/188] Port integration manager class to new dialog props --- src/IntegrationManager.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/IntegrationManager.js b/src/IntegrationManager.js index 4ca7fc57dc..9cd984f12f 100644 --- a/src/IntegrationManager.js +++ b/src/IntegrationManager.js @@ -1,5 +1,6 @@ /* Copyright 2017 New Vector 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. @@ -19,11 +20,14 @@ import SdkConfig from './SdkConfig'; import ScalarMessaging from './ScalarMessaging'; import ScalarAuthClient from './ScalarAuthClient'; import RoomViewStore from './stores/RoomViewStore'; +import MatrixClientPeg from "./MatrixClientPeg"; if (!global.mxIntegrationManager) { global.mxIntegrationManager = {}; } +// TODO: TravisR - What even is this? + export default class IntegrationManager { static _init() { if (!global.mxIntegrationManager.client || !global.mxIntegrationManager.connected) { @@ -62,16 +66,10 @@ export default class IntegrationManager { console.error("Scalar error", global.mxIntegrationManager); return; } - const integType = 'type_' + integName; - const src = (global.mxIntegrationManager.client && global.mxIntegrationManager.client.hasCredentials()) ? - global.mxIntegrationManager.client.getScalarInterfaceUrlForRoom( - {roomId: RoomViewStore.getRoomId()}, - integType, - integId, - ) : - null; Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { - src: src, + room: MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()), + screen: 'type_' + integName, + integrationId: integId, onFinished: onFinished, }, "mx_IntegrationsManager"); } From f699fed720a2689b48810685c3df2f034841defa Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 17 Jun 2019 15:30:24 -0600 Subject: [PATCH 056/188] Defer sticker picker scalar auth to integration manager dialog or when needed, instead of up front. --- src/components/views/rooms/Stickerpicker.js | 78 +++++++++++---------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index a0e3f1b7a9..809d948840 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ import React from 'react'; -import { _t } from '../../../languageHandler'; +import {_t, _td} from '../../../languageHandler'; import AppTile from '../elements/AppTile'; import MatrixClientPeg from '../../../MatrixClientPeg'; import Modal from '../../../Modal'; @@ -53,6 +53,9 @@ export default class Stickerpicker extends React.Component { this.popoverWidth = 300; this.popoverHeight = 300; + // This is loaded by _acquireScalarClient on an as-needed basis. + this.scalarClient = null; + this.state = { showStickers: false, imError: null, @@ -63,14 +66,34 @@ export default class Stickerpicker extends React.Component { }; } - _removeStickerpickerWidgets() { + _acquireScalarClient() { + if (this.scalarClient) return Promise.resolve(this.scalarClient); + if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { + this.scalarClient = new ScalarAuthClient(); + return this.scalarClient.connect().then(() => { + this.forceUpdate(); + return this.scalarClient; + }).catch((e) => { + this._imError(_td("Failed to connect to integrations server"), e); + }); + } else { + this._imError(_td("No integrations server is configured to manage stickers with")); + } + } + + async _removeStickerpickerWidgets() { + const scalarClient = await this._acquireScalarClient(); console.warn('Removing Stickerpicker widgets'); if (this.state.widgetId) { - this.scalarClient.disableWidgetAssets(widgetType, this.state.widgetId).then(() => { - console.warn('Assets disabled'); - }).catch((err) => { - console.error('Failed to disable assets'); - }); + if (scalarClient) { + scalarClient.disableWidgetAssets(widgetType, this.state.widgetId).then(() => { + console.warn('Assets disabled'); + }).catch((err) => { + console.error('Failed to disable assets'); + }); + } else { + console.error("Cannot disable assets: no scalar client"); + } } else { console.warn('No widget ID specified, not disabling assets'); } @@ -87,19 +110,7 @@ export default class Stickerpicker extends React.Component { // Close the sticker picker when the window resizes window.addEventListener('resize', this._onResize); - this.scalarClient = null; - if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { - this.scalarClient = new ScalarAuthClient(); - this.scalarClient.connect().then(() => { - this.forceUpdate(); - }).catch((e) => { - this._imError("Failed to connect to integrations server", e); - }); - } - - if (!this.state.imError) { - this.dispatcherRef = dis.register(this._onWidgetAction); - } + this.dispatcherRef = dis.register(this._onWidgetAction); // Track updates to widget state in account data MatrixClientPeg.get().on('accountData', this._updateWidget); @@ -126,7 +137,7 @@ export default class Stickerpicker extends React.Component { console.error(errorMsg, e); this.setState({ showStickers: false, - imError: errorMsg, + imError: _t(errorMsg), }); } @@ -337,24 +348,15 @@ export default class Stickerpicker extends React.Component { /** * Launch the integrations manager on the stickers integration page */ - _launchManageIntegrations() { + async _launchManageIntegrations() { const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); - this.scalarClient.connect().done(() => { - const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? - this.scalarClient.getScalarInterfaceUrlForRoom( - this.props.room, - 'type_' + widgetType, - this.state.widgetId, - ) : - null; - Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { - src: src, - }, "mx_IntegrationsManager"); - this.setState({showStickers: false}); - }, (err) => { - this.setState({imError: err}); - console.error('Error ensuring a valid scalar_token exists', err); - }); + + // The integrations manager will handle scalar auth for us. + Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { + room: this.props.room, + screen: `type_${widgetType}`, + integrationId: this.state.widgetId, + }, "mx_IntegrationsManager"); } render() { From d2d0cb2e9e6f671c380eb68228f024dcb5bab009 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 17 Jun 2019 15:34:30 -0600 Subject: [PATCH 057/188] Port AppTile (widgets) over to new integration manager dialog props --- src/components/views/elements/AppTile.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 959cee7ace..60bc8a337e 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -240,19 +240,13 @@ export default class AppTile extends React.Component { if (this.props.onEditClick) { this.props.onEditClick(); } else { + // The dialog handles scalar auth for us const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); - this._scalarClient.connect().done(() => { - const src = this._scalarClient.getScalarInterfaceUrlForRoom( - this.props.room, 'type_' + this.props.type, this.props.id); - Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { - src: src, - }, "mx_IntegrationsManager"); - }, (err) => { - this.setState({ - error: err.message, - }); - console.error('Error ensuring a valid scalar_token exists', err); - }); + Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { + room: this.props.room, + screen: 'type_' + this.props.type, + integrationId: this.props.id + }, "mx_IntegrationsManager"); } } From 974a11ed201d11a08bfa165d61af6401cf34d957 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 17 Jun 2019 15:39:32 -0600 Subject: [PATCH 058/188] Defer scalar auth in AppsDrawer to widgets/manager dialog --- src/components/views/rooms/AppsDrawer.js | 30 ++++-------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/src/components/views/rooms/AppsDrawer.js b/src/components/views/rooms/AppsDrawer.js index e0e7a48b8c..3e5528996f 100644 --- a/src/components/views/rooms/AppsDrawer.js +++ b/src/components/views/rooms/AppsDrawer.js @@ -24,8 +24,6 @@ import AppTile from '../elements/AppTile'; import Modal from '../../../Modal'; import dis from '../../../dispatcher'; import sdk from '../../../index'; -import SdkConfig from '../../../SdkConfig'; -import ScalarAuthClient from '../../../ScalarAuthClient'; import ScalarMessaging from '../../../ScalarMessaging'; import { _t } from '../../../languageHandler'; import WidgetUtils from '../../../utils/WidgetUtils'; @@ -63,20 +61,6 @@ module.exports = React.createClass({ }, componentDidMount: function() { - this.scalarClient = null; - if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { - this.scalarClient = new ScalarAuthClient(); - this.scalarClient.connect().then(() => { - this.forceUpdate(); - }).catch((e) => { - console.log('Failed to connect to integrations server'); - // TODO -- Handle Scalar errors - // this.setState({ - // scalar_error: err, - // }); - }); - } - this.dispatcherRef = dis.register(this.onAction); }, @@ -144,16 +128,10 @@ module.exports = React.createClass({ _launchManageIntegrations: function() { const IntegrationsManager = sdk.getComponent('views.settings.IntegrationsManager'); - this.scalarClient.connect().done(() => { - const src = (this.scalarClient !== null && this.scalarClient.hasCredentials()) ? - this.scalarClient.getScalarInterfaceUrlForRoom(this.props.room, 'add_integ') : - null; - Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { - src: src, - }, 'mx_IntegrationsManager'); - }, (err) => { - console.error('Error ensuring a valid scalar_token exists', err); - }); + Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { + room: this.props.room, + screen: 'add_integ', + }, 'mx_IntegrationsManager'); }, onClickAddWidget: function(e) { From d58ab8e6d079b2a2f29c78b9a386cfdb56453491 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 17 Jun 2019 15:50:09 -0600 Subject: [PATCH 059/188] Remove excessive scalar auth checks in manager util class --- src/IntegrationManager.js | 65 ++++++++++----------------------------- 1 file changed, 16 insertions(+), 49 deletions(-) diff --git a/src/IntegrationManager.js b/src/IntegrationManager.js index 9cd984f12f..7a9d336e97 100644 --- a/src/IntegrationManager.js +++ b/src/IntegrationManager.js @@ -22,55 +22,22 @@ import ScalarAuthClient from './ScalarAuthClient'; import RoomViewStore from './stores/RoomViewStore'; import MatrixClientPeg from "./MatrixClientPeg"; -if (!global.mxIntegrationManager) { - global.mxIntegrationManager = {}; -} - -// TODO: TravisR - What even is this? - +// TODO: We should use this everywhere. export default class IntegrationManager { - static _init() { - if (!global.mxIntegrationManager.client || !global.mxIntegrationManager.connected) { - if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { - ScalarMessaging.startListening(); - global.mxIntegrationManager.client = new ScalarAuthClient(); - - return global.mxIntegrationManager.client.connect().then(() => { - global.mxIntegrationManager.connected = true; - }).catch((e) => { - console.error("Failed to connect to integrations server", e); - global.mxIntegrationManager.error = e; - }); - } else { - console.error('Invalid integration manager config', SdkConfig.get()); - } + /** + * Launch the integrations manager on the specified integration page + * @param {string} integName integration / widget type + * @param {string} integId integration / widget ID + * @param {function} onFinished Callback to invoke on integration manager close + */ + static async open(integName, integId, onFinished) { + // The dialog will take care of scalar auth for us + const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); + Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { + room: MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()), + screen: 'type_' + integName, + integrationId: integId, + onFinished: onFinished, + }, "mx_IntegrationsManager"); } - } - - /** - * Launch the integrations manager on the specified integration page - * @param {string} integName integration / widget type - * @param {string} integId integration / widget ID - * @param {function} onFinished Callback to invoke on integration manager close - */ - static async open(integName, integId, onFinished) { - await IntegrationManager._init(); - if (global.mxIntegrationManager.client) { - await global.mxIntegrationManager.client.connect(); - } else { - return; - } - const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); - if (global.mxIntegrationManager.error || - !(global.mxIntegrationManager.client && global.mxIntegrationManager.client.hasCredentials())) { - console.error("Scalar error", global.mxIntegrationManager); - return; - } - Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { - room: MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()), - screen: 'type_' + integName, - integrationId: integId, - onFinished: onFinished, - }, "mx_IntegrationsManager"); - } } From 8f6e8c1ec7dfe1ab5937ac5496a6506587e86c15 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 17 Jun 2019 15:51:14 -0600 Subject: [PATCH 060/188] Appease the linter --- src/components/views/elements/AppTile.js | 2 +- src/components/views/elements/ManageIntegsButton.js | 3 +-- src/components/views/settings/IntegrationsManager.js | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 60bc8a337e..034a3318a5 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -245,7 +245,7 @@ export default class AppTile extends React.Component { Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { room: this.props.room, screen: 'type_' + this.props.type, - integrationId: this.props.id + integrationId: this.props.id, }, "mx_IntegrationsManager"); } } diff --git a/src/components/views/elements/ManageIntegsButton.js b/src/components/views/elements/ManageIntegsButton.js index f5d087e452..ef5604dba6 100644 --- a/src/components/views/elements/ManageIntegsButton.js +++ b/src/components/views/elements/ManageIntegsButton.js @@ -21,7 +21,6 @@ import sdk from '../../../index'; import ScalarAuthClient from '../../../ScalarAuthClient'; import Modal from "../../../Modal"; import { _t } from '../../../languageHandler'; -import AccessibleButton from './AccessibleButton'; export default class ManageIntegsButton extends React.Component { constructor(props) { @@ -47,7 +46,7 @@ export default class ManageIntegsButton extends React.Component { title={_t("Manage Integrations")} onClick={this.onManageIntegrations} /> - ) + ); } return integrationsButton; diff --git a/src/components/views/settings/IntegrationsManager.js b/src/components/views/settings/IntegrationsManager.js index d9bf6351e9..754693b73e 100644 --- a/src/components/views/settings/IntegrationsManager.js +++ b/src/components/views/settings/IntegrationsManager.js @@ -77,7 +77,7 @@ export default class IntegrationsManager extends React.Component { loading: false, connected: false, }); - }) + }); } componentDidMount() { @@ -135,4 +135,4 @@ export default class IntegrationsManager extends React.Component { return ; } -} \ No newline at end of file +} From d5db0077edf820717a6d9c74d55cd83130cc3218 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 17 Jun 2019 15:53:11 -0600 Subject: [PATCH 061/188] Remove unused imports --- src/IntegrationManager.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/IntegrationManager.js b/src/IntegrationManager.js index 7a9d336e97..7ff9aff86e 100644 --- a/src/IntegrationManager.js +++ b/src/IntegrationManager.js @@ -16,9 +16,6 @@ limitations under the License. */ import Modal from './Modal'; import sdk from './index'; -import SdkConfig from './SdkConfig'; -import ScalarMessaging from './ScalarMessaging'; -import ScalarAuthClient from './ScalarAuthClient'; import RoomViewStore from './stores/RoomViewStore'; import MatrixClientPeg from "./MatrixClientPeg"; From 0692775f982a18ad64d8b8a44b1f4c9e6b57b627 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Mon, 17 Jun 2019 18:15:13 +0000 Subject: [PATCH 062/188] Translated using Weblate (Hungarian) Currently translated at 100.0% (1650 of 1650 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 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 3c9deaa0c6..3c9e35e75e 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -1995,5 +1995,11 @@ "Set a new custom sound": "Új egyedi hang beállítása", "Browse": "Böngész", "Use lowercase letters, numbers, dashes and underscores only": "Csak kisbetűt, számokat, kötőjeleket és aláhúzásokat használj", - "Cannot reach identity server": "Az azonosítási szerver nem érhető el" + "Cannot reach identity server": "Az azonosítási szerver nem érhető el", + "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Tudsz regisztrálni, de néhány funkció nem lesz elérhető amíg az azonosítási szerver újra elérhető lesz. Ha ezt a figyelmeztetést folyamatosan látod, ellenőrizd a beállításokat vagy vedd fel a kapcsolatot a szerver adminisztrátorával.", + "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "A jelszavadat újra beállíthatod, de néhány funkció nem lesz elérhető amíg az azonosítási szerver újra elérhető lesz. Ha ezt a figyelmeztetést folyamatosan látod, ellenőrizd a beállításokat vagy vedd fel a kapcsolatot a szerver adminisztrátorával.", + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Beléphetsz, de néhány funkció nem lesz elérhető amíg az azonosítási szerver újra elérhető lesz. Ha ezt a figyelmeztetést folyamatosan látod, ellenőrizd a beállításokat vagy vedd fel a kapcsolatot a szerver adminisztrátorával.", + "Log in to your new account.": "Belépés az új fiókodba.", + "You can now close this window or log in to your new account.": "Ezt az ablakot bezárhatod vagy beléphetsz az új fiókodba.", + "Registration Successful": "Regisztráció sikeres" } From 47579f37e8986ed4594c52642b68a8d5e2368ddb Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 18 Jun 2019 08:40:58 +0200 Subject: [PATCH 063/188] clarify why its always safe to not append @room at end while parsing --- src/editor/deserialize.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/editor/deserialize.js b/src/editor/deserialize.js index 200f3eb885..9307812bad 100644 --- a/src/editor/deserialize.js +++ b/src/editor/deserialize.js @@ -27,6 +27,9 @@ function parseAtRoomMentions(text, partCreator) { if (textPart.length) { parts.push(partCreator.plain(textPart)); } + // it's safe to never append @room after the last text + // as split will report an empty string at the end if + // `text` ended in @room. const isLast = i === arr.length - 1; if (!isLast) { parts.push(partCreator.atRoomPill(ATROOM)); From 3119feaa17036acaf5c788c0bc6635aeb6d43f1a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 18 Jun 2019 09:48:25 +0200 Subject: [PATCH 064/188] whitespace --- src/editor/parts.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/editor/parts.js b/src/editor/parts.js index 67d36e6660..dc2c1e69a2 100644 --- a/src/editor/parts.js +++ b/src/editor/parts.js @@ -266,7 +266,6 @@ class AtRoomPillPart extends RoomPillPart { } } - class UserPillPart extends PillPart { constructor(userId, displayName, member) { super(userId, displayName); From 1db505c6674b51e040023b925aa468c332437f75 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 18 Jun 2019 09:50:31 +0200 Subject: [PATCH 065/188] comment typo --- src/editor/deserialize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/deserialize.js b/src/editor/deserialize.js index 9307812bad..7e4e82affe 100644 --- a/src/editor/deserialize.js +++ b/src/editor/deserialize.js @@ -27,7 +27,7 @@ function parseAtRoomMentions(text, partCreator) { if (textPart.length) { parts.push(partCreator.plain(textPart)); } - // it's safe to never append @room after the last text + // it's safe to never append @room after the last textPart // as split will report an empty string at the end if // `text` ended in @room. const isLast = i === arr.length - 1; From d8c0f1200918fbf57efb95484fd82b36a32b193b Mon Sep 17 00:00:00 2001 From: Osoitz Date: Tue, 18 Jun 2019 04:26:26 +0000 Subject: [PATCH 066/188] Translated using Weblate (Basque) Currently translated at 100.0% (1651 of 1651 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/eu/ --- src/i18n/strings/eu.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/eu.json b/src/i18n/strings/eu.json index 5a4b8901e9..f0e3013090 100644 --- a/src/i18n/strings/eu.json +++ b/src/i18n/strings/eu.json @@ -1962,5 +1962,6 @@ "Invalid base_url for m.identity_server": "Baliogabeko base_url m.identity_server zerbitzariarentzat", "Log in to your new account.": "Hasi saioa zure kontu berrian.", "You can now close this window or log in to your new account.": "Itxi leiho hau edo hasi saioa zure kontu berrian.", - "Registration Successful": "Ongi erregistratuta" + "Registration Successful": "Ongi erregistratuta", + "Upload all": "Igo denak" } From 172924734087af186b893145ed1a8d2b9f369de0 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Tue, 18 Jun 2019 02:58:01 +0000 Subject: [PATCH 067/188] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (1651 of 1651 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index a4c5914392..26b1109abd 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -1996,5 +1996,6 @@ "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "您可以登入,但有些功能在身份識別伺服器重新上線前會沒辦法運作。如果您一直看到這個警告,請檢查您的設定或聯絡伺服器管理員。", "Log in to your new account.": "登入到您的新帳號。", "You can now close this window or log in to your new account.": "您現在可以關閉此視窗或登入到您的新帳號了。", - "Registration Successful": "註冊成功" + "Registration Successful": "註冊成功", + "Upload all": "上傳全部" } From f95f194b6a21d65d10e2ed02b0c3bfdef0481f22 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 18 Jun 2019 11:49:36 +0200 Subject: [PATCH 068/188] keep old arrow-up behaviour when editing is not enabled also, move caret at end/start checks before choosing what to do also, selectHistory shouldn't return a promise --- .../views/rooms/MessageComposerInput.js | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 7684e1dced..bf0287d376 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -1206,39 +1206,42 @@ export default class MessageComposerInput extends React.Component { onVerticalArrow = (e, up) => { if (e.ctrlKey || e.shiftKey || e.metaKey) return; - if (e.altKey) { + // selection must be collapsed + const selection = this.state.editorState.selection; + if (!selection.isCollapsed) return; + // and we must be at the edge of the document (up=start, down=end) + const document = this.state.editorState.document; + if (up) { + if (!selection.anchor.isAtStartOfNode(document)) return; + } else { + if (!selection.anchor.isAtEndOfNode(document)) return; + } + + const editingEnabled = SettingsStore.isFeatureEnabled("feature_message_editing"); + const shouldSelectHistory = (editingEnabled && e.altKey) || !editingEnabled; + const shouldEditLastMessage = editingEnabled && !e.altKey && up; + + if (shouldSelectHistory) { // Try select composer history const selected = this.selectHistory(up); if (selected) { // We're selecting history, so prevent the key event from doing anything else e.preventDefault(); } - } else if (!e.altKey && up) { - // Try edit the latest message - const selection = this.state.editorState.selection; - - // selection must be collapsed - if (!selection.isCollapsed) return; - const document = this.state.editorState.document; - - // and we must be at the edge of the document (up=start, down=end) - if (!selection.anchor.isAtStartOfNode(document)) return; - - if (!e.altKey) { - const editEvent = findEditableEvent(this.props.room, false); - if (editEvent) { - // We're selecting history, so prevent the key event from doing anything else - e.preventDefault(); - dis.dispatch({ - action: 'edit_event', - event: editEvent, - }); - } + } else if (shouldEditLastMessage) { + const editEvent = findEditableEvent(this.props.room, false); + if (editEvent) { + // We're selecting history, so prevent the key event from doing anything else + e.preventDefault(); + dis.dispatch({ + action: 'edit_event', + event: editEvent, + }); } } }; - selectHistory = async (up) => { + selectHistory = (up) => { const delta = up ? -1 : 1; // True if we are not currently selecting history, but composing a message From ea32d12c77d45535af65d9acf924dcaa46feccb5 Mon Sep 17 00:00:00 2001 From: Tirifto Date: Tue, 18 Jun 2019 09:11:23 +0000 Subject: [PATCH 069/188] Translated using Weblate (Esperanto) Currently translated at 83.9% (1385 of 1651 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/eo/ --- src/i18n/strings/eo.json | 52 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/eo.json b/src/i18n/strings/eo.json index 24d52f9331..133d96b3df 100644 --- a/src/i18n/strings/eo.json +++ b/src/i18n/strings/eo.json @@ -1547,5 +1547,55 @@ "Your Riot is misconfigured": "Via kliento Riot estas misagordita", "All devices for this user are trusted": "Ĉiuj aparatoj de tiu ĉi uzanto estas fidataj", "All devices in this encrypted room are trusted": "Ĉiuj aparatoj en ĉi tiu ĉifrita ĉambro estas fidataj", - "Your key share request has been sent - please check your other devices for key share requests.": "Via peto por havigo de ŝlosilo sendiĝis – bonvolu kontroli viajn aliajn aparatojn pro petoj." + "Your key share request has been sent - please check your other devices for key share requests.": "Via peto por havigo de ŝlosilo sendiĝis – bonvolu kontroli viajn aliajn aparatojn pro petoj.", + "At this time it is not possible to reply with an emote.": "Ankoraŭ ne eblas respondi per mieno.", + "Joining room …": "Aliĝanta al ĉambro …", + "Loading …": "Enleganta …", + "Rejecting invite …": "Rifuzanta inviton …", + "Join the conversation with an account": "Aliĝu al la interparolo per konto", + "Sign Up": "Registriĝi", + "Sign In": "Saluti", + "You were kicked from %(roomName)s by %(memberName)s": "%(memberName)s forpelis vin de %(roomName)s", + "Reason: %(reason)s": "Kialo: %(reason)s", + "Forget this room": "Forgesi ĉi tiun ĉambron", + "Re-join": "Re-aliĝi", + "You were banned from %(roomName)s by %(memberName)s": "%(memberName)s vin forbaris de %(roomName)s", + "Something went wrong with your invite to %(roomName)s": "Io misokazis al via invito al %(roomName)s", + "%(errcode)s was returned while trying to valide your invite. You could try to pass this information on to a room admin.": "Okazis eraro %(errcode)s dum kontrolado de via invito. Eble transdonu tiun informon al ĉambrestro.", + "You can only join it with a working invite.": "Vi povas aliĝi nur kun funkcianta invito.", + "You can still join it because this is a public room.": "Tamen vi povas aliĝi, ĉar ĉi tiu ĉambro estas publika.", + "Join the discussion": "Aliĝi al la diskuto", + "Try to join anyway": "Tamen provi aliĝi", + "This invite to %(roomName)s wasn't sent to your account": "Ĉi tiu invito al %(roomName)s ne sendiĝis al via konto", + "Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.": "Salutu per alia konto, petu inviton, aŭ aldonu la retpoŝtadreson %(email)s al ĉi tiu konto.", + "Do you want to chat with %(user)s?": "Ĉu vi volas babili kun %(user)s?", + "Do you want to join %(roomName)s?": "Ĉu vi volas aliĝi al %(roomName)s?", + " invited you": " vin invitis", + "You're previewing %(roomName)s. Want to join it?": "Vi antaŭrigardas ĉambron %(roomName)s. Ĉu vi volas aliĝi?", + "%(roomName)s can't be previewed. Do you want to join it?": "Vi ne povas antaŭrigardi ĉambron %(roomName)s. Ĉu vi al ĝi volas aliĝi?", + "This room doesn't exist. Are you sure you're at the right place?": "Ĉi tiu ĉambro ne ekzistas. Ĉu vi certe provas la ĝustan lokon?", + "Try again later, or ask a room admin to check if you have access.": "Reprovu poste, aŭ petu administranton kontroli, ĉu vi rajtas aliri.", + "%(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.": "Okazis eraro %(errcode)s dum aliro al la ĉambro. Se vi pensas, ke vi mise vidas la eraron, bonvolu raporti problemon.", + "Never lose encrypted messages": "Neniam perdu ĉifritajn mesaĝojn", + "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Mesaĝoj en ĉi tiu ĉambro estas tutvoje ĉifrataj. Nur vi kaj la ricevonto(j) havas la ŝlosilojn necesajn por ilin legi.", + "Securely back up your keys to avoid losing them. Learn more.": "Savkopiu sekure viajn ŝlosilojn por ilin ne perdi. Eksciu plion.", + "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Gradaltigo de la ĉambro forigos la nunan ĉambron kaj kreos novan kun la sama nomo.", + "You don't currently have any stickerpacks enabled": "Vi havas neniujn ŝaltitajn glumarkarojn", + "Add some now": "Iujn aldoni", + "Stickerpack": "Glumarkaro", + "Hide Stickers": "Kaŝi glumarkojn", + "Show Stickers": "Montri glumarkojn", + "Failed to revoke invite": "Malsukcesis senvalidigi inviton", + "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Ne povis senvalidigi inviton. Aŭ la servilo nun trairas problemon, aŭ vi ne havas sufiĉajn permesojn.", + "Continue With Encryption Disabled": "Pluigi sen ĉifrado", + "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. This action is irreversible.": "Ĉi tio igos vian konton daŭre neuzebla. Vi ne povos saluti, kaj neniu povos reregistri la saman identigilon de uzanto. Ĝi foririgos vian konton de ĉiuj enataj ĉambroj, kaj forigos detalojn de via konto de la identeca servilo. Tiun agon ne eblas malfari.", + "Deactivating your account does not by default cause us to forget messages you have sent. If you would like us to forget your messages, please tick the box below.": "Malaktivigo de via konto implicite ne forgesigas viajn mesaĝojn al ni. Se vi volas, ke ni ilin forgesu, bonvolu marki la suban markbutonon.", + "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Videbleco de mesaĝoj en Matrix similas tiun de retpoŝto. Nia forgeso de viaj mesaĝoj signifas, ke ili haviĝos al neniu nova aŭ neregistrita uzanto, sed registritaj uzantoj, kiuj jam havas viajn mesaĝojn, ankoraŭ povos aliri siajn kopiaĵojn.", + "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Bonvolu dum malaktivigo forgesi ĉiujn mesaĝojn, kiujn mi sendis. (Averto: tio vidigos al osaj uzantoj neplenajn interparolojn.)", + "Use Legacy Verification (for older clients)": "Uzi malnovecan kontrolon (por malnovaj klientoj)", + "Verify by comparing a short text string.": "Kontrolu per komparo de mallonga teksto.", + "Begin Verifying": "Komenci kontrolon", + "Waiting for partner to accept...": "Atendanta akcepton de kunulo…", + "Nothing appearing? Not all clients support interactive verification yet. .": "Ĉu neniu aperas? Ankoraŭ ne ĉiuj klientoj subtenas interagan kontrolon. .", + "Waiting for %(userId)s to confirm...": "Atendanta konfirmon de %(userId)s…" } From 3cd91feb2ade70754232217dae1a33a68e62492b Mon Sep 17 00:00:00 2001 From: random Date: Tue, 18 Jun 2019 09:10:05 +0000 Subject: [PATCH 070/188] Translated using Weblate (Italian) Currently translated at 100.0% (1651 of 1651 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 4f10d82009..06e3c7b573 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -1953,5 +1953,9 @@ "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Puoi accedere, ma alcune funzioni non saranno disponibili finchè il server identità non sarà tornato online. Se continui a vedere questo avviso, controlla la tua configurazione o contatta un amministratore del server.", "Unexpected error resolving identity server configuration": "Errore inaspettato risolvendo la configurazione del server identità", "Like or Dislike": "Piace o Non piace", - "Use lowercase letters, numbers, dashes and underscores only": "Usa solo minuscole, numeri, trattini e trattini bassi" + "Use lowercase letters, numbers, dashes and underscores only": "Usa solo minuscole, numeri, trattini e trattini bassi", + "Upload all": "Invia tutto", + "Log in to your new account.": "Accedi al tuo nuovo account.", + "You can now close this window or log in to your new account.": "Ora puoi chiudere questa finestra o accedere al tuo nuovo account.", + "Registration Successful": "Registrazione riuscita" } From 02c9e29937ee82c00945c015d5cb91973210c0c1 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 18 Jun 2019 13:47:33 +0200 Subject: [PATCH 071/188] use renamed method that also takes local redactions into account now --- 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 52fd6d9be4..1644d87a7e 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -592,7 +592,7 @@ module.exports = React.createClass({ readReceiptMap={this._readReceiptMap} showUrlPreview={this.props.showUrlPreview} checkUnmounting={this._isUnmounting} - eventSendStatus={mxEv.replacementOrOwnStatus()} + eventSendStatus={mxEv.getAssociatedLocalEchoStatus()} tileShape={this.props.tileShape} isTwelveHour={this.props.isTwelveHour} permalinkCreator={this.props.permalinkCreator} From f9188bca92a06aa67377fdcb1f9950346622f9fe Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 18 Jun 2019 13:48:51 +0200 Subject: [PATCH 072/188] reduced opacity for pending redactions --- res/css/views/rooms/_EventTile.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index a6194832a3..62632eab27 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -169,6 +169,9 @@ limitations under the License. .mx_EventTile_sending .mx_RoomPill { opacity: 0.5; } +.mx_EventTile_sending.mx_EventTile_redacted .mx_UnknownBody { + opacity: 0.4; +} .mx_EventTile_notSent { color: $event-notsent-color; From c4fc2a80894557aff41e8429ee4f9ee9b927070b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 18 Jun 2019 14:57:58 +0200 Subject: [PATCH 073/188] remove redundant localecho part from method name --- 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 1644d87a7e..8352872a2d 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -592,7 +592,7 @@ module.exports = React.createClass({ readReceiptMap={this._readReceiptMap} showUrlPreview={this.props.showUrlPreview} checkUnmounting={this._isUnmounting} - eventSendStatus={mxEv.getAssociatedLocalEchoStatus()} + eventSendStatus={mxEv.getAssociatedStatus()} tileShape={this.props.tileShape} isTwelveHour={this.props.isTwelveHour} permalinkCreator={this.props.permalinkCreator} From be37332bb07ef59822ba0c3fbe7444172db830c9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 18 Jun 2019 07:55:43 -0600 Subject: [PATCH 074/188] Further simplify usage of integrations --- src/FromWidgetPostMessageApi.js | 14 ++++++-- src/IntegrationManager.js | 40 --------------------- src/components/views/rooms/Stickerpicker.js | 4 +-- 3 files changed, 14 insertions(+), 44 deletions(-) delete mode 100644 src/IntegrationManager.js diff --git a/src/FromWidgetPostMessageApi.js b/src/FromWidgetPostMessageApi.js index 61c51d4a20..79e5206f50 100644 --- a/src/FromWidgetPostMessageApi.js +++ b/src/FromWidgetPostMessageApi.js @@ -17,9 +17,12 @@ limitations under the License. import URL from 'url'; import dis from './dispatcher'; -import IntegrationManager from './IntegrationManager'; import WidgetMessagingEndpoint from './WidgetMessagingEndpoint'; import ActiveWidgetStore from './stores/ActiveWidgetStore'; +import sdk from "./index"; +import Modal from "./Modal"; +import MatrixClientPeg from "./MatrixClientPeg"; +import RoomViewStore from "./stores/RoomViewStore"; const WIDGET_API_VERSION = '0.0.2'; // Current API version const SUPPORTED_WIDGET_API_VERSIONS = [ @@ -189,7 +192,14 @@ export default class FromWidgetPostMessageApi { const data = event.data.data || event.data.widgetData; const integType = (data && data.integType) ? data.integType : null; const integId = (data && data.integId) ? data.integId : null; - IntegrationManager.open(integType, integId); + + // The dialog will take care of scalar auth for us + const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); + Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { + room: MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()), + screen: 'type_' + integType, + integrationId: integId, + }, "mx_IntegrationsManager"); } else if (action === 'set_always_on_screen') { // This is a new message: there is no reason to support the deprecated widgetData here const data = event.data.data; diff --git a/src/IntegrationManager.js b/src/IntegrationManager.js deleted file mode 100644 index 7ff9aff86e..0000000000 --- a/src/IntegrationManager.js +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2017 New Vector 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. -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 Modal from './Modal'; -import sdk from './index'; -import RoomViewStore from './stores/RoomViewStore'; -import MatrixClientPeg from "./MatrixClientPeg"; - -// TODO: We should use this everywhere. -export default class IntegrationManager { - /** - * Launch the integrations manager on the specified integration page - * @param {string} integName integration / widget type - * @param {string} integId integration / widget ID - * @param {function} onFinished Callback to invoke on integration manager close - */ - static async open(integName, integId, onFinished) { - // The dialog will take care of scalar auth for us - const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); - Modal.createTrackedDialog('Integrations Manager', '', IntegrationsManager, { - room: MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()), - screen: 'type_' + integName, - integrationId: integId, - onFinished: onFinished, - }, "mx_IntegrationsManager"); - } -} diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index 809d948840..2dc174ceac 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -68,7 +68,7 @@ export default class Stickerpicker extends React.Component { _acquireScalarClient() { if (this.scalarClient) return Promise.resolve(this.scalarClient); - if (SdkConfig.get().integrations_ui_url && SdkConfig.get().integrations_rest_url) { + if (ScalarAuthClient.isPossible()) { this.scalarClient = new ScalarAuthClient(); return this.scalarClient.connect().then(() => { this.forceUpdate(); @@ -348,7 +348,7 @@ export default class Stickerpicker extends React.Component { /** * Launch the integrations manager on the stickers integration page */ - async _launchManageIntegrations() { + _launchManageIntegrations() { const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager"); // The integrations manager will handle scalar auth for us. From b6ca0ea6bf31f74a30ef031be9e1ac484854a7e3 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 18 Jun 2019 08:01:38 -0600 Subject: [PATCH 075/188] Appease the linter --- src/components/views/rooms/Stickerpicker.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index 2dc174ceac..6918810842 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -19,7 +19,6 @@ import AppTile from '../elements/AppTile'; import MatrixClientPeg from '../../../MatrixClientPeg'; import Modal from '../../../Modal'; import sdk from '../../../index'; -import SdkConfig from '../../../SdkConfig'; import ScalarAuthClient from '../../../ScalarAuthClient'; import dis from '../../../dispatcher'; import AccessibleButton from '../elements/AccessibleButton'; From 27a94bb250e7839110ac94e9e282fa7ddb446eb3 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 18 Jun 2019 15:46:23 +0100 Subject: [PATCH 076/188] js-sdk rc.2 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9af8cbf4b0..b125ba6b7a 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "linkifyjs": "^2.1.6", "lodash": "^4.13.1", "lolex": "2.3.2", - "matrix-js-sdk": "2.0.1-rc.1", + "matrix-js-sdk": "2.0.1-rc.2", "optimist": "^0.6.1", "pako": "^1.0.5", "png-chunks-extract": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index 5a7fc5ddb1..9bf63b8b47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4877,10 +4877,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.0.tgz#490b70e062ee24636536e3d9481e333733d00f2c" integrity sha512-3Zs9P/0zzwTob2pdgT0CHZuMbnSUSp8MB1bddfm+HDmnFWHGT4jvEZRf+2RuPoa+cjdn/z25SEt5gFTqdhvJAg== -matrix-js-sdk@2.0.1-rc.1: - version "2.0.1-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-2.0.1-rc.1.tgz#2590d6741d74af6b047e362eeea1b9a76c9c2143" - integrity sha512-t4EIK3xLiGdo8HrQeXYO58iw1e/959gidMvKfup8Pw8PJbRRQWXTe4DD0/Qytzq/zgbrx+1L4NxxceOzzwiwiw== +matrix-js-sdk@2.0.1-rc.2: + version "2.0.1-rc.2" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-2.0.1-rc.2.tgz#6a00e4264f7e27a6f71a25ee9cbd935fbfbbf759" + integrity sha512-mq93SfeI9cc+BPfjNQKNzdLmq7l3BgFos8fhMt9ToLJB/wEg4xJXU0Nc8+31dDd65IDa4tGSyyCL4mRtpT6Nmw== dependencies: another-json "^0.2.0" babel-runtime "^6.26.0" From 043cae2aa96198dadc0ba72b460ceb20d716e297 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 18 Jun 2019 15:52:29 +0100 Subject: [PATCH 077/188] Prepare changelog for v1.2.2-rc.2 --- CHANGELOG.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index acf920aef0..21f627597a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,50 @@ +Changes in [1.2.2-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.2.2-rc.2) (2019-06-18) +============================================================================================================= +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.2.2-rc.1...v1.2.2-rc.2) + + * Defer scalar API calls until they are needed + [\#3115](https://github.com/matrix-org/matrix-react-sdk/pull/3115) + * Blend pending redactions + [\#3117](https://github.com/matrix-org/matrix-react-sdk/pull/3117) + * Keep old arrow-up behaviour when editing is not enabled + [\#3116](https://github.com/matrix-org/matrix-react-sdk/pull/3116) + * Restore Composer History under shift-up & down + [\#3098](https://github.com/matrix-org/matrix-react-sdk/pull/3098) + * Allow changing server if validation has failed + [\#3114](https://github.com/matrix-org/matrix-react-sdk/pull/3114) + * Add Upload All button to UploadConfirmDialog + [\#3109](https://github.com/matrix-org/matrix-react-sdk/pull/3109) + * Re-enable register button + [\#3112](https://github.com/matrix-org/matrix-react-sdk/pull/3112) + * keep mx_Field stretching + [\#3111](https://github.com/matrix-org/matrix-react-sdk/pull/3111) + * Fix double-spinner + [\#3107](https://github.com/matrix-org/matrix-react-sdk/pull/3107) + * Fix display of canonicalAlias in group room info + [\#3110](https://github.com/matrix-org/matrix-react-sdk/pull/3110) + * Fix welcome user + [\#3106](https://github.com/matrix-org/matrix-react-sdk/pull/3106) + * Support editing emote messages + [\#3105](https://github.com/matrix-org/matrix-react-sdk/pull/3105) + * Use flex: 1 for mx_Field to replace all the calc(100% - 20px) and more + [\#3104](https://github.com/matrix-org/matrix-react-sdk/pull/3104) + * Use overflow on MemberInfo name/mxid so that the back button stays + [\#3099](https://github.com/matrix-org/matrix-react-sdk/pull/3099) + * Allow changing servers on nonfatal errors + [\#3102](https://github.com/matrix-org/matrix-react-sdk/pull/3102) + * Simplify email registration + [\#3101](https://github.com/matrix-org/matrix-react-sdk/pull/3101) + * Allow arrow keys navigation in autocomplete list + [\#2966](https://github.com/matrix-org/matrix-react-sdk/pull/2966) + * Edit unsent messages + [\#3097](https://github.com/matrix-org/matrix-react-sdk/pull/3097) + * Fix registration with email + non-default HS + [\#3096](https://github.com/matrix-org/matrix-react-sdk/pull/3096) + * Raise action bar above read marker + [\#3095](https://github.com/matrix-org/matrix-react-sdk/pull/3095) + * Console log more helpfully + [\#3094](https://github.com/matrix-org/matrix-react-sdk/pull/3094) + Changes in [1.2.2-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.2.2-rc.1) (2019-06-12) ============================================================================================================= [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.2.1...v1.2.2-rc.1) From a835d916e1145e5423a1106260604adf23929dd8 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 18 Jun 2019 15:52:29 +0100 Subject: [PATCH 078/188] v1.2.2-rc.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b125ba6b7a..5966e00103 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "1.2.2-rc.1", + "version": "1.2.2-rc.2", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From c8e121dc706da0bd8c1ef31b3ee42dd2e4f7281e Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 18 Jun 2019 18:43:14 +0100 Subject: [PATCH 079/188] Restore warning for if you're already logged in ...when clicking an email link. Although now we can complete the registration because we can do so without replacing your session. --- src/components/structures/MatrixChat.js | 39 -------------- .../structures/auth/Registration.js | 51 ++++++++++++++++--- src/i18n/strings/en_EN.json | 7 +-- 3 files changed, 49 insertions(+), 48 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index fb35ab548b..d0f5a7e005 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1709,45 +1709,6 @@ export default React.createClass({ // returns a promise which resolves to the new MatrixClient onRegistered: function(credentials) { - if (this.state.register_session_id) { - // The user came in through an email validation link. To avoid overwriting - // their session, check to make sure the session isn't someone else, and - // isn't a guest user since we'll usually have set a guest user session before - // starting the registration process. This isn't perfect since it's possible - // the user had a separate guest session they didn't actually mean to replace. - const sessionOwner = Lifecycle.getStoredSessionOwner(); - const sessionIsGuest = Lifecycle.getStoredSessionIsGuest(); - if (sessionOwner && !sessionIsGuest && sessionOwner !== credentials.userId) { - console.log( - `Found a session for ${sessionOwner} but ${credentials.userId} is trying to verify their ` + - `email address. Restoring the session for ${sessionOwner} with warning.`, - ); - this._loadSession(); - - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - // N.B. first param is passed to piwik and so doesn't want i18n - Modal.createTrackedDialog('Existing session on register', '', - QuestionDialog, { - title: _t('You are logged in to another account'), - description: _t( - "Thank you for verifying your email! The account you're logged into here " + - "(%(sessionUserId)s) appears to be different from the account you've verified an " + - "email for (%(verifiedUserId)s). If you would like to log in to %(verifiedUserId2)s, " + - "please log out first.", { - sessionUserId: sessionOwner, - verifiedUserId: credentials.userId, - - // TODO: Fix translations to support reusing variables. - // https://github.com/vector-im/riot-web/issues/9086 - verifiedUserId2: credentials.userId, - }, - ), - hasCancelButton: false, - }); - - return MatrixClientPeg.get(); - } - } return Lifecycle.setLoggedIn(credentials); }, diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js index 662444379f..e825dd7034 100644 --- a/src/components/structures/auth/Registration.js +++ b/src/components/structures/auth/Registration.js @@ -97,6 +97,13 @@ module.exports = React.createClass({ // Our matrix client - part of state because we can't render the UI auth // component without it. matrixClient: null, + + // The user ID we've just registered + registeredUsername: null, + + // if a different user ID to the one we just registered is logged in, + // this is the user ID that's logged in. + differentLoggedInUserId: null, }; }, @@ -297,7 +304,25 @@ module.exports = React.createClass({ const newState = { doingUIAuth: false, + registeredUsername: response.user_id, }; + + // The user came in through an email validation link. To avoid overwriting + // their session, check to make sure the session isn't someone else, and + // isn't a guest user since we'll usually have set a guest user session before + // starting the registration process. This isn't perfect since it's possible + // the user had a separate guest session they didn't actually mean to replace. + const sessionOwner = Lifecycle.getStoredSessionOwner(); + const sessionIsGuest = Lifecycle.getStoredSessionIsGuest(); + if (sessionOwner && !sessionIsGuest && sessionOwner !== response.userId) { + console.log( + `Found a session for ${sessionOwner} but ${response.userId} has just registered.`, + ); + newState.differentLoggedInUserId = sessionOwner; + } else { + newState.differentLoggedInUserId = null; + } + if (response.access_token) { const cli = await this.props.onLoggedIn({ userId: response.user_id, @@ -538,6 +563,7 @@ module.exports = React.createClass({ const AuthHeader = sdk.getComponent('auth.AuthHeader'); const AuthBody = sdk.getComponent("auth.AuthBody"); const AuthPage = sdk.getComponent('auth.AuthPage'); + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); let errorText; const err = this.state.errorText; @@ -574,28 +600,41 @@ module.exports = React.createClass({ let body; if (this.state.completedNoSignin) { let regDoneText; - if (this.state.formVals.password) { + if (this.state.differentLoggedInUserId) { + regDoneText =
+

{_t( + "Your new account (%(newAccountId)s) is registered, but you're already " + + "logged into a different account (%(loggedInUserId)s).", { + newAccountId: this.state.registeredUsername, + loggedInUserId: this.state.differentLoggedInUserId, + }, + )}

+

+ {_t("Continue with previous account")} +

+
; + } else if (this.state.formVals.password) { // We're the client that started the registration - regDoneText = _t( + regDoneText =

{_t( "Log in to your new account.", {}, { a: (sub) => {sub}, }, - ); + )}

; } else { // We're not the original client: the user probably got to us by clicking the // email validation link. We can't offer a 'go straight to your account' link // as we don't have the original creds. - regDoneText = _t( + regDoneText =

{_t( "You can now close this window or log in to your new account.", {}, { a: (sub) => {sub}, }, - ); + )}

; } body =

{_t("Registration Successful")}

-

{ regDoneText }

+ { regDoneText }
; } else { body =
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 69af5c9d75..606aaa951c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1254,8 +1254,8 @@ "Unknown devices": "Unknown devices", "Upload files (%(current)s of %(total)s)": "Upload files (%(current)s of %(total)s)", "Upload files": "Upload files", - "Upload": "Upload", "Upload all": "Upload all", + "Upload": "Upload", "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.": "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.", "These files are too large to upload. The file size limit is %(limit)s.": "These files are too large to upload. The file size limit is %(limit)s.", "Some files are too large to be uploaded. The file size limit is %(limit)s.": "Some files are too large to be uploaded. The file size limit is %(limit)s.", @@ -1456,8 +1456,6 @@ "Review terms and conditions": "Review terms and conditions", "Old cryptography data detected": "Old cryptography data detected", "Data from an older version of Riot has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Data from an older version of Riot has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.", - "You are logged in to another account": "You are logged in to another account", - "Thank you for verifying your email! The account you're logged into here (%(sessionUserId)s) appears to be different from the account you've verified an email for (%(verifiedUserId)s). If you would like to log in to %(verifiedUserId2)s, please log out first.": "Thank you for verifying your email! The account you're logged into here (%(sessionUserId)s) appears to be different from the account you've verified an email for (%(verifiedUserId)s). If you would like to log in to %(verifiedUserId2)s, please log out first.", "Logout": "Logout", "Your Communities": "Your Communities", "Did you know: you can use communities to filter your Riot.im experience!": "Did you know: you can use communities to filter your Riot.im experience!", @@ -1564,6 +1562,9 @@ "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", "Unable to query for supported registration methods.": "Unable to query for supported registration methods.", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).", + "Continue with previous account": "Continue with previous account", + "Sign out of previous account": "Sign out of previous account", "Log in to your new account.": "Log in to your new account.", "You can now close this window or log in to your new account.": "You can now close this window or log in to your new account.", "Registration Successful": "Registration Successful", From e69e9dd70532104c3220101e6cfe6cf39d7a4b87 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Wed, 19 Jun 2019 02:29:24 +0000 Subject: [PATCH 080/188] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (1660 of 1660 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index 26b1109abd..b4b1cd1f05 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -1997,5 +1997,15 @@ "Log in to your new account.": "登入到您的新帳號。", "You can now close this window or log in to your new account.": "您現在可以關閉此視窗或登入到您的新帳號了。", "Registration Successful": "註冊成功", - "Upload all": "上傳全部" + "Upload all": "上傳全部", + "No integrations server configured": "沒有設定好的整合伺服器", + "This Riot instance does not have an integrations server configured.": "這個 Riot 站台沒有設定整合伺服器。", + "Connecting to integrations server...": "正在連線到整合伺服器……", + "Cannot connect to integrations server": "無法連線到整合伺服器", + "The integrations server is offline or it cannot reach your homeserver.": "整合伺服器已離線或無法連線到您的家伺服器。", + "Unnamed microphone": "未命名的麥克風", + "Unnamed audio output": "未命名的音訊輸出", + "Unnamed camera": "未命名的攝影機", + "Failed to connect to integrations server": "連線到整合伺服器失敗", + "No integrations server is configured to manage stickers with": "未設定整合伺服器來管理貼圖" } From 6970552a873e425a56f136c121d19ccec3e1c65b Mon Sep 17 00:00:00 2001 From: Szimszon Date: Tue, 18 Jun 2019 18:33:29 +0000 Subject: [PATCH 081/188] Translated using Weblate (Hungarian) Currently translated at 100.0% (1660 of 1660 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 | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 3c9e35e75e..edae9eb6be 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -2001,5 +2001,16 @@ "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Beléphetsz, de néhány funkció nem lesz elérhető amíg az azonosítási szerver újra elérhető lesz. Ha ezt a figyelmeztetést folyamatosan látod, ellenőrizd a beállításokat vagy vedd fel a kapcsolatot a szerver adminisztrátorával.", "Log in to your new account.": "Belépés az új fiókodba.", "You can now close this window or log in to your new account.": "Ezt az ablakot bezárhatod vagy beléphetsz az új fiókodba.", - "Registration Successful": "Regisztráció sikeres" + "Registration Successful": "Regisztráció sikeres", + "No integrations server configured": "Integrációs szerver nincs beállítva", + "This Riot instance does not have an integrations server configured.": "Ennek a Riot kliensnek nincs beállítva integrációs szerver.", + "Connecting to integrations server...": "Integrációs szerverhez csatlakozás...", + "Cannot connect to integrations server": "Az integrációs szerverhez nem lehet kapcsolódni", + "The integrations server is offline or it cannot reach your homeserver.": "Az integrációs szerver nem működik vagy nem tudja elérni a matrix szerveredet.", + "Unnamed microphone": "Név nélküli mikrofon", + "Unnamed audio output": "Név nélküli hang kimenet", + "Unnamed camera": "Név nélküli kamera", + "Failed to connect to integrations server": "Az integrációs szerverhez nem sikerült csatlakozni", + "No integrations server is configured to manage stickers with": "Nincs integrációs szerver konfigurálva amivel a matricákat lehetne kezelni", + "Upload all": "Mindet feltölt" } From 4ec7a8ddffe0dc417bf6616a243bc2ea5d2a8ba1 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 19 Jun 2019 11:26:11 +0100 Subject: [PATCH 082/188] Fix casing of TooltipButton My brain can't deal with two different ways to write "Tooltip", so this converges the naming to match the rest of the code base. Separate commits will fix up the file names for case-insensitive file systems. --- res/css/_components.scss | 2 +- res/css/views/elements/_ToolTipButton.scss | 9 +++++---- src/components/structures/GroupView.js | 5 +++-- src/components/views/elements/ToolTipButton.js | 9 +++++---- src/components/views/rooms/EventTile.js | 5 +++-- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/res/css/_components.scss b/res/css/_components.scss index 843f314bd1..582dc59517 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -99,8 +99,8 @@ @import "./views/elements/_SyntaxHighlight.scss"; @import "./views/elements/_TextWithTooltip.scss"; @import "./views/elements/_ToggleSwitch.scss"; -@import "./views/elements/_ToolTipButton.scss"; @import "./views/elements/_Tooltip.scss"; +@import "./views/elements/_TooltipButton.scss"; @import "./views/elements/_Validation.scss"; @import "./views/globals/_MatrixToolbar.scss"; @import "./views/groups/_GroupPublicityToggle.scss"; diff --git a/res/css/views/elements/_ToolTipButton.scss b/res/css/views/elements/_ToolTipButton.scss index c496e67515..6ea36c800e 100644 --- a/res/css/views/elements/_ToolTipButton.scss +++ b/res/css/views/elements/_ToolTipButton.scss @@ -1,5 +1,6 @@ /* Copyright 2017 New Vector 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,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_ToolTipButton { +.mx_TooltipButton { display: inline-block; width: 11px; height: 11px; @@ -33,17 +34,17 @@ limitations under the License. cursor: pointer; } -.mx_ToolTipButton:hover { +.mx_TooltipButton:hover { opacity: 1.0; } -.mx_ToolTipButton_container { +.mx_TooltipButton_container { position: relative; top: -18px; left: 4px; } -.mx_ToolTipButton_helpText { +.mx_TooltipButton_helpText { width: 400px; text-align: start; line-height: 17px !important; diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index cdfbe26fea..7bf5400942 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -1,6 +1,7 @@ /* Copyright 2017 Vector Creations Ltd. Copyright 2017, 2018 New Vector 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. @@ -861,9 +862,9 @@ export default React.createClass({ const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const TintableSvg = sdk.getComponent('elements.TintableSvg'); const Spinner = sdk.getComponent('elements.Spinner'); - const ToolTipButton = sdk.getComponent('elements.ToolTipButton'); + const TooltipButton = sdk.getComponent('elements.TooltipButton'); - const roomsHelpNode = this.state.editing ? :
; return ( -
+
? { tip }
diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 9837b4a029..7d11ddac61 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -1,6 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 New Vector 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. @@ -670,13 +671,13 @@ module.exports = withMatrixClient(React.createClass({ {'requestLink': (sub) => { sub }}, ); - const ToolTipButton = sdk.getComponent('elements.ToolTipButton'); + const TooltipButton = sdk.getComponent('elements.TooltipButton'); const keyRequestInfo = isEncryptionFailure ?
{ keyRequestInfoContent } - +
: null; let reactionsRow; From ab4c5f0152a27d029e88aa52204dd57059dbdaab Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 19 Jun 2019 11:30:37 +0100 Subject: [PATCH 083/188] TooltipButton rename step 1 of 2 --- .../elements/{_ToolTipButton.scss => _TooltipButton.scss.tmp} | 0 .../views/elements/{ToolTipButton.js => TooltipButton.js.tmp} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename res/css/views/elements/{_ToolTipButton.scss => _TooltipButton.scss.tmp} (100%) rename src/components/views/elements/{ToolTipButton.js => TooltipButton.js.tmp} (100%) diff --git a/res/css/views/elements/_ToolTipButton.scss b/res/css/views/elements/_TooltipButton.scss.tmp similarity index 100% rename from res/css/views/elements/_ToolTipButton.scss rename to res/css/views/elements/_TooltipButton.scss.tmp diff --git a/src/components/views/elements/ToolTipButton.js b/src/components/views/elements/TooltipButton.js.tmp similarity index 100% rename from src/components/views/elements/ToolTipButton.js rename to src/components/views/elements/TooltipButton.js.tmp From bb4b5d779805d1cedd3af9ed071ef5dc96c6fe0e Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 19 Jun 2019 11:32:03 +0100 Subject: [PATCH 084/188] TooltipButton rename step 2 of 2 --- .../elements/{_TooltipButton.scss.tmp => _TooltipButton.scss} | 0 .../views/elements/{TooltipButton.js.tmp => TooltipButton.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename res/css/views/elements/{_TooltipButton.scss.tmp => _TooltipButton.scss} (100%) rename src/components/views/elements/{TooltipButton.js.tmp => TooltipButton.js} (100%) diff --git a/res/css/views/elements/_TooltipButton.scss.tmp b/res/css/views/elements/_TooltipButton.scss similarity index 100% rename from res/css/views/elements/_TooltipButton.scss.tmp rename to res/css/views/elements/_TooltipButton.scss diff --git a/src/components/views/elements/TooltipButton.js.tmp b/src/components/views/elements/TooltipButton.js similarity index 100% rename from src/components/views/elements/TooltipButton.js.tmp rename to src/components/views/elements/TooltipButton.js From c57d93702a1f55328a2cc0ff17753d15dbfb4270 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 19 Jun 2019 11:46:24 +0100 Subject: [PATCH 085/188] De-duplicate notif badge code We had two different places we were deciding whether to show a badge. Let's just have one. --- src/RoomNotifs.js | 12 ++++++------ src/components/views/rooms/RoomTile.js | 12 ++---------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index 39384b5bea..5690817da5 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -26,12 +26,12 @@ export const MUTE = 'mute'; export const BADGE_STATES = [ALL_MESSAGES, ALL_MESSAGES_LOUD]; export const MENTION_BADGE_STATES = [...BADGE_STATES, MENTIONS_ONLY]; -function _shouldShowNotifBadge(roomNotifState) { +export function shouldShowNotifBadge(roomNotifState) { const showBadgeInStates = [ALL_MESSAGES, ALL_MESSAGES_LOUD]; return showBadgeInStates.indexOf(roomNotifState) > -1; } -function _shouldShowMentionBadge(roomNotifState) { +export function shouldShowMentionBadge(roomNotifState) { return roomNotifState !== MUTE; } @@ -41,8 +41,8 @@ export function aggregateNotificationCount(rooms) { const highlight = room.getUnreadNotificationCount('highlight') > 0; const notificationCount = room.getUnreadNotificationCount(); - const notifBadges = notificationCount > 0 && _shouldShowNotifBadge(roomNotifState); - const mentionBadges = highlight && _shouldShowMentionBadge(roomNotifState); + const notifBadges = notificationCount > 0 && shouldShowNotifBadge(roomNotifState); + const mentionBadges = highlight && shouldShowMentionBadge(roomNotifState); const badges = notifBadges || mentionBadges; if (badges) { @@ -60,8 +60,8 @@ export function getRoomHasBadge(room) { const highlight = room.getUnreadNotificationCount('highlight') > 0; const notificationCount = room.getUnreadNotificationCount(); - const notifBadges = notificationCount > 0 && _shouldShowNotifBadge(roomNotifState); - const mentionBadges = highlight && _shouldShowMentionBadge(roomNotifState); + const notifBadges = notificationCount > 0 && shouldShowNotifBadge(roomNotifState); + const mentionBadges = highlight && shouldShowMentionBadge(roomNotifState); return notifBadges || mentionBadges; } diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index e1b9567ebd..c4bd2adf2c 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -67,14 +67,6 @@ module.exports = React.createClass({ }); }, - _shouldShowNotifBadge: function() { - return RoomNotifs.BADGE_STATES.includes(this.state.notifState); - }, - - _shouldShowMentionBadge: function() { - return RoomNotifs.MENTION_BADGE_STATES.includes(this.state.notifState); - }, - _isDirectMessageRoom: function(roomId) { const dmRooms = DMRoomMap.shared().getUserIdForRoomId(roomId); return Boolean(dmRooms); @@ -301,8 +293,8 @@ module.exports = React.createClass({ const notificationCount = this.props.notificationCount; // var highlightCount = this.props.room.getUnreadNotificationCount("highlight"); - const notifBadges = notificationCount > 0 && this._shouldShowNotifBadge(); - const mentionBadges = this.props.highlight && this._shouldShowMentionBadge(); + const notifBadges = notificationCount > 0 && RoomNotifs.shouldShowNotifBadge(this.state.notifState); + const mentionBadges = this.props.highlight && RoomNotifs.shouldShowMentionBadge(this.state.notifState); const badges = notifBadges || mentionBadges; let subtext = null; From 0e6f401b62bee23bd2a60fcae014717a7b4aac08 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 19 Jun 2019 11:48:47 +0100 Subject: [PATCH 086/188] copyright --- src/RoomNotifs.js | 1 + src/components/views/rooms/RoomTile.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index 5690817da5..5d3444b0f6 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.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. diff --git a/src/components/views/rooms/RoomTile.js b/src/components/views/rooms/RoomTile.js index c4bd2adf2c..be73985d16 100644 --- a/src/components/views/rooms/RoomTile.js +++ b/src/components/views/rooms/RoomTile.js @@ -2,6 +2,7 @@ Copyright 2015, 2016 OpenMarket Ltd Copyright 2017 New Vector Ltd Copyright 2018 Michael Telatynski <7t3chguy@gmail.com> +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. From edd43a270664f548873b179c2fb211adba531392 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 19 Jun 2019 12:06:32 +0100 Subject: [PATCH 087/188] Fix favicon/title badge count This was using a separate function (in MatrixChat) that didn't take into account whether we were supposed to be hiding the badge for rooms so would include notifs that were hidden everywhere else. Also make it a function & put it in RoomNotifs with all its friends. Fixes https://github.com/vector-im/riot-web/issues/3060 --- src/RoomNotifs.js | 21 +++++++++++++++++++++ src/components/structures/MatrixChat.js | 15 ++------------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index 39384b5bea..887a1e4699 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -35,6 +35,27 @@ function _shouldShowMentionBadge(roomNotifState) { return roomNotifState !== MUTE; } +export function countRoomsWithNotif(rooms) { + return rooms.reduce((result, room, index) => { + const roomNotifState = getRoomNotifsState(room.roomId); + const highlight = room.getUnreadNotificationCount('highlight') > 0; + const notificationCount = room.getUnreadNotificationCount(); + + const notifBadges = notificationCount > 0 && _shouldShowNotifBadge(roomNotifState); + const mentionBadges = highlight && _shouldShowMentionBadge(roomNotifState); + const isInvite = room.hasMembershipState(MatrixClientPeg.get().credentials.userId, 'invite'); + const badges = notifBadges || mentionBadges || isInvite; + + if (badges) { + result.count++; + if (highlight) { + result.highlight = true; + } + } + return result; + }, {count: 0, highlight: false}); +} + export function aggregateNotificationCount(rooms) { return rooms.reduce((result, room, index) => { const roomNotifState = getRoomNotifsState(room.roomId); diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index d0f5a7e005..4dc5057eaf 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -54,6 +54,7 @@ import ResizeNotifier from "../../utils/ResizeNotifier"; import { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils"; import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils"; import DMRoomMap from '../../utils/DMRoomMap'; +import { countRoomsWithNotif } from '../../RoomNotifs'; // Disable warnings for now: we use deprecated bluebird functions // and need to migrate, but they spam the console with warnings. @@ -1749,19 +1750,7 @@ export default React.createClass({ }, updateStatusIndicator: function(state, prevState) { - let notifCount = 0; - - const rooms = MatrixClientPeg.get().getRooms(); - for (let i = 0; i < rooms.length; ++i) { - if (rooms[i].hasMembershipState(MatrixClientPeg.get().credentials.userId, 'invite')) { - notifCount++; - } else if (rooms[i].getUnreadNotificationCount()) { - // if we were summing unread notifs: - // notifCount += rooms[i].getUnreadNotificationCount(); - // instead, we just count the number of rooms with notifs. - notifCount++; - } - } + let notifCount = countRoomsWithNotif(MatrixClientPeg.get().getRooms()).count; if (PlatformPeg.get()) { PlatformPeg.get().setErrorStatus(state === 'ERROR'); From 23b29ce3f5bf1860f425ebd0a52a8629537b407a Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 19 Jun 2019 12:12:19 +0100 Subject: [PATCH 088/188] Use the variables we just defined above --- src/RoomNotifs.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index 5d3444b0f6..f62eeeec41 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -28,12 +28,11 @@ export const BADGE_STATES = [ALL_MESSAGES, ALL_MESSAGES_LOUD]; export const MENTION_BADGE_STATES = [...BADGE_STATES, MENTIONS_ONLY]; export function shouldShowNotifBadge(roomNotifState) { - const showBadgeInStates = [ALL_MESSAGES, ALL_MESSAGES_LOUD]; - return showBadgeInStates.indexOf(roomNotifState) > -1; + return BADGE_STATES.includes(roomNotifState); } export function shouldShowMentionBadge(roomNotifState) { - return roomNotifState !== MUTE; + return MENTION_BADGE_STATES.includes(roomNotifState); } export function aggregateNotificationCount(rooms) { From 6eddce8c32538ad1074fc9295f78c36995ef6264 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 19 Jun 2019 12:13:17 +0100 Subject: [PATCH 089/188] lint --- src/components/structures/MatrixChat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 4dc5057eaf..d942bb142e 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1750,7 +1750,7 @@ export default React.createClass({ }, updateStatusIndicator: function(state, prevState) { - let notifCount = countRoomsWithNotif(MatrixClientPeg.get().getRooms()).count; + const notifCount = countRoomsWithNotif(MatrixClientPeg.get().getRooms()).count; if (PlatformPeg.get()) { PlatformPeg.get().setErrorStatus(state === 'ERROR'); From 399f59130e7c864781f4f046ae4c34da662549ca Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Wed, 19 Jun 2019 09:01:35 +0000 Subject: [PATCH 090/188] Translated using Weblate (Albanian) Currently translated at 99.6% (1655 of 1661 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/sq/ --- src/i18n/strings/sq.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index a6f03bb6e0..3d94d3a670 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -1960,5 +1960,19 @@ "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Mund të bëni hyrjen, por disa veçori do të jenë të papërdorshme, derisa shërbyesi i identiteteve të jetë sërish në linjë. Nëse vazhdoni ta shihni këtë sinjalizim, kontrolloni formësimin tuaj ose lidhuni me një përgjegjës të shërbyesit.", "Log in to your new account.": "Bëni hyrjen te llogaria juaj e re.", "You can now close this window or log in to your new account.": "Tani mund ta mbyllni këtë dritare ose të bëni hyrjen në llogarinë tuaj të re.", - "Registration Successful": "Regjistrim i Suksesshëm" + "Registration Successful": "Regjistrim i Suksesshëm", + "No integrations server configured": "S’ka të formësuar shërbyes integrimesh", + "This Riot instance does not have an integrations server configured.": "Kjo instancë Riot-i s’ka të formësuar ndonjë shërbyes integrimesh.", + "Connecting to integrations server...": "Po lidhet te një shërbyes integrimesh…", + "Cannot connect to integrations server": "S’lidhet dot me shërbyes integrimesh", + "The integrations server is offline or it cannot reach your homeserver.": "Shërbyesi i integrimeve s’është në linjë ose s’kap dot shërbyesin tuaj Home.", + "Unnamed microphone": "Mikrofon i paemërtuar", + "Unnamed audio output": "Dalje audio e paemërtuar", + "Unnamed camera": "Kamerë e paemërtuar", + "Failed to connect to integrations server": "S’u arrit të lidhej me shërbyes integrimesh", + "No integrations server is configured to manage stickers with": "S’ka shërbyes integrimesh të formësuar për administrim ngjitësish", + "Upload all": "Ngarkoji krejt", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Llogaria juaj e re (%(newAccountId)s) është e regjistruar, por jeni i futur në një tjetër llogari (%(loggedInUserId)s).", + "Continue with previous account": "Vazhdoni me llogarinë e mëparshme", + "Sign out of previous account": "Dilni nga llogaria e mëparshme" } From a10675a4008120e8dfcb940e119fffe844c39325 Mon Sep 17 00:00:00 2001 From: Tuomas Hietala Date: Wed, 19 Jun 2019 12:23:43 +0000 Subject: [PATCH 091/188] Translated using Weblate (Finnish) Currently translated at 97.5% (1620 of 1661 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 | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/i18n/strings/fi.json b/src/i18n/strings/fi.json index fbcc3d2430..c07e6f0d86 100644 --- a/src/i18n/strings/fi.json +++ b/src/i18n/strings/fi.json @@ -816,9 +816,9 @@ "Unignored user": "Sallitut käyttäjät", "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s tasolta %(fromPowerLevel)s tasolle %(toPowerLevel)s", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s muutti %(powerLevelDiffText)s:n oikeustasoa.", - "%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s pienoisohjelmaa muokannut %(senderName)s", - "%(widgetName)s widget added by %(senderName)s": "%(widgetName)s pienoisohjelman lisännyt %(senderName)s", - "%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s pienoisohjelman poistanut %(senderName)s", + "%(widgetName)s widget modified by %(senderName)s": "%(senderName)s muutti pienoisohjelmaa %(widgetName)s", + "%(widgetName)s widget added by %(senderName)s": "%(senderName)s lisäsi pienoisohjelman %(widgetName)s", + "%(widgetName)s widget removed by %(senderName)s": "%(senderName)s poisti pienoisohjelman %(widgetName)s", "Send": "Lähetä", "Delete %(count)s devices|other": "Poista %(count)s laitetta", "Delete %(count)s devices|one": "Poista laite", @@ -1864,5 +1864,8 @@ "Create your Matrix account on ": "Luo Matrix-tili palvelimelle ", "Add room": "Lisää huone", "Your profile": "Oma profiilisi", - "Your Matrix account on ": "Matrix-tilisi palvelimella " + "Your Matrix account on ": "Matrix-tilisi palvelimella ", + "Cannot reach homeserver": "Kotipalvelinta ei voida tavoittaa", + "Your Riot is misconfigured": "Riotin asetukset ovat pielessä", + "Cannot reach identity server": "Identiteettipalvelinta ei voida tavoittaa" } From 057f2da32764b0f718ab4d82c07fd329ddaeb66b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Wed, 19 Jun 2019 10:47:22 +0000 Subject: [PATCH 092/188] Translated using Weblate (French) Currently translated at 100.0% (1661 of 1661 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index cbd43fafc0..709610cf96 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -2002,5 +2002,19 @@ "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Vous pouvez vous connecter, mais certaines fonctionnalités ne seront pas disponibles jusqu’au retour du serveur d’identité. Si vous continuez à voir cet avertissement, vérifiez votre configuration ou contactez un administrateur du serveur.", "Log in to your new account.": "Connectez-vous à votre nouveau compte.", "You can now close this window or log in to your new account.": "Vous pouvez à présent fermer cette fenêtre ou vous connecter à votre nouveau compte.", - "Registration Successful": "Inscription réussie" + "Registration Successful": "Inscription réussie", + "No integrations server configured": "Aucun serveur d’intégrations configuré", + "This Riot instance does not have an integrations server configured.": "Cette instance de Riot n’a aucun serveur d’intégrations configuré.", + "Connecting to integrations server...": "Connexion au serveur d’intégrations…", + "Cannot connect to integrations server": "Impossible de se connecter au serveur d’intégrations", + "The integrations server is offline or it cannot reach your homeserver.": "Le serveur d’intégrations est hors ligne ou il ne peut pas joindre votre serveur d’accueil.", + "Unnamed microphone": "Microphone sans nom", + "Unnamed audio output": "Sortie audio sans nom", + "Unnamed camera": "Caméra sans nom", + "Failed to connect to integrations server": "Échec de la connexion au serveur d’intégrations", + "No integrations server is configured to manage stickers with": "Aucun serveur d’intégrations n’est configuré pour gérer les stickers", + "Upload all": "Tout envoyer", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Votre nouveau compte (%(newAccountId)s) est créé, mais vous êtes déjà connecté avec un autre compte (%(loggedInUserId)s).", + "Continue with previous account": "Continuer avec le compte précédent", + "Sign out of previous account": "Se déconnecter du compte précédent" } From 2247c5953d441b773115239a7f62ad7a25cfbe01 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 19 Jun 2019 13:33:06 +0100 Subject: [PATCH 093/188] Fix conflicting PRs --- src/RoomNotifs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js index a21ec30d93..2d5e4b3136 100644 --- a/src/RoomNotifs.js +++ b/src/RoomNotifs.js @@ -41,8 +41,8 @@ export function countRoomsWithNotif(rooms) { const highlight = room.getUnreadNotificationCount('highlight') > 0; const notificationCount = room.getUnreadNotificationCount(); - const notifBadges = notificationCount > 0 && _shouldShowNotifBadge(roomNotifState); - const mentionBadges = highlight && _shouldShowMentionBadge(roomNotifState); + const notifBadges = notificationCount > 0 && shouldShowNotifBadge(roomNotifState); + const mentionBadges = highlight && shouldShowMentionBadge(roomNotifState); const isInvite = room.hasMembershipState(MatrixClientPeg.get().credentials.userId, 'invite'); const badges = notifBadges || mentionBadges || isInvite; From 5c5c4cfbc1bb9aaa61db2263073fcc48e9bfb1af Mon Sep 17 00:00:00 2001 From: Tuomas Hietala Date: Wed, 19 Jun 2019 12:30:22 +0000 Subject: [PATCH 094/188] Translated using Weblate (Finnish) Currently translated at 97.7% (1622 of 1661 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 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/fi.json b/src/i18n/strings/fi.json index c07e6f0d86..5de77cb728 100644 --- a/src/i18n/strings/fi.json +++ b/src/i18n/strings/fi.json @@ -1867,5 +1867,7 @@ "Your Matrix account on ": "Matrix-tilisi palvelimella ", "Cannot reach homeserver": "Kotipalvelinta ei voida tavoittaa", "Your Riot is misconfigured": "Riotin asetukset ovat pielessä", - "Cannot reach identity server": "Identiteettipalvelinta ei voida tavoittaa" + "Cannot reach identity server": "Identiteettipalvelinta ei voida tavoittaa", + "Ensure you have a stable internet connection, or get in touch with the server admin": "Varmista, että internet-yhteytesi on vakaa, tai ota yhteyttä palvelimen ylläpitäjään", + "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Pyydä Riot-ylläpitäjääsi tarkistamaan, onko asetuksissasivirheellisiä tai toistettuja merkintöjä." } From dc9282c539e796eceed1908c1c90242a7260a80d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 19 Jun 2019 13:57:37 +0100 Subject: [PATCH 095/188] Remove unused ContextualMenu features --- res/css/structures/_ContextualMenu.scss | 6 ----- src/components/structures/ContextualMenu.js | 27 +-------------------- 2 files changed, 1 insertion(+), 32 deletions(-) diff --git a/res/css/structures/_ContextualMenu.scss b/res/css/structures/_ContextualMenu.scss index b6644c1752..e818bb092e 100644 --- a/res/css/structures/_ContextualMenu.scss +++ b/res/css/structures/_ContextualMenu.scss @@ -163,12 +163,6 @@ limitations under the License. bottom: 1px; } -.mx_ContextualMenu_field { - padding: 3px 6px 3px 6px; - cursor: pointer; - white-space: nowrap; -} - .mx_ContextualMenu_spinner { display: block; margin: 0 auto; diff --git a/src/components/structures/ContextualMenu.js b/src/components/structures/ContextualMenu.js index edd6f79270..3ce52247d9 100644 --- a/src/components/structures/ContextualMenu.js +++ b/src/components/structures/ContextualMenu.js @@ -1,6 +1,7 @@ /* Copyright 2015, 2016 OpenMarket Ltd Copyright 2018 New Vector 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. @@ -15,7 +16,6 @@ See the License for the specific language governing permissions and limitations under the License. */ - import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; @@ -48,7 +48,6 @@ export default class ContextualMenu extends React.Component { menuWidth: PropTypes.number, menuHeight: PropTypes.number, chevronOffset: PropTypes.number, - menuColour: PropTypes.string, chevronFace: PropTypes.string, // top, bottom, left, right or none // Function to be called on menu close onFinished: PropTypes.func, @@ -157,25 +156,6 @@ export default class ContextualMenu extends React.Component { chevronOffset.top = Math.max(props.chevronOffset, props.chevronOffset + target - adjusted); } - // To override the default chevron colour, if it's been set - let chevronCSS = ""; - if (props.menuColour) { - chevronCSS = ` - .mx_ContextualMenu_chevron_left:after { - border-right-color: ${props.menuColour}; - } - .mx_ContextualMenu_chevron_right:after { - border-left-color: ${props.menuColour}; - } - .mx_ContextualMenu_chevron_top:after { - border-left-color: ${props.menuColour}; - } - .mx_ContextualMenu_chevron_bottom:after { - border-left-color: ${props.menuColour}; - } - `; - } - const chevron = hasChevron ?
: undefined; @@ -202,10 +182,6 @@ export default class ContextualMenu extends React.Component { menuStyle.height = props.menuHeight; } - if (props.menuColour) { - menuStyle["backgroundColor"] = props.menuColour; - } - if (!isNaN(Number(props.menuPaddingTop))) { menuStyle["paddingTop"] = props.menuPaddingTop; } @@ -236,7 +212,6 @@ export default class ContextualMenu extends React.Component {
{ props.hasBackground &&
} -
; } } From 21344609a42e4c1a5400b402a6aa5563b01b5871 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 19 Jun 2019 15:47:31 +0100 Subject: [PATCH 096/188] released js-sdk --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 5966e00103..f917777f8c 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "linkifyjs": "^2.1.6", "lodash": "^4.13.1", "lolex": "2.3.2", - "matrix-js-sdk": "2.0.1-rc.2", + "matrix-js-sdk": "2.0.1", "optimist": "^0.6.1", "pako": "^1.0.5", "png-chunks-extract": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index 9bf63b8b47..62868d0a92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4877,10 +4877,10 @@ mathml-tag-names@^2.0.1: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.0.tgz#490b70e062ee24636536e3d9481e333733d00f2c" integrity sha512-3Zs9P/0zzwTob2pdgT0CHZuMbnSUSp8MB1bddfm+HDmnFWHGT4jvEZRf+2RuPoa+cjdn/z25SEt5gFTqdhvJAg== -matrix-js-sdk@2.0.1-rc.2: - version "2.0.1-rc.2" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-2.0.1-rc.2.tgz#6a00e4264f7e27a6f71a25ee9cbd935fbfbbf759" - integrity sha512-mq93SfeI9cc+BPfjNQKNzdLmq7l3BgFos8fhMt9ToLJB/wEg4xJXU0Nc8+31dDd65IDa4tGSyyCL4mRtpT6Nmw== +matrix-js-sdk@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-2.0.1.tgz#e9691c7fc142793aa8cd79e92d45698bcc5da8c4" + integrity sha512-+yj9fBdIE65v1+46TL/eLQGohtNZGBEtOD1n3nTAVBMogyVb2bpUWnqTli0ghiOCG9MKq7tWi+G4bDBTABxuxA== dependencies: another-json "^0.2.0" babel-runtime "^6.26.0" From b91e5a7d7c6cdae02c773d6b2a65e9a4cbce1b0b Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 19 Jun 2019 15:52:17 +0100 Subject: [PATCH 097/188] Prepare changelog for v1.2.2 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21f627597a..0a2ea1d48b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Changes in [1.2.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.2.2) (2019-06-19) +=================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.2.2-rc.2...v1.2.2) + +No changes since rc.2 + Changes in [1.2.2-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v1.2.2-rc.2) (2019-06-18) ============================================================================================================= [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v1.2.2-rc.1...v1.2.2-rc.2) From 2d6317fdd0303ec575e64588e5490606207a1d3f Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 19 Jun 2019 15:52:18 +0100 Subject: [PATCH 098/188] v1.2.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f917777f8c..96ec129f28 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "1.2.2-rc.2", + "version": "1.2.2", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From dc947ab21c0b928483397a1347903e73cc53f620 Mon Sep 17 00:00:00 2001 From: Tuomas Hietala Date: Wed, 19 Jun 2019 12:36:03 +0000 Subject: [PATCH 099/188] Translated using Weblate (Finnish) Currently translated at 99.4% (1651 of 1661 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 | 55 ++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/src/i18n/strings/fi.json b/src/i18n/strings/fi.json index 5de77cb728..21b99da142 100644 --- a/src/i18n/strings/fi.json +++ b/src/i18n/strings/fi.json @@ -58,8 +58,8 @@ "Attachment": "Liite", "Autoplay GIFs and videos": "Toista GIF-animaatiot ja videot automaattisesti", "%(senderName)s banned %(targetName)s.": "%(senderName)s antoi porttikiellon käyttäjälle %(targetName)s.", - "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Yhdistäminen kotipalvelimeen epäonnistui. Ole hyvä ja tarkista verkkoyhteytesi ja varmista että kotipalvelimen SSL-sertifikaatti on luotettu, ja että jokin selaimen lisäosa ei estä pyyntöjen lähettämisen.", - "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Yhdistäminen kotipalveluun HTTP:n avulla ei ole mahdollista kun selaimen osoitepalkissa on HTTPS URL. Käytä joko HTTPS tai salli turvattomat skriptit.", + "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Kotipalvelimeen ei saada yhteyttä. Tarkista verkkoyhteytesi, varmista että kotipalvelimesi SSL-sertifikaatti on luotettu, ja että mikään selaimen lisäosa ei estä pyyntöjen lähettämistä.", + "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Yhdistäminen kotipalvelimeen HTTP:n avulla ei ole mahdollista, kun selaimen osoitepalkissa on HTTPS-osoite. Käytä joko HTTPS:ää tai salli turvattomat skriptit.", "Can't load user settings": "Käyttäjäasetusten lataaminen epäonnistui", "Change Password": "Muuta salasana", "%(senderName)s changed their profile picture.": "%(senderName)s muutti profiilikuvansa.", @@ -527,7 +527,7 @@ "Tried to load a specific point in this room's timeline, but was unable to find it.": "Huoneen aikajanan tietty hetki yritettiin ladata, mutta sitä ei löytynyt.", "Unable to ascertain that the address this invite was sent to matches one associated with your account.": "Ei voida varmistaa että osoite, johon tämä kutsu lähetettiin, vastaa tiliisi liittettyä osoitetta.", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (oikeustaso %(powerLevelNumber)s)", - "Verification Pending": "Varmennus on vireillä", + "Verification Pending": "Varmennus odottaa", "(could not connect media)": "(mediaa ei voitu yhdistää)", "WARNING: Device already verified, but keys do NOT MATCH!": "VAROITUS: Laite on jo varmennettu mutta avaimet eivät vastaa toisiaan!", "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "VAROITUS: AVAIMEN VARMENNUS EPÄONNISTUI! Käyttäjän %(userId)s ja laitteen %(deviceId)s allekirjoitusavain on \"%(fprint)s\" joka ei vastaa annettua avainta \"%(fingerprint)s\". Tämä saattaa tarkoittaa että viestintäsi siepataan!", @@ -583,7 +583,7 @@ "Username available": "Käyttäjätunnus saatavilla", "Username not available": "Käyttäjätunnus ei ole saatavissa", "Something went wrong!": "Jokin meni vikaan!", - "This will be your account name on the homeserver, or you can pick a different server.": "Tästä tulee tilisi nimi -kotipalvelimella, tai voit valita toisen palvelimen.", + "This will be your account name on the homeserver, or you can pick a different server.": "Tästä tulee tilisi nimi -kotipalvelimella, tai voit valita toisen palvelimen.", "If you already have a Matrix account you can log in instead.": "Jos sinulla on jo Matrix-tili, voit kirjautua.", "Your browser does not support the required cryptography extensions": "Selaimesi ei tue vaadittuja kryptografisia laajennuksia", "Not a valid Riot keyfile": "Ei kelvollinen Riot-avaintiedosto", @@ -661,7 +661,7 @@ "Ban this user?": "Anna porttikielto tälle käyttäjälle?", "Unignore": "Huomioi käyttäjä jälleen", "Ignore": "Jätä käyttäjä huomioimatta", - "Jump to read receipt": "Hyppää lukukuittakseen", + "Jump to read receipt": "Hyppää lukukuittaukseen", "Mention": "Mainitse", "Invite": "Kutsu", "User Options": "Käyttäjä-asetukset", @@ -749,7 +749,7 @@ "Community ID": "Yhteisötunniste", "example": "esimerkki", "Advanced options": "Lisäasetukset", - "Block users on other matrix homeservers from joining this room": "Salli vain tämän palvelimen käyttäjät", + "Block users on other matrix homeservers from joining this room": "Salli vain tämän kotipalvelimen käyttäjät", "This setting cannot be changed later!": "Tätä asetusta ei voi muuttaa myöhemmin!", "Add rooms to the community summary": "Lisää huoneita yhteisön yhteenvetoon", "Which rooms would you like to add to this summary?": "Mitkä huoneet haluaisit lisätä tähän yhteenvetoon?", @@ -1101,7 +1101,7 @@ "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s poisti osoitteet %(removedAddresses)s tältä huoneelta.", "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s asetti tälle huoneelle pääosoitteen %(address)s.", "%(senderName)s removed the main address for this room.": "%(senderName)s poisti tämän huoneen pääosoitteen.", - "Please contact your service administrator to continue using the service.": "Otathan yhteyttä palvelun ylläpitäjään jatkaaksesi palvelun käyttöä.", + "Please contact your service administrator to continue using the service.": "Ota yhteyttä palvelun ylläpitäjään jatkaaksesi palvelun käyttöä.", "Unable to connect to Homeserver. Retrying...": "Kotipalvelimeen ei saatu yhteyttä. Yritetään uudelleen...", "User %(user_id)s does not exist": "Käyttäjää %(user_id)s ei ole olemassa", "Avoid repeated words and characters": "Vältä toistettuja sanoja ja merkkejä", @@ -1212,7 +1212,7 @@ "Log out and remove encryption keys?": "Kirjaudutaanko ulos ja poistetaan salausavaimet?", "Encrypted": "Salattu", "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "Muutokset historian lukuoikeuksiin pätevät vain tuleviin viesteihin tässä huoneessa. Nykyisen historian näkyvyys pysyy muuttumattomana.", - "Open Devtools": "Avaa Devtools", + "Open Devtools": "Avaa kehittäjätyökalut", "You don't currently have any stickerpacks enabled": "Sinulla ei ole tarrapaketteja käytössä", "Stickerpack": "Tarrapaketti", "Hide Stickers": "Piilota tarrat", @@ -1229,7 +1229,7 @@ "Theme": "Teema", "Default theme": "Oletusteema", "Account management": "Tilin hallinta", - "Composer": "Lähetys", + "Composer": "Viestin kirjoitus", "Preferences": "Valinnat", "Voice & Video": "Ääni ja video", "Help & About": "Ohje ja tietoja", @@ -1451,7 +1451,7 @@ "Share User": "Jaa käyttäjä", "Share Community": "Jaa yhteisö", "Share Room Message": "Jaa huoneviesti", - "Use a longer keyboard pattern with more turns": "Käytä pidempiä näppäinyhdistelmiä suuremmalla vuoromäärällä", + "Use a longer keyboard pattern with more turns": "Käytä pidempiä näppäinyhdistelmiä, joissa on enemmän suunnanmuutoksia", "Changes your display nickname in the current room only": "Vaihtaa näyttönimesi vain nykyisessä huoneessa", "Group & filter rooms by custom tags (refresh to apply changes)": "Ryhmittele ja suodata huoneita tagien perusteella (päivitä ottaaksesi muutokset käyttöön)", "Render simple counters in room header": "Näytä yksinkertaiset laskurit huoneen yläpalkissa", @@ -1550,7 +1550,7 @@ "Ban users": "Estä käyttäjiä", "Remove messages": "Poista viestejä", "Notify everyone": "Kiinnitä kaikkien huomio", - "Send %(eventType)s events": "Lähetä %(eventType)s tapahtumaa", + "Send %(eventType)s events": "Lähetä %(eventType)s-tapahtumat", "Select the roles required to change various parts of the room": "Valitse roolit, jotka vaaditaan huoneen eri osioiden vaihtamiseen", "Enable encryption?": "Ota salaus käyttöön?", "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.": "Salausta ei voi ottaa pois käytöstä käyttöönoton jälkeen. Viestejä, jotka on lähetetty salattuun huoneeseen, voidaan lukea vain huoneen jäsenten, ei palvelimen, toimesta. Salauksen käyttöönotto saattaa haitata bottien ja siltojen toimivuutta. Lisää tietoa salauksesta.", @@ -1733,7 +1733,7 @@ "Guest access is disabled on this homeserver.": "Tämä kotipalvelin ei salli vieraiden pääsyä.", "Failed to perform homeserver discovery": "Kotipalvelimen etsinnän suoritus epäonnistui", "Unknown failure discovering homeserver": "Kotipalvelimen etsinnässä tapahtui tuntematon virhe", - "This homeserver doesn't offer any login flows which are supported by this client.": "Tämä kotipalvelin ei tarjoa yhtään kirjautumistapaa, jota tämä klientti tukisi.", + "This homeserver doesn't offer any login flows which are supported by this client.": "Tämä kotipalvelin ei tarjoa yhtään kirjautumistapaa, jota tämä asiakasohjelma tukisi.", "Claimed Ed25519 fingerprint key": "Väitetty Ed25519-avaimen sormenjälki", "Set up with a Recovery Key": "Ota palautusavain käyttöön", "Please enter your passphrase a second time to confirm.": "Syötä salalauseesi toisen kerran varmistukseksi.", @@ -1869,5 +1869,34 @@ "Your Riot is misconfigured": "Riotin asetukset ovat pielessä", "Cannot reach identity server": "Identiteettipalvelinta ei voida tavoittaa", "Ensure you have a stable internet connection, or get in touch with the server admin": "Varmista, että internet-yhteytesi on vakaa, tai ota yhteyttä palvelimen ylläpitäjään", - "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Pyydä Riot-ylläpitäjääsi tarkistamaan, onko asetuksissasivirheellisiä tai toistettuja merkintöjä." + "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Pyydä Riot-ylläpitäjääsi tarkistamaan, onko asetuksissasivirheellisiä tai toistettuja merkintöjä.", + "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Voit rekisteröityä, mutta osa toiminnoista on pois käytöstä kunnes identiteettipalvelin on jälleen toiminnassa. Jos tämä varoitus toistuu, tarkista asetuksesi tai ota yhteyttä palvelimen ylläpitäjään.", + "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Voit palauttaa salasanasi, mutta osa toiminnoista on pois käytöstä kunnes identiteettipalvelin on jälleen toiminnassa. Jos tämä varoitus toistuu, tarkista asetuksesi tai ota yhteyttä palvelimen ylläpitäjään.", + "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Voit kirjautua, mutta osa toiminnoista on pois käytöstä kunnes identiteettipalvelin on jälleen toiminnassa. Jos tämä varoitus toistuu, tarkista asetuksesi tai ota yhteyttä palvelimen ylläpitäjään.", + "Unexpected error resolving identity server configuration": "Odottamaton virhe selvitettäessä identiteettipalvelimen asetuksia", + "Show recently visited rooms above the room list": "Näytä hiljattain vieraillut huoneet huonelistan yläpuolella", + "Low bandwidth mode": "Matalan kaistanleveyden tila", + "No integrations server configured": "Integraatiopalvelinta ei ole määritetty", + "This Riot instance does not have an integrations server configured.": "Tälle Riot-instanssille ei ole määritetty integraatiopalvelinta.", + "Connecting to integrations server...": "Yhdistetään integraatiopalvelimelle...", + "Cannot connect to integrations server": "Integraatiopalvelimeen ei saada yhteyttä", + "The integrations server is offline or it cannot reach your homeserver.": "Integraatiopalvelin on pois toiminnasta tai ei saa yhteyttä kotipalvelimeesi.", + "Unnamed microphone": "Nimetön mikrofoni", + "Unnamed audio output": "Nimetön äänilähtö", + "Unnamed camera": "Nimetön kamera", + "Uploaded sound": "Ladattu ääni", + "Sounds": "Äänet", + "Notification sound": "Ilmoitusääni", + "Reset": "Palauta alkutilaan", + "Set a new custom sound": "Aseta uusi mukautettu ääni", + "Browse": "Selaa", + "Failed to connect to integrations server": "Integraatiopalvelimelle yhdistäminen epäonnistui", + "No integrations server is configured to manage stickers with": "Tarrojen hallintaa varten ei ole määritetty integraatiopalvelinta", + "Use lowercase letters, numbers, dashes and underscores only": "Käytä ainoastaan pieniä kirjaimia, numeroita, ajatusviivoja ja alaviivoja", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Uusi tilisi (%(newAccountId)s) on rekisteröity, mutta olet jo kirjautuneena toisella tilillä (%(loggedInUserId)s).", + "Continue with previous account": "Jatka aiemmalla tilillä", + "Sign out of previous account": "Kirjaudu ulos aiemmasta tilistä", + "Log in to your new account.": "Kirjaudu uudelle tilillesi.", + "You can now close this window or log in to your new account.": "Voit nyt sulkea tämän ikkunan tai kirjautua uudelle tilillesi.", + "Registration Successful": "Rekisteröityminen onnistui" } From 75fc7697423eb8f2037d30a44b16baae30ebb237 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 18 Jun 2019 18:53:55 +0200 Subject: [PATCH 100/188] insert "caret nodes" where pills don't have an adjacent text node just empty spans, where the caret can be placed. --- src/editor/render.js | 176 +++++++++++++++++++++++++++++++++---------- 1 file changed, 136 insertions(+), 40 deletions(-) diff --git a/src/editor/render.js b/src/editor/render.js index 58ef0eaee1..ed354ab8f5 100644 --- a/src/editor/render.js +++ b/src/editor/render.js @@ -15,6 +15,133 @@ See the License for the specific language governing permissions and limitations under the License. */ +export function needsCaretNodeBefore(part, prevPart) { + const isFirst = !prevPart || prevPart.type === "newline"; + return !part.canEdit && (isFirst || !prevPart.canEdit); +} + +export function needsCaretNodeAfter(part, isLastOfLine) { + return !part.canEdit && isLastOfLine; +} + +function insertAfter(node, nodeToInsert) { + const next = node.nextSibling; + if (next) { + node.parentElement.insertBefore(nodeToInsert, next); + } else { + node.parentElement.appendChild(nodeToInsert); + } +} + +// a caret node is an empty node that allows the caret to be place +// where otherwise it wouldn't be possible +// (e.g. next to a pill span without adjacent text node) +function createCaretNode() { + const span = document.createElement("span"); + span.className = "caret"; + return span; +} + +function updateCaretNode(node) { + // ensure the caret node is empty + // otherwise they'll break everything + // as only things part of the model should have text in them + // browsers could end up typing in the caret node for any + // number of reasons, so revert this. + node.textContent = ""; +} + +function isCaretNode(node) { + return node && node.tagName === "SPAN" && node.className === "caret"; +} + +function removeNextSiblings(node) { + if (!node) { + return; + } + node = node.nextSibling; + while (node) { + const removeNode = node; + node = node.nextSibling; + removeNode.remove(); + } +} + +function removeChildren(parent) { + const firstChild = parent.firstChild; + if (firstChild) { + removeNextSiblings(firstChild); + firstChild.remove(); + } +} + +function reconcileLine(lineContainer, parts) { + let currentNode; + let prevPart; + const lastPart = parts[parts.length - 1]; + + for (const part of parts) { + const isFirst = !prevPart; + currentNode = isFirst ? lineContainer.firstChild : currentNode.nextSibling; + + if (needsCaretNodeBefore(part, prevPart)) { + if (isCaretNode(currentNode)) { + currentNode = currentNode.nextSibling; + updateCaretNode(currentNode); + } else { + lineContainer.insertBefore(createCaretNode(), currentNode); + } + } + // remove nodes until matching current part + while (currentNode && !part.canUpdateDOMNode(currentNode)) { + const nextNode = currentNode.nextSibling; + lineContainer.removeChild(currentNode); + currentNode = nextNode; + } + // update or insert node for current part + if (currentNode && part) { + part.updateDOMNode(currentNode); + } else if (part) { + currentNode = part.toDOMNode(); + // hooks up nextSibling for next iteration + lineContainer.appendChild(currentNode); + } + + if (needsCaretNodeAfter(part, part === lastPart)) { + if (isCaretNode(currentNode.nextSibling)) { + currentNode = currentNode.nextSibling; + updateCaretNode(currentNode); + } else { + const caretNode = createCaretNode(); + insertAfter(currentNode, caretNode); + currentNode = caretNode; + } + } + + prevPart = part; + } + + removeNextSiblings(currentNode); +} + +function reconcileEmptyLine(lineContainer) { + // empty div needs to have a BR in it to give it height + let foundBR = false; + let partNode = lineContainer.firstChild; + while (partNode) { + const nextNode = partNode.nextSibling; + if (!foundBR && partNode.tagName === "BR") { + foundBR = true; + } else { + partNode.remove(); + } + partNode = nextNode; + } + if (!foundBR) { + lineContainer.appendChild(document.createElement("br")); + } +} + export function renderModel(editor, model) { const lines = model.parts.reduce((lines, part) => { if (part.type === "newline") { @@ -25,8 +152,9 @@ export function renderModel(editor, model) { } return lines; }, [[]]); - // TODO: refactor this code, DRY it lines.forEach((parts, i) => { + // find first (and remove anything else) div without className + // (as browsers insert these in contenteditable) line container let lineContainer = editor.childNodes[i]; while (lineContainer && (lineContainer.tagName !== "DIV" || !!lineContainer.className)) { editor.removeChild(lineContainer); @@ -38,46 +166,14 @@ export function renderModel(editor, model) { } if (parts.length) { - parts.forEach((part, j) => { - let partNode = lineContainer.childNodes[j]; - while (partNode && !part.canUpdateDOMNode(partNode)) { - lineContainer.removeChild(partNode); - partNode = lineContainer.childNodes[j]; - } - if (partNode && part) { - part.updateDOMNode(partNode); - } else if (part) { - lineContainer.appendChild(part.toDOMNode()); - } - }); - - let surplusElementCount = Math.max(0, lineContainer.childNodes.length - parts.length); - while (surplusElementCount) { - lineContainer.removeChild(lineContainer.lastChild); - --surplusElementCount; - } + reconcileLine(lineContainer, parts); } else { - // empty div needs to have a BR in it to give it height - let foundBR = false; - let partNode = lineContainer.firstChild; - while (partNode) { - const nextNode = partNode.nextSibling; - if (!foundBR && partNode.tagName === "BR") { - foundBR = true; - } else { - lineContainer.removeChild(partNode); - } - partNode = nextNode; - } - if (!foundBR) { - lineContainer.appendChild(document.createElement("br")); - } - } - - let surplusElementCount = Math.max(0, editor.childNodes.length - lines.length); - while (surplusElementCount) { - editor.removeChild(editor.lastChild); - --surplusElementCount; + reconcileEmptyLine(lineContainer); } }); + if (lines.length) { + removeNextSiblings(editor.children[lines.length]); + } else { + removeChildren(editor); + } } From 607fc328ed775579579fe48170dafd30e701e4e2 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 19 Jun 2019 10:57:29 +0200 Subject: [PATCH 101/188] also process first part when processing empty and mergeable parts this was preventing clearing an emtpy plain part when inserting a pill-candidate at the beginning of the model, which prevented a caret node from being inserted before the pill. --- src/editor/model.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor/model.js b/src/editor/model.js index 7cc6041044..c8fa20efce 100644 --- a/src/editor/model.js +++ b/src/editor/model.js @@ -158,11 +158,11 @@ export default class EditorModel { } _mergeAdjacentParts(docPos) { - let prevPart = this._parts[0]; - for (let i = 1; i < this._parts.length; ++i) { + let prevPart; + for (let i = 0; i < this._parts.length; ++i) { let part = this._parts[i]; const isEmpty = !part.text.length; - const isMerged = !isEmpty && prevPart.merge(part); + const isMerged = !isEmpty && prevPart && prevPart.merge(part); if (isEmpty || isMerged) { // remove empty or merged part part = prevPart; From a229641985364db2687c0b1a6cd30134cc79f862 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 19 Jun 2019 17:36:17 +0200 Subject: [PATCH 102/188] use caret nodes in caret positioning code, to move caret out of pills --- src/editor/caret.js | 118 ++++++++++++++++++++++++++++++++------------ 1 file changed, 86 insertions(+), 32 deletions(-) diff --git a/src/editor/caret.js b/src/editor/caret.js index f93e9604d5..c56022d8c6 100644 --- a/src/editor/caret.js +++ b/src/editor/caret.js @@ -15,50 +15,104 @@ See the License for the specific language governing permissions and limitations under the License. */ +import {needsCaretNodeBefore, needsCaretNodeAfter} from "./render"; + export function setCaretPosition(editor, model, caretPosition) { const sel = document.getSelection(); sel.removeAllRanges(); const range = document.createRange(); + const {offset, lineIndex, nodeIndex} = getLineAndNodePosition(model, caretPosition); + const lineNode = editor.childNodes[lineIndex]; + + let focusNode; + // empty line with just a
+ if (nodeIndex === -1) { + focusNode = lineNode; + } else { + focusNode = lineNode.childNodes[nodeIndex]; + // make sure we have a text node + if (focusNode.nodeType === Node.ELEMENT_NODE && focusNode.firstChild) { + focusNode = focusNode.firstChild; + } + } + range.setStart(focusNode, offset); + range.collapse(true); + sel.addRange(range); +} + +function getLineAndNodePosition(model, caretPosition) { const {parts} = model; - const {index} = caretPosition; + const partIndex = caretPosition.index; + const lineResult = findNodeInLineForPart(parts, partIndex); + const {lineIndex} = lineResult; + let {nodeIndex} = lineResult; let {offset} = caretPosition; + // we're at an empty line between a newline part + // and another newline part or end/start of parts. + // set offset to 0 so it gets set to the
inside the line container + if (nodeIndex === -1) { + offset = 0; + } else { + // move caret out of uneditable part (into caret node, or empty line br) if needed + ({nodeIndex, offset} = moveOutOfUneditablePart(parts, partIndex, nodeIndex, offset)); + } + return {lineIndex, nodeIndex, offset}; +} + +function findNodeInLineForPart(parts, partIndex) { let lineIndex = 0; let nodeIndex = -1; - for (let i = 0; i <= index; ++i) { + + let prevPart = null; + // go through to parts up till (and including) the index + // to find newline parts + for (let i = 0; i <= partIndex; ++i) { const part = parts[i]; - if (part && part.type === "newline") { - if (i < index) { - lineIndex += 1; - nodeIndex = -1; - } else { - // if index points at a newline part, - // put the caret at the end of the previous part - // so it stays on the same line - const prevPart = parts[i - 1]; - offset = prevPart ? prevPart.text.length : 0; + if (part.type === "newline") { + lineIndex += 1; + nodeIndex = -1; + prevPart = null; + } else { + nodeIndex += 1; + if (needsCaretNodeBefore(part, prevPart)) { + nodeIndex += 1; + } + // only jump over caret node if we're not at our destination node already, + // as we'll assume in moveOutOfUneditablePart that nodeIndex + // refers to the node corresponding to the part, + // and not an adjacent caret node + if (i < partIndex) { + const nextPart = parts[i + 1]; + const isLastOfLine = !nextPart || nextPart.type === "newline"; + if (needsCaretNodeAfter(part, isLastOfLine)) { + nodeIndex += 1; + } + } + prevPart = part; + } + } + + return {lineIndex, nodeIndex}; +} + +function moveOutOfUneditablePart(parts, partIndex, nodeIndex, offset) { + // move caret before or after uneditable part + const part = parts[partIndex]; + if (part && !part.canEdit) { + if (offset === 0) { + nodeIndex -= 1; + const prevPart = parts[partIndex - 1]; + // if the previous node is a caret node, it's empty + // so the offset can stay at 0 + // only when it's not, we need to set the offset + // at the end of the node + if (!needsCaretNodeBefore(part, prevPart)) { + offset = prevPart.text.length; } } else { nodeIndex += 1; + offset = 0; } } - let focusNode; - const lineNode = editor.childNodes[lineIndex]; - if (lineNode) { - focusNode = lineNode.childNodes[nodeIndex]; - if (!focusNode) { - focusNode = lineNode; - } else if (focusNode.nodeType === Node.ELEMENT_NODE) { - focusNode = focusNode.childNodes[0]; - } - } - // node not found, set caret at end - if (!focusNode) { - range.selectNodeContents(editor); - range.collapse(false); - } else { - // make sure we have a text node - range.setStart(focusNode, offset); - range.collapse(true); - } - sel.addRange(range); + return {nodeIndex, offset}; } From f0271b593d461a8f4417707c42fc7a76eef48743 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 19 Jun 2019 17:37:17 +0200 Subject: [PATCH 103/188] remove special casing for moving caret after newline and pills not needed anymore with new caret logic and having caret nodes --- src/editor/model.js | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/editor/model.js b/src/editor/model.js index c8fa20efce..1080df67ba 100644 --- a/src/editor/model.js +++ b/src/editor/model.js @@ -103,8 +103,7 @@ export default class EditorModel { } this._mergeAdjacentParts(); const caretOffset = diff.at - removedOffsetDecrease + addedLen; - let newPosition = this.positionForOffset(caretOffset, true); - newPosition = newPosition.skipUneditableParts(this._parts); + const newPosition = this.positionForOffset(caretOffset, true); this._setActivePart(newPosition); this._updateCallback(newPosition); } @@ -140,10 +139,9 @@ export default class EditorModel { let pos; if (replacePart) { this._replacePart(this._autoCompletePartIdx, replacePart); - let index = this._autoCompletePartIdx; + const index = this._autoCompletePartIdx; if (caretOffset === undefined) { - caretOffset = 0; - index += 1; + caretOffset = replacePart.text.length; } pos = new DocumentPosition(index, caretOffset); } @@ -283,13 +281,4 @@ 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; - } - } } From b16bc0178aecd6aa8775bd71709f7011b2354dba Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 19 Jun 2019 17:37:52 +0200 Subject: [PATCH 104/188] insert manually, as insertHTML command moves caret inconsistently across browsers --- src/components/views/elements/MessageEditor.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/views/elements/MessageEditor.js b/src/components/views/elements/MessageEditor.js index 9f5265cfd3..0830708701 100644 --- a/src/components/views/elements/MessageEditor.js +++ b/src/components/views/elements/MessageEditor.js @@ -78,6 +78,14 @@ export default class MessageEditor extends React.Component { this.model.update(text, event.inputType, caret); } + _insertText(textToInsert, inputType = "insertText") { + const sel = document.getSelection(); + const {caret, text} = getCaretOffsetAndText(this._editorRef, sel); + const newText = text.substr(0, caret.offset) + textToInsert + text.substr(caret.offset); + caret.offset += textToInsert.length; + this.model.update(newText, inputType, caret); + } + _isCaretAtStart() { const {caret} = getCaretOffsetAndText(this._editorRef, document.getSelection()); return caret.offset === 0; @@ -92,7 +100,7 @@ export default class MessageEditor extends React.Component { // 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"); + this._insertText("\n"); return; } // autocomplete or enter to send below shouldn't have any modifier keys pressed. From ff3c52a736abc8d1ac75fdcb3e89e5f85ebcb573 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 19 Jun 2019 14:59:59 -0600 Subject: [PATCH 105/188] Consider cancelled verifications when mounting IncomingSasDialog The cancellation can be because of a background problem, or because the user received another verification request from the same user. The cancel function does get called, however due to the speed of our dialog handling the state ends up being lost forever. Instead of trying to de-layer dialogs, this just fastforwards the whole dialog to "cancelled" on mount if required. Fixes https://github.com/vector-im/riot-web/issues/10118 --- src/components/views/dialogs/IncomingSasDialog.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/views/dialogs/IncomingSasDialog.js b/src/components/views/dialogs/IncomingSasDialog.js index da2211c10f..5158b004f3 100644 --- a/src/components/views/dialogs/IncomingSasDialog.js +++ b/src/components/views/dialogs/IncomingSasDialog.js @@ -46,6 +46,13 @@ export default class IncomingSasDialog extends React.Component { this._fetchOpponentProfile(); } + componentWillMount() { + if (this.props.verifier.cancelled) { + console.log("Verifier was cancelled in the background."); + this.setState({phase: PHASE_CANCELLED}); + } + } + componentWillUnmount() { if (this.state.phase !== PHASE_CANCELLED && this.state.phase !== PHASE_VERIFIED) { this.props.verifier.cancel('User cancel'); From bf443149b5cd08cac4b7f9016e600ba802111b6f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 19 Jun 2019 15:07:33 -0600 Subject: [PATCH 106/188] Make the verification cancelled dialog say OK instead of Cancel Fixes https://github.com/vector-im/riot-web/issues/9306 Includes unexpected cleanup of i18n --- src/components/views/verification/VerificationCancelled.js | 2 +- src/i18n/strings/en_EN.json | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/views/verification/VerificationCancelled.js b/src/components/views/verification/VerificationCancelled.js index b21153f2cc..baace2ca1e 100644 --- a/src/components/views/verification/VerificationCancelled.js +++ b/src/components/views/verification/VerificationCancelled.js @@ -31,7 +31,7 @@ export default class VerificationCancelled extends React.Component { "The other party cancelled the verification.", )}

diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c17b5d9fec..3edaaf6241 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -372,7 +372,7 @@ "Decline": "Decline", "Accept": "Accept", "The other party cancelled the verification.": "The other party cancelled the verification.", - "Cancel": "Cancel", + "OK": "OK", "Verified!": "Verified!", "You've successfully verified this user.": "You've successfully verified this user.", "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.", @@ -380,6 +380,7 @@ "Verify this user by confirming the following emoji appear on their screen.": "Verify this user by confirming the following emoji appear on their screen.", "Verify this user by confirming the following number appears on their screen.": "Verify this user by confirming the following number appears on their screen.", "Unable to find a supported verification method.": "Unable to find a supported verification method.", + "Cancel": "Cancel", "For maximum security, we recommend you do this in person or use another trusted means of communication.": "For maximum security, we recommend you do this in person or use another trusted means of communication.", "Dog": "Dog", "Cat": "Cat", @@ -518,7 +519,6 @@ "An error occurred whilst saving your email notification preferences.": "An error occurred whilst saving your email notification preferences.", "Keywords": "Keywords", "Enter keywords separated by a comma:": "Enter keywords separated by a comma:", - "OK": "OK", "Failed to change settings": "Failed to change settings", "Can't update user notification settings": "Can't update user notification settings", "Failed to update keywords": "Failed to update keywords", @@ -1145,7 +1145,6 @@ "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.", "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (Warning: this will cause future users to see an incomplete view of conversations)", "To continue, please enter your password:": "To continue, please enter your password:", - "password": "password", "Verify device": "Verify device", "Use Legacy Verification (for older clients)": "Use Legacy Verification (for older clients)", "Verify by comparing a short text string.": "Verify by comparing a short text string.", @@ -1335,7 +1334,6 @@ "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use this app with an existing Matrix account on a different homeserver.", "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.": "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.", "To continue, please enter your password.": "To continue, please enter your password.", - "Password:": "Password:", "Please review and accept all of the homeserver's policies": "Please review and accept all of the homeserver's policies", "Please review and accept the policies of this homeserver:": "Please review and accept the policies of this homeserver:", "An email has been sent to %(emailAddress)s": "An email has been sent to %(emailAddress)s", @@ -1567,7 +1565,6 @@ "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).", "Continue with previous account": "Continue with previous account", - "Sign out of previous account": "Sign out of previous account", "Log in to your new account.": "Log in to your new account.", "You can now close this window or log in to your new account.": "You can now close this window or log in to your new account.", "Registration Successful": "Registration Successful", From 1d4089dda7ef3f9082017432dc4f8c77a6c6f419 Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Thu, 20 Jun 2019 06:24:27 +0000 Subject: [PATCH 107/188] Translated using Weblate (Bulgarian) Currently translated at 100.0% (1661 of 1661 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/bg/ --- src/i18n/strings/bg.json | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/bg.json b/src/i18n/strings/bg.json index dd513abf82..b48232b085 100644 --- a/src/i18n/strings/bg.json +++ b/src/i18n/strings/bg.json @@ -1982,7 +1982,21 @@ "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Може да влезете в профила си, но някои функции няма да са достъпни докато сървъра за самоличност е офлайн. Ако продължавате да виждате това предупреждение, проверете конфигурацията или се свържете с администратора на сървъра.", "Unexpected error resolving identity server configuration": "Неочаквана грешка при откриване на конфигурацията на сървъра за самоличност", "Use lowercase letters, numbers, dashes and underscores only": "Използвайте само малки букви, цифри, тирета и подчерта", - "Log in to your new account.": "Влезте в новия си акаунт.", - "You can now close this window or log in to your new account.": "Можете да затворите този прозорец или да влезете в новия си акаунт.", - "Registration Successful": "Успешна регистрация" + "Log in to your new account.": "Влезте в новия си профил.", + "You can now close this window or log in to your new account.": "Можете да затворите този прозорец или да влезете в новия си профил.", + "Registration Successful": "Успешна регистрация", + "No integrations server configured": "Не е конфигуриран сървър за интеграции", + "This Riot instance does not have an integrations server configured.": "Тази Riot инсталация няма конфигуриран сървър за интеграции.", + "Connecting to integrations server...": "Свързване към сървъра за интеграции...", + "Cannot connect to integrations server": "Неуспешна връзка към сървъра за интеграции", + "The integrations server is offline or it cannot reach your homeserver.": "Сървърът за интеграции не работи или не може да се свърже с вашия сървър.", + "Unnamed microphone": "Микрофон без име", + "Unnamed audio output": "Аудио изход без име", + "Unnamed camera": "Камера без име", + "Failed to connect to integrations server": "Неуспешно свързване със сървъра за интеграции", + "No integrations server is configured to manage stickers with": "Няма конфигуриран сървър за интеграции, с който да се управляват стикерите", + "Upload all": "Качи всички", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Новият ви профил (%(newAccountId)s) е регистриран, но вече сте влезли с друг профил (%(loggedInUserId)s).", + "Continue with previous account": "Продължи с предишния профил", + "Sign out of previous account": "Излез от предишния профил" } From cbce373697a234eae10d0240a7dc10a7ed5ced2b Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 20 Jun 2019 00:56:20 +0000 Subject: [PATCH 108/188] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (1661 of 1661 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index b4b1cd1f05..3d9b570dfe 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -2007,5 +2007,8 @@ "Unnamed audio output": "未命名的音訊輸出", "Unnamed camera": "未命名的攝影機", "Failed to connect to integrations server": "連線到整合伺服器失敗", - "No integrations server is configured to manage stickers with": "未設定整合伺服器來管理貼圖" + "No integrations server is configured to manage stickers with": "未設定整合伺服器來管理貼圖", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "您的新帳號 %(newAccountId)s 已註冊,但您已經登入到不同的帳號 (%(loggedInUserId)s)。", + "Continue with previous account": "使用先前的帳號繼續", + "Sign out of previous account": "登出先前的帳號" } From a39420f72cf88aa11dca7b0fd2f76013bf42b77f Mon Sep 17 00:00:00 2001 From: Tirifto Date: Wed, 19 Jun 2019 19:54:03 +0000 Subject: [PATCH 109/188] Translated using Weblate (Esperanto) Currently translated at 83.3% (1384 of 1661 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/eo/ --- src/i18n/strings/eo.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/i18n/strings/eo.json b/src/i18n/strings/eo.json index 133d96b3df..1718b05cda 100644 --- a/src/i18n/strings/eo.json +++ b/src/i18n/strings/eo.json @@ -103,7 +103,7 @@ "Unknown (user, device) pair:": "Nekonata duopo (uzanto, aparato):", "Device already verified!": "Aparato jam kontroliĝis!", "WARNING: Device already verified, but keys do NOT MATCH!": "AVERTO: Aparato jam kontroliĝis, sed la ŝlosiloj NE KONGRUAS!", - "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "AVERTO: KONTROLO DE ŜLOSILO MALSUKCESIS! Subskriba ŝlosilo por %(userId)s kaj aparato%(deviceId)s estas \"%(fprint)s\", kiu ne kongruas kun la donita ŝlosilo \"%(fingerprint)s\". Eble do via komuniko estas subaŭskultata!", + "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "AVERTO: KONTROLO DE ŜLOSILO MALSUKCESIS! Subskriba ŝlosilo por %(userId)s kaj aparato%(deviceId)s estas « %(fprint)s », kiu ne kongruas kun la donita ŝlosilo « %(fingerprint)s ». Eble do via komuniko estas subaŭskultata!", "Verified key": "Kontrolita ŝlosilo", "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "La donita subskriba ŝlosilo kongruas kun la ŝlosilo ricevita de %(userId)s por ĝia aparato %(deviceId)s. Aparato markita kiel kontrolita.", "Unrecognised command:": "Nerekonita komando:", @@ -126,7 +126,7 @@ "%(senderName)s unbanned %(targetName)s.": "%(senderName)s malbaris uzanton %(targetName)s.", "%(senderName)s kicked %(targetName)s.": "%(senderName)s forpelis uzanton %(targetName)s.", "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s nuligis inviton por %(targetName)s.", - "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s ŝanĝis la temon al \"%(topic)s\".", + "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s ŝanĝis la temon al « %(topic)s ».", "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s forigis nomon de la ĉambro.", "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s ŝanĝis nomon de la ĉambro al %(roomName)s.", "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s sendis bildon.", @@ -683,7 +683,7 @@ "You are currently blacklisting unverified devices; to send messages to these devices you must verify them.": "Vi nun malpermesas legadon de ĉifritaj mesaĝoj al nekontrolitaj aparatoj; por sendi mesaĝojn al tiuj, vi devas ilin kontroli.", "We recommend you go through the verification process for each device to confirm they belong to their legitimate owner, but you can resend the message without verifying if you prefer.": "Ni rekomendas al vi bone kontroli ĉiun aparaton por certigi, ke ĝi apartenas al la verŝajna posedanto, sed vi povas resendi la mesaĝon sen kontrolo, laŭprefere.", "Room contains unknown devices": "Ĉambro enhavas nekonatajn aparatojn", - "\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" enhavas aparatojn, kiujn vi neniam vidis antaŭe.", + "\"%(RoomName)s\" contains devices that you haven't seen before.": "« %(RoomName)s » enhavas aparatojn, kiujn vi neniam vidis antaŭe.", "Unknown devices": "Nekonataj aparatoj", "Private Chat": "Privata babilo", "Public Chat": "Publika babilo", @@ -697,7 +697,7 @@ "You must register to use this functionality": "Vi devas registriĝî por uzi tiun ĉi funkcion", "You must join the room to see its files": "Vi devas aliĝi al la ĉambro por vidi tie dosierojn", "There are no visible files in this room": "En ĉi tiu ĉambro estas neniaj videblaj dosieroj", - "

HTML for your community's page

\n

\n Use the long description to introduce new members to the community, or distribute\n some important links\n

\n

\n You can even use 'img' tags\n

\n": "

HTML por la paĝo de via komunumo

\n

\n Uzu la longan priskribon por enkonduki novajn komunumanojn, aŭ disdoni iujn\n gravajn ligilojn\n

\n

\n Vi povas eĉ uzi etikedojn 'img'\n

\n", + "

HTML for your community's page

\n

\n Use the long description to introduce new members to the community, or distribute\n some important links\n

\n

\n You can even use 'img' tags\n

\n": "

HTML por la paĝo de via komunumo

\n

\n Uzu la longan priskribon por enkonduki novajn komunumanojn, aŭ disdoni iujn\n gravajn ligilojn\n

\n

\n Vi povas eĉ uzi etikedojn « img »\n

\n", "Add rooms to the community summary": "Aldoni ĉambrojn al la komunuma superrigardo", "Which rooms would you like to add to this summary?": "Kiujn ĉambrojn vi volas aldoni al ĉi tiu superrigardo?", "Add to summary": "Aldoni al superrigardo", @@ -1146,9 +1146,9 @@ "Reversed words aren't much harder to guess": "Renversitaj vortoj ne estas multe pli malfacile konjekteblaj", "Predictable substitutions like '@' instead of 'a' don't help very much": "Facile diveneblaj anstataŭigoj, kiel '@' anstataŭ 'a', ne helpas multe", "Add another word or two. Uncommon words are better.": "Aldonu alian vorton aŭ du. Maloftaj vortoj pli bonas.", - "Repeats like \"aaa\" are easy to guess": "Ripetoj kiel \"aaa\" estas facile diveneblaj", - "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"": "Ripetoj kiel \"abcabcabc\" estas apenaŭ pli bonaj ol nur \"abc\"", - "Sequences like abc or 6543 are easy to guess": "Sinsekvoj kiel \"abc\" aŭ \"6543\" estas facile diveneblaj", + "Repeats like \"aaa\" are easy to guess": "Ripetoj kiel « aaa » estas facile diveneblaj", + "Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"": "Ripetoj kiel « abcabcabc » estas apenaŭ pli bonaj ol nur « abc »", + "Sequences like abc or 6543 are easy to guess": "Sinsekvoj kiel « abc » aŭ « 6543 » estas facile diveneblaj", "Recent years are easy to guess": "Freŝdataj jaroj estas facile diveneblaj", "Dates are often easy to guess": "Datoj estas ofte facile diveneblaj", "This is a top-10 common password": "Ĉi tiu pasvorto estas inter la 10 plej oftaj", @@ -1396,7 +1396,7 @@ "A conference call could not be started because the integrations server is not available": "Grupa voko ne povis komenciĝi, ĉar la kuniga servilo estas neatingebla", "Replying With Files": "Respondado kun dosieroj", "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "Nun ne eblas respondi kun dosiero. Ĉu vi volas alŝuti la dosieron sen respondo?", - "The file '%(fileName)s' failed to upload.": "Malsukcesis alŝuti dosieron «%(fileName)s».", + "The file '%(fileName)s' failed to upload.": "Malsukcesis alŝuti dosieron « %(fileName)s ».", "The server does not support the room version specified.": "La servilo ne subtenas la donitan ĉambran version.", "Name or Matrix ID": "Nomo aŭ Matrix-identigilo", "Email, name or Matrix ID": "Retpoŝtadreso, nomo, aŭ Matrix-identigilo", @@ -1508,7 +1508,7 @@ "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Ŝanĝoj al viaj komunumaj nomo kaj profilbildo eble ne montriĝos al aliaj uzantoj ĝis 30 minutoj.", "Who can join this community?": "Kiu povas aliĝi al tiu ĉi komunumo?", "This room is not public. You will not be able to rejoin without an invite.": "Ĉi tiu ĉambro ne estas publika. Vi ne povos realiĝi sen invito.", - "Can't leave Server Notices room": "Ne eblas eliri el ĉambro « Server Notices »", + "Can't leave Server Notices room": "Ne eblas eliri el ĉambro «  Server Notices  »", "Revoke invite": "Nuligi inviton", "Invited by %(sender)s": "Invitita de %(sender)s", "Error updating main address": "Eraro dum ĝisdatigo de la ĉefa adreso", From a03a2d5b0f16fd6fda59bbc6bcf4f68d0082b72c Mon Sep 17 00:00:00 2001 From: random Date: Thu, 20 Jun 2019 09:12:54 +0000 Subject: [PATCH 110/188] Translated using Weblate (Italian) Currently translated at 100.0% (1661 of 1661 strings) Translation: Riot Web/matrix-react-sdk Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/it/ --- src/i18n/strings/it.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json index 06e3c7b573..7b6e3a61aa 100644 --- a/src/i18n/strings/it.json +++ b/src/i18n/strings/it.json @@ -1957,5 +1957,18 @@ "Upload all": "Invia tutto", "Log in to your new account.": "Accedi al tuo nuovo account.", "You can now close this window or log in to your new account.": "Ora puoi chiudere questa finestra o accedere al tuo nuovo account.", - "Registration Successful": "Registrazione riuscita" + "Registration Successful": "Registrazione riuscita", + "No integrations server configured": "Nessun server di integrazione configurato", + "This Riot instance does not have an integrations server configured.": "Questa istanza di Riot non ha un server di integrazione configurato.", + "Connecting to integrations server...": "Connessione al server di integrazione...", + "Cannot connect to integrations server": "Impossibile connettersi al server di integrazione", + "The integrations server is offline or it cannot reach your homeserver.": "Il server di integrazione è offline o non può raggiungere il tuo homeserver.", + "Unnamed microphone": "Microfono senza nome", + "Unnamed audio output": "Uscita audio senza nome", + "Unnamed camera": "Fotocamera senza nome", + "Failed to connect to integrations server": "Connessione al server di integrazione fallita", + "No integrations server is configured to manage stickers with": "Nessun server di integrazione configurato con cui gestire gli adesivi", + "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Il tuo nuovo account (%(newAccountId)s) è registrato, ma hai già fatto l'accesso in un account diverso (%(loggedInUserId)s).", + "Continue with previous account": "Continua con l'account precedente", + "Sign out of previous account": "Disconnetti dall'account precedente" } From 366a4aa3081c91d8bfba302294236333161f1172 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 20 Jun 2019 14:44:18 +0200 Subject: [PATCH 111/188] put zero-width spaces in caret nodes so chrome doesn't ignore them this requires an update of the editor DOM > text & caret offset logic, as the ZWS need to be ignored. --- src/editor/dom.js | 73 +++++++++++++++++++++++++++++++++++--------- src/editor/render.js | 18 +++++------ 2 files changed, 68 insertions(+), 23 deletions(-) diff --git a/src/editor/dom.js b/src/editor/dom.js index 3ef1df24c3..5c873034b2 100644 --- a/src/editor/dom.js +++ b/src/editor/dom.js @@ -15,6 +15,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import {ZERO_WIDTH_SPACE, isCaretNode} from "./render"; + export function walkDOMDepthFirst(rootNode, enterNodeCallback, leaveNodeCallback) { let node = rootNode.firstChild; while (node && node !== rootNode) { @@ -38,27 +40,54 @@ export function walkDOMDepthFirst(rootNode, enterNodeCallback, leaveNodeCallback } export function getCaretOffsetAndText(editor, sel) { - let {focusNode} = sel; - const {focusOffset} = sel; - let caretOffset = focusOffset; + let {focusNode, focusOffset} = sel; + // sometimes focusNode is an element, and then focusOffset means + // the index of a child element ... - 1 🤷 + if (focusNode.nodeType === Node.ELEMENT_NODE && focusOffset !== 0) { + focusNode = focusNode.childNodes[focusOffset - 1]; + focusOffset = focusNode.textContent.length; + } + const {text, focusNodeOffset} = getTextAndFocusNodeOffset(editor, focusNode, focusOffset); + const caret = getCaret(focusNode, focusNodeOffset, focusOffset); + return {caret, text}; +} + +// gets the caret position details, ignoring and adjusting to +// the ZWS if you're typing in a caret node +function getCaret(focusNode, focusNodeOffset, focusOffset) { + let atNodeEnd = focusOffset === focusNode.textContent.length; + if (focusNode.nodeType === Node.TEXT_NODE && isCaretNode(focusNode.parentElement)) { + const zwsIdx = focusNode.nodeValue.indexOf(ZERO_WIDTH_SPACE); + if (zwsIdx !== -1 && zwsIdx < focusOffset) { + focusOffset -= 1; + } + // if typing in a caret node, you're either typing before or after the ZWS. + // In both cases, you should be considered at node end because the ZWS is + // not included in the text here, and once the model is updated and rerendered, + // that caret node will be removed. + atNodeEnd = true; + } + return {offset: focusNodeOffset + focusOffset, atNodeEnd}; +} + +// gets the text of the editor as a string, +// and the offset in characters where the focusNode starts in that string +// all ZWS from caret nodes are filtered out +function getTextAndFocusNodeOffset(editor, focusNode, focusOffset) { + let focusNodeOffset = 0; let foundCaret = false; let text = ""; - if (focusNode.nodeType === Node.ELEMENT_NODE && focusOffset !== 0) { - focusNode = focusNode.childNodes[focusOffset - 1]; - caretOffset = focusNode.textContent.length; - } - function enterNodeCallback(node) { - const nodeText = node.nodeType === Node.TEXT_NODE && node.nodeValue; if (!foundCaret) { if (node === focusNode) { foundCaret = true; } } + const nodeText = node.nodeType === Node.TEXT_NODE && getTextNodeValue(node); if (nodeText) { if (!foundCaret) { - caretOffset += nodeText.length; + focusNodeOffset += nodeText.length; } text += nodeText; } @@ -73,14 +102,30 @@ export function getCaretOffsetAndText(editor, sel) { if (node.tagName === "DIV" && node.nextSibling && node.nextSibling.tagName === "DIV") { text += "\n"; if (!foundCaret) { - caretOffset += 1; + focusNodeOffset += 1; } } } walkDOMDepthFirst(editor, enterNodeCallback, leaveNodeCallback); - const atNodeEnd = sel.focusOffset === sel.focusNode.textContent.length; - const caret = {atNodeEnd, offset: caretOffset}; - return {caret, text}; + return {text, focusNodeOffset}; +} + +// get text value of text node, ignoring ZWS if it's a caret node +function getTextNodeValue(node) { + const nodeText = node.nodeValue; + // filter out ZWS for caret nodes + if (isCaretNode(node.parentElement)) { + // typed in the caret node, so there is now something more in it than the ZWS + // so filter out the ZWS, and take the typed text into account + if (nodeText.length !== 1) { + return nodeText.replace(ZERO_WIDTH_SPACE, ""); + } else { + // only contains ZWS, which is ignored, so return emtpy string + return ""; + } + } else { + return nodeText; + } } diff --git a/src/editor/render.js b/src/editor/render.js index ed354ab8f5..97d84d70b2 100644 --- a/src/editor/render.js +++ b/src/editor/render.js @@ -33,25 +33,25 @@ function insertAfter(node, nodeToInsert) { } } -// a caret node is an empty node that allows the caret to be place +export const ZERO_WIDTH_SPACE = "\u200b"; +// a caret node is a node that allows the caret to be placed // where otherwise it wouldn't be possible // (e.g. next to a pill span without adjacent text node) function createCaretNode() { const span = document.createElement("span"); span.className = "caret"; + span.appendChild(document.createTextNode(ZERO_WIDTH_SPACE)); return span; } function updateCaretNode(node) { - // ensure the caret node is empty - // otherwise they'll break everything - // as only things part of the model should have text in them - // browsers could end up typing in the caret node for any - // number of reasons, so revert this. - node.textContent = ""; + // ensure the caret node contains only a zero-width space + if (node.textContent !== ZERO_WIDTH_SPACE) { + node.textContent = ZERO_WIDTH_SPACE; + } } -function isCaretNode(node) { +export function isCaretNode(node) { return node && node.tagName === "SPAN" && node.className === "caret"; } @@ -86,8 +86,8 @@ function reconcileLine(lineContainer, parts) { if (needsCaretNodeBefore(part, prevPart)) { if (isCaretNode(currentNode)) { - currentNode = currentNode.nextSibling; updateCaretNode(currentNode); + currentNode = currentNode.nextSibling; } else { lineContainer.insertBefore(createCaretNode(), currentNode); } From 0f8dd102bf9707069921a89bf593a17cd6f49792 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 20 Jun 2019 14:17:06 -0600 Subject: [PATCH 112/188] Move early-cancel stuff to constructor --- src/components/views/dialogs/IncomingSasDialog.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/views/dialogs/IncomingSasDialog.js b/src/components/views/dialogs/IncomingSasDialog.js index 5158b004f3..0720fedddc 100644 --- a/src/components/views/dialogs/IncomingSasDialog.js +++ b/src/components/views/dialogs/IncomingSasDialog.js @@ -34,9 +34,15 @@ export default class IncomingSasDialog extends React.Component { constructor(props) { super(props); + let phase = PHASE_START; + if (this.props.verifier.cancelled) { + console.log("Verifier was cancelled in the background."); + phase = PHASE_CANCELLED; + } + this._showSasEvent = null; this.state = { - phase: PHASE_START, + phase: phase, sasVerified: false, opponentProfile: null, opponentProfileError: null, @@ -46,13 +52,6 @@ export default class IncomingSasDialog extends React.Component { this._fetchOpponentProfile(); } - componentWillMount() { - if (this.props.verifier.cancelled) { - console.log("Verifier was cancelled in the background."); - this.setState({phase: PHASE_CANCELLED}); - } - } - componentWillUnmount() { if (this.state.phase !== PHASE_CANCELLED && this.state.phase !== PHASE_VERIFIED) { this.props.verifier.cancel('User cancel'); From dea412c9067344c53c9326041d31fea20425826a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 21 Jun 2019 09:03:28 +0100 Subject: [PATCH 113/188] Add file size to UploadConfirmDialog Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/dialogs/UploadConfirmDialog.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/UploadConfirmDialog.js b/src/components/views/dialogs/UploadConfirmDialog.js index 7e682a8301..98c031e89b 100644 --- a/src/components/views/dialogs/UploadConfirmDialog.js +++ b/src/components/views/dialogs/UploadConfirmDialog.js @@ -1,5 +1,6 @@ /* Copyright 2019 New Vector Ltd +Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,6 +19,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import sdk from '../../../index'; import { _t } from '../../../languageHandler'; +import filesize from "filesize"; export default class UploadConfirmDialog extends React.Component { static propTypes = { @@ -75,7 +77,7 @@ export default class UploadConfirmDialog extends React.Component { preview =
-
{this.props.file.name}
+
{this.props.file.name} ({filesize(this.props.file.size)})
; } else { @@ -84,7 +86,7 @@ export default class UploadConfirmDialog extends React.Component { - {this.props.file.name} + {this.props.file.name} ({filesize(this.props.file.size)})
; } From c5c987f62ed3bd964655de4683eb7821e4ee5cba Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 21 Jun 2019 11:21:13 +0200 Subject: [PATCH 114/188] use BOM marker instead of ZWS that's what others do ... --- src/editor/render.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/editor/render.js b/src/editor/render.js index 97d84d70b2..eb15230580 100644 --- a/src/editor/render.js +++ b/src/editor/render.js @@ -33,7 +33,8 @@ function insertAfter(node, nodeToInsert) { } } -export const ZERO_WIDTH_SPACE = "\u200b"; +// this is a BOM marker actually +export const ZERO_WIDTH_SPACE = "\ufeff"; // a caret node is a node that allows the caret to be placed // where otherwise it wouldn't be possible // (e.g. next to a pill span without adjacent text node) From da766b8cbaa1c39a4402144fae67361b825f7c89 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 21 Jun 2019 11:21:38 +0200 Subject: [PATCH 115/188] caretNode is a better className --- src/editor/render.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/render.js b/src/editor/render.js index eb15230580..690851c4ea 100644 --- a/src/editor/render.js +++ b/src/editor/render.js @@ -40,7 +40,7 @@ export const ZERO_WIDTH_SPACE = "\ufeff"; // (e.g. next to a pill span without adjacent text node) function createCaretNode() { const span = document.createElement("span"); - span.className = "caret"; + span.className = "caretNode"; span.appendChild(document.createTextNode(ZERO_WIDTH_SPACE)); return span; } @@ -53,7 +53,7 @@ function updateCaretNode(node) { } export function isCaretNode(node) { - return node && node.tagName === "SPAN" && node.className === "caret"; + return node && node.tagName === "SPAN" && node.className === "caretNode"; } function removeNextSiblings(node) { From 503d702f47338d876565c8bb42eb4bd493c04636 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 21 Jun 2019 11:40:27 +0200 Subject: [PATCH 116/188] take list nesting into account for indenting --- src/editor/deserialize.js | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/editor/deserialize.js b/src/editor/deserialize.js index 7e4e82affe..6507a8dc12 100644 --- a/src/editor/deserialize.js +++ b/src/editor/deserialize.js @@ -70,7 +70,7 @@ function parseCodeBlock(n, partCreator) { return parts; } -function parseElement(n, partCreator) { +function parseElement(n, partCreator, state) { switch (n.nodeName) { case "A": return parseLink(n, partCreator); @@ -86,12 +86,18 @@ function parseElement(n, partCreator) { return partCreator.plain(`\`${n.textContent}\``); case "DEL": return partCreator.plain(`${n.textContent}`); - case "LI": + case "LI": { + const indent = " ".repeat(state.listDepth - 1); if (n.parentElement.nodeName === "OL") { - return partCreator.plain(` 1. `); + return partCreator.plain(`${indent}1. `); } else { - return partCreator.plain(` - `); + return partCreator.plain(`${indent}- `); } + } + case "OL": + case "UL": + state.listDepth = (state.listDepth || 0) + 1; + // es-lint-disable-next-line no-fallthrough default: // don't textify block nodes we'll decend into if (!checkDecendInto(n)) { @@ -161,6 +167,7 @@ function parseHtmlMessage(html, partCreator) { const parts = []; let lastNode; let inQuote = false; + const state = {}; function onNodeEnter(n) { if (checkIgnored(n)) { @@ -178,7 +185,7 @@ function parseHtmlMessage(html, partCreator) { if (n.nodeType === Node.TEXT_NODE) { newParts.push(...parseAtRoomMentions(n.nodeValue, partCreator)); } else if (n.nodeType === Node.ELEMENT_NODE) { - const parseResult = parseElement(n, partCreator); + const parseResult = parseElement(n, partCreator, state); if (parseResult) { if (Array.isArray(parseResult)) { newParts.push(...parseResult); @@ -207,8 +214,14 @@ function parseHtmlMessage(html, partCreator) { if (checkIgnored(n)) { return; } - if (n.nodeName === "BLOCKQUOTE") { - inQuote = false; + switch (n.nodeName) { + case "BLOCKQUOTE": + inQuote = false; + break; + case "OL": + case "UL": + state.listDepth -= 1; + break; } lastNode = n; } From b5598df3f793b942f91137e0d98c118e9792317a Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 21 Jun 2019 11:06:27 +0100 Subject: [PATCH 117/188] Remove redundant extra chevrons from ContextualMenu This removes a seemingly redundant layer of extra chevrons from `ContextualMenu`. Since both chevrons are the same color, there should be no visual change. --- res/css/structures/_ContextualMenu.scss | 49 +------------------------ 1 file changed, 1 insertion(+), 48 deletions(-) diff --git a/res/css/structures/_ContextualMenu.scss b/res/css/structures/_ContextualMenu.scss index e818bb092e..fa2d87029d 100644 --- a/res/css/structures/_ContextualMenu.scss +++ b/res/css/structures/_ContextualMenu.scss @@ -1,5 +1,6 @@ /* Copyright 2015, 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. @@ -58,18 +59,6 @@ limitations under the License. border-bottom: 8px solid transparent; } -.mx_ContextualMenu_chevron_right::after { - content: ''; - width: 0; - height: 0; - border-top: 7px solid transparent; - border-left: 7px solid $menu-bg-color; - border-bottom: 7px solid transparent; - position: absolute; - top: -7px; - right: 1px; -} - .mx_ContextualMenu_left { left: 0; } @@ -89,18 +78,6 @@ limitations under the License. border-bottom: 8px solid transparent; } -.mx_ContextualMenu_chevron_left::after { - content: ''; - width: 0; - height: 0; - border-top: 7px solid transparent; - border-right: 7px solid $menu-bg-color; - border-bottom: 7px solid transparent; - position: absolute; - top: -7px; - left: 1px; -} - .mx_ContextualMenu_top { top: 0; } @@ -120,18 +97,6 @@ limitations under the License. border-right: 8px solid transparent; } -.mx_ContextualMenu_chevron_top::after { - content: ''; - width: 0; - height: 0; - border-left: 7px solid transparent; - border-bottom: 7px solid $menu-bg-color; - border-right: 7px solid transparent; - position: absolute; - left: -7px; - top: 1px; -} - .mx_ContextualMenu_bottom { bottom: 0; } @@ -151,18 +116,6 @@ limitations under the License. border-right: 8px solid transparent; } -.mx_ContextualMenu_chevron_bottom::after { - content: ''; - width: 0; - height: 0; - border-left: 7px solid transparent; - border-top: 7px solid $menu-bg-color; - border-right: 7px solid transparent; - position: absolute; - left: -7px; - bottom: 1px; -} - .mx_ContextualMenu_spinner { display: block; margin: 0 auto; From cf0799289db2ec89f4ed09111a2c4c55eb3a4bc9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 21 Jun 2019 13:48:52 +0200 Subject: [PATCH 118/188] Disable left/right arrow navigating completions for now as the autocomplete is now very eager to appear, this breaks caret navigation when typing e.g. anything with a colon. Ideally, we should make the AC less eager to appear, but this is a quick fix for now. --- src/components/views/rooms/MessageComposerInput.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index bf0287d376..7a64e9ad51 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -682,14 +682,6 @@ export default class MessageComposerInput extends React.Component { if (this.autocomplete.countCompletions() > 0) { if (!(ev.ctrlKey || ev.shiftKey || ev.altKey || ev.metaKey)) { switch (ev.keyCode) { - case KeyCode.LEFT: - this.autocomplete.moveSelection(-1); - ev.preventDefault(); - return true; - case KeyCode.RIGHT: - this.autocomplete.moveSelection(+1); - ev.preventDefault(); - return true; case KeyCode.UP: this.autocomplete.moveSelection(-1); ev.preventDefault(); From 6dcdad028e94f7e9fc3d5fbb5a315c12f726ab7e Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 20 Jun 2019 12:03:22 +0100 Subject: [PATCH 119/188] Clone ContextualMenu to InteractiveTooltip As part of reactions and editing work, we're adding a new style of tooltip that allows interacting with the content of the tooltip. `ContextualMenu` is closest out of the things we have today, but it doesn't position in quite the way we want and it's already quite complex. To get started, let's first clone that to a new `InteractiveTooltip`. Part of https://github.com/vector-im/riot-web/issues/9753 Part of https://github.com/vector-im/riot-web/issues/9716 --- res/css/_components.scss | 1 + .../views/elements/_InteractiveTooltip.scss | 164 ++++++++++++++++++ .../views/elements/InteractiveTooltip.js | 141 +++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 res/css/views/elements/_InteractiveTooltip.scss create mode 100644 src/components/views/elements/InteractiveTooltip.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 582dc59517..fa388c4e6a 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -86,6 +86,7 @@ @import "./views/elements/_Field.scss"; @import "./views/elements/_ImageView.scss"; @import "./views/elements/_InlineSpinner.scss"; +@import "./views/elements/_InteractiveTooltip.scss"; @import "./views/elements/_ManageIntegsButton.scss"; @import "./views/elements/_MemberEventListSummary.scss"; @import "./views/elements/_MessageEditor.scss"; diff --git a/res/css/views/elements/_InteractiveTooltip.scss b/res/css/views/elements/_InteractiveTooltip.scss new file mode 100644 index 0000000000..11f548fa18 --- /dev/null +++ b/res/css/views/elements/_InteractiveTooltip.scss @@ -0,0 +1,164 @@ +/* +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_InteractiveTooltip_wrapper { + position: fixed; + z-index: 5000; +} + +.mx_InteractiveTooltip_background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 1.0; + z-index: 5000; +} + +.mx_InteractiveTooltip { + border-radius: 4px; + box-shadow: 4px 4px 12px 0 $menu-box-shadow-color; + background-color: $menu-bg-color; + color: $primary-fg-color; + position: absolute; + font-size: 14px; + z-index: 5001; +} + +.mx_InteractiveTooltip_right { + right: 0; +} + +.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_right { + right: 8px; +} + +.mx_InteractiveTooltip_chevron_right { + position: absolute; + right: -8px; + top: 0px; + width: 0; + height: 0; + border-top: 8px solid transparent; + border-left: 8px solid $menu-bg-color; + border-bottom: 8px solid transparent; +} + +.mx_InteractiveTooltip_chevron_right::after { + content: ''; + width: 0; + height: 0; + border-top: 7px solid transparent; + border-left: 7px solid $menu-bg-color; + border-bottom: 7px solid transparent; + position: absolute; + top: -7px; + right: 1px; +} + +.mx_InteractiveTooltip_left { + left: 0; +} + +.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_left { + left: 8px; +} + +.mx_InteractiveTooltip_chevron_left { + position: absolute; + left: -8px; + top: 0px; + width: 0; + height: 0; + border-top: 8px solid transparent; + border-right: 8px solid $menu-bg-color; + border-bottom: 8px solid transparent; +} + +.mx_InteractiveTooltip_chevron_left::after { + content: ''; + width: 0; + height: 0; + border-top: 7px solid transparent; + border-right: 7px solid $menu-bg-color; + border-bottom: 7px solid transparent; + position: absolute; + top: -7px; + left: 1px; +} + +.mx_InteractiveTooltip_top { + top: 0; +} + +.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_top { + top: 8px; +} + +.mx_InteractiveTooltip_chevron_top { + position: absolute; + left: 0px; + top: -8px; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-bottom: 8px solid $menu-bg-color; + border-right: 8px solid transparent; +} + +.mx_InteractiveTooltip_chevron_top::after { + content: ''; + width: 0; + height: 0; + border-left: 7px solid transparent; + border-bottom: 7px solid $menu-bg-color; + border-right: 7px solid transparent; + position: absolute; + left: -7px; + top: 1px; +} + +.mx_InteractiveTooltip_bottom { + bottom: 0; +} + +.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_bottom { + bottom: 8px; +} + +.mx_InteractiveTooltip_chevron_bottom { + position: absolute; + left: 0px; + bottom: -8px; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-top: 8px solid $menu-bg-color; + border-right: 8px solid transparent; +} + +.mx_InteractiveTooltip_chevron_bottom::after { + content: ''; + width: 0; + height: 0; + border-left: 7px solid transparent; + border-top: 7px solid $menu-bg-color; + border-right: 7px solid transparent; + position: absolute; + left: -7px; + bottom: 1px; +} diff --git a/src/components/views/elements/InteractiveTooltip.js b/src/components/views/elements/InteractiveTooltip.js new file mode 100644 index 0000000000..7c582a2b71 --- /dev/null +++ b/src/components/views/elements/InteractiveTooltip.js @@ -0,0 +1,141 @@ +/* +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 ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +const InteractiveTooltipContainerId = "mx_InteractiveTooltip_Container"; + +function getOrCreateContainer() { + let container = document.getElementById(InteractiveTooltipContainerId); + + if (!container) { + container = document.createElement("div"); + container.id = InteractiveTooltipContainerId; + document.body.appendChild(container); + } + + return container; +} + +export default class InteractiveTooltip extends React.Component { + propTypes: { + top: PropTypes.number, + bottom: PropTypes.number, + left: PropTypes.number, + right: PropTypes.number, + chevronOffset: PropTypes.number, + chevronFace: PropTypes.string, // top, bottom, left, right or none + // Function to be called on menu close + onFinished: PropTypes.func, + + // If true, insert an invisible screen-sized element behind the + // menu that when clicked will close it. + hasBackground: PropTypes.bool, + + // The component to render as the context menu + elementClass: PropTypes.element.isRequired, + // on resize callback + windowResize: PropTypes.func, + // method to close menu + closeTooltip: PropTypes.func, + }; + + render() { + const position = {}; + let chevronFace = null; + const props = this.props; + + if (props.top) { + position.top = props.top; + } else { + position.bottom = props.bottom; + } + + if (props.left) { + position.left = props.left; + chevronFace = 'left'; + } else { + position.right = props.right; + chevronFace = 'right'; + } + + const chevronOffset = {}; + if (props.chevronFace) { + chevronFace = props.chevronFace; + } + const hasChevron = chevronFace && chevronFace !== "none"; + + if (chevronFace === 'top' || chevronFace === 'bottom') { + chevronOffset.left = props.chevronOffset; + } else { + chevronOffset.top = props.chevronOffset; + } + + const chevron = hasChevron ? +
: + undefined; + const className = 'mx_InteractiveTooltip_wrapper'; + + const menuClasses = classNames({ + 'mx_InteractiveTooltip': true, + 'mx_InteractiveTooltip_left': !hasChevron && position.left, + 'mx_InteractiveTooltip_right': !hasChevron && position.right, + 'mx_InteractiveTooltip_top': !hasChevron && position.top, + 'mx_InteractiveTooltip_bottom': !hasChevron && position.bottom, + 'mx_InteractiveTooltip_withChevron_left': chevronFace === 'left', + 'mx_InteractiveTooltip_withChevron_right': chevronFace === 'right', + 'mx_InteractiveTooltip_withChevron_top': chevronFace === 'top', + 'mx_InteractiveTooltip_withChevron_bottom': chevronFace === 'bottom', + }); + + const ElementClass = props.elementClass; + + return
+
+ { chevron } + +
+ { props.hasBackground &&
} +
; + } +} + +export function createTooltip(ElementClass, props, hasBackground=true) { + const closeTooltip = function(...args) { + ReactDOM.unmountComponentAtNode(getOrCreateContainer()); + + if (props && props.onFinished) { + props.onFinished.apply(null, args); + } + }; + + // We only reference closeTooltip once per call to createTooltip + const menu = ; + + ReactDOM.render(menu, getOrCreateContainer()); + + return {close: closeTooltip}; +} From 32bf4588dd3fa38c0169d9b1594de6e8fec39603 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 20 Jun 2019 18:33:45 +0100 Subject: [PATCH 120/188] Center tooltip along top or bottom of target This adjusts the positioning to work more the way we want: * Tooltip is position on the top or bottom edge of the target depending on where space is available * Tooltip and chevron are centered In addition, more bits borrowed from `ContextualMenu` are not needed, so they have been removed for simplicity. Part of https://github.com/vector-im/riot-web/issues/9753 Part of https://github.com/vector-im/riot-web/issues/9716 --- .../views/elements/_InteractiveTooltip.scss | 74 +-------------- .../views/elements/InteractiveTooltip.js | 92 ++++++++++--------- 2 files changed, 50 insertions(+), 116 deletions(-) diff --git a/res/css/views/elements/_InteractiveTooltip.scss b/res/css/views/elements/_InteractiveTooltip.scss index 11f548fa18..3ec20be928 100644 --- a/res/css/views/elements/_InteractiveTooltip.scss +++ b/res/css/views/elements/_InteractiveTooltip.scss @@ -39,79 +39,13 @@ limitations under the License. z-index: 5001; } -.mx_InteractiveTooltip_right { - right: 0; -} - -.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_right { - right: 8px; -} - -.mx_InteractiveTooltip_chevron_right { - position: absolute; - right: -8px; - top: 0px; - width: 0; - height: 0; - border-top: 8px solid transparent; - border-left: 8px solid $menu-bg-color; - border-bottom: 8px solid transparent; -} - -.mx_InteractiveTooltip_chevron_right::after { - content: ''; - width: 0; - height: 0; - border-top: 7px solid transparent; - border-left: 7px solid $menu-bg-color; - border-bottom: 7px solid transparent; - position: absolute; - top: -7px; - right: 1px; -} - -.mx_InteractiveTooltip_left { - left: 0; -} - -.mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_left { - left: 8px; -} - -.mx_InteractiveTooltip_chevron_left { - position: absolute; - left: -8px; - top: 0px; - width: 0; - height: 0; - border-top: 8px solid transparent; - border-right: 8px solid $menu-bg-color; - border-bottom: 8px solid transparent; -} - -.mx_InteractiveTooltip_chevron_left::after { - content: ''; - width: 0; - height: 0; - border-top: 7px solid transparent; - border-right: 7px solid $menu-bg-color; - border-bottom: 7px solid transparent; - position: absolute; - top: -7px; - left: 1px; -} - -.mx_InteractiveTooltip_top { - top: 0; -} - .mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_top { top: 8px; } .mx_InteractiveTooltip_chevron_top { position: absolute; - left: 0px; + left: calc(50% - 8px); top: -8px; width: 0; height: 0; @@ -132,17 +66,13 @@ limitations under the License. top: 1px; } -.mx_InteractiveTooltip_bottom { - bottom: 0; -} - .mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_bottom { bottom: 8px; } .mx_InteractiveTooltip_chevron_bottom { position: absolute; - left: 0px; + left: calc(50% - 8px); bottom: -8px; width: 0; height: 0; diff --git a/src/components/views/elements/InteractiveTooltip.js b/src/components/views/elements/InteractiveTooltip.js index 7c582a2b71..b3d0b32fa7 100644 --- a/src/components/views/elements/InteractiveTooltip.js +++ b/src/components/views/elements/InteractiveTooltip.js @@ -33,21 +33,19 @@ function getOrCreateContainer() { return container; } +/* + * This style of tooltip takes a `target` element's rect and centers the tooltip + * along one edge of the target. + */ export default class InteractiveTooltip extends React.Component { propTypes: { - top: PropTypes.number, - bottom: PropTypes.number, - left: PropTypes.number, - right: PropTypes.number, - chevronOffset: PropTypes.number, - chevronFace: PropTypes.string, // top, bottom, left, right or none + // A DOMRect from the target element + targetRect: PropTypes.object.isRequired, // Function to be called on menu close onFinished: PropTypes.func, - // If true, insert an invisible screen-sized element behind the // menu that when clicked will close it. hasBackground: PropTypes.bool, - // The component to render as the context menu elementClass: PropTypes.element.isRequired, // on resize callback @@ -56,58 +54,64 @@ export default class InteractiveTooltip extends React.Component { closeTooltip: PropTypes.func, }; + constructor() { + super(); + + this.state = { + contentRect: null, + }; + } + + collectContentRect = (element) => { + // We don't need to clean up when unmounting, so ignore + if (!element) return; + + this.setState({ + contentRect: element.getBoundingClientRect(), + }); + } + render() { + const props = this.props; + const { targetRect } = props; + + // The window X and Y offsets are to adjust position when zoomed in to page + const targetLeft = targetRect.left + window.pageXOffset; + const targetBottom = targetRect.bottom + window.pageYOffset; + const targetTop = targetRect.top + window.pageYOffset; + + // Align the tooltip vertically on whichever side of the target has more + // space available. const position = {}; let chevronFace = null; - const props = this.props; - - if (props.top) { - position.top = props.top; + if (targetBottom < window.innerHeight / 2) { + position.top = targetBottom; + chevronFace = "top"; } else { - position.bottom = props.bottom; + position.bottom = window.innerHeight - targetTop; + chevronFace = "bottom"; } - if (props.left) { - position.left = props.left; - chevronFace = 'left'; - } else { - position.right = props.right; - chevronFace = 'right'; - } + // Center the tooltip horizontally with the target's center. + position.left = targetLeft + targetRect.width / 2; - const chevronOffset = {}; - if (props.chevronFace) { - chevronFace = props.chevronFace; - } - const hasChevron = chevronFace && chevronFace !== "none"; - - if (chevronFace === 'top' || chevronFace === 'bottom') { - chevronOffset.left = props.chevronOffset; - } else { - chevronOffset.top = props.chevronOffset; - } - - const chevron = hasChevron ? -
: - undefined; - const className = 'mx_InteractiveTooltip_wrapper'; + const chevron =
; const menuClasses = classNames({ 'mx_InteractiveTooltip': true, - 'mx_InteractiveTooltip_left': !hasChevron && position.left, - 'mx_InteractiveTooltip_right': !hasChevron && position.right, - 'mx_InteractiveTooltip_top': !hasChevron && position.top, - 'mx_InteractiveTooltip_bottom': !hasChevron && position.bottom, - 'mx_InteractiveTooltip_withChevron_left': chevronFace === 'left', - 'mx_InteractiveTooltip_withChevron_right': chevronFace === 'right', 'mx_InteractiveTooltip_withChevron_top': chevronFace === 'top', 'mx_InteractiveTooltip_withChevron_bottom': chevronFace === 'bottom', }); + const menuStyle = {}; + if (this.state.contentRect) { + menuStyle.left = `-${this.state.contentRect.width / 2}px`; + } + const ElementClass = props.elementClass; - return
-
+ return
+
{ chevron }
From 3bd247ebaa02acf973f992cb6c01b221cd03c90f Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 21 Jun 2019 11:41:19 +0100 Subject: [PATCH 121/188] Tweak interactive tooltip to match design This tweaks the tooltip to match the color, spacing, etc. seen in the designs. Part of https://github.com/vector-im/riot-web/issues/9753 Part of https://github.com/vector-im/riot-web/issues/9716 --- .../views/elements/_InteractiveTooltip.scss | 43 +++++-------------- res/themes/dark/css/_dark.scss | 3 ++ res/themes/light/css/_light.scss | 3 ++ 3 files changed, 16 insertions(+), 33 deletions(-) diff --git a/res/css/views/elements/_InteractiveTooltip.scss b/res/css/views/elements/_InteractiveTooltip.scss index 3ec20be928..a949941dd8 100644 --- a/res/css/views/elements/_InteractiveTooltip.scss +++ b/res/css/views/elements/_InteractiveTooltip.scss @@ -30,17 +30,18 @@ limitations under the License. } .mx_InteractiveTooltip { - border-radius: 4px; - box-shadow: 4px 4px 12px 0 $menu-box-shadow-color; - background-color: $menu-bg-color; - color: $primary-fg-color; + border-radius: 3px; + background-color: $interactive-tooltip-bg-color; + color: $interactive-tooltip-fg-color; position: absolute; - font-size: 14px; + font-size: 10px; + font-weight: 600; + padding: 6px; z-index: 5001; } .mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_top { - top: 8px; + top: 10px; // 8px chevron + 2px spacing } .mx_InteractiveTooltip_chevron_top { @@ -50,24 +51,12 @@ limitations under the License. width: 0; height: 0; border-left: 8px solid transparent; - border-bottom: 8px solid $menu-bg-color; + border-bottom: 8px solid $interactive-tooltip-bg-color; border-right: 8px solid transparent; } -.mx_InteractiveTooltip_chevron_top::after { - content: ''; - width: 0; - height: 0; - border-left: 7px solid transparent; - border-bottom: 7px solid $menu-bg-color; - border-right: 7px solid transparent; - position: absolute; - left: -7px; - top: 1px; -} - .mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_bottom { - bottom: 8px; + bottom: 10px; // 8px chevron + 2px spacing } .mx_InteractiveTooltip_chevron_bottom { @@ -77,18 +66,6 @@ limitations under the License. width: 0; height: 0; border-left: 8px solid transparent; - border-top: 8px solid $menu-bg-color; + border-top: 8px solid $interactive-tooltip-bg-color; border-right: 8px solid transparent; } - -.mx_InteractiveTooltip_chevron_bottom::after { - content: ''; - width: 0; - height: 0; - border-left: 7px solid transparent; - border-top: 7px solid $menu-bg-color; - border-right: 7px solid transparent; - position: absolute; - left: -7px; - bottom: 1px; -} diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index bdccf71540..ed1cc162a0 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -160,6 +160,9 @@ $reaction-row-button-selected-border-color: $accent-color; $tooltip-timeline-bg-color: $tagpanel-bg-color; $tooltip-timeline-fg-color: #ffffff; +$interactive-tooltip-bg-color: $base-color; +$interactive-tooltip-fg-color: #ffffff; + // ***** Mixins! ***** @define-mixin mx_DialogButton { diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 8244485ee3..361f6fa408 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -272,6 +272,9 @@ $reaction-row-button-selected-border-color: $accent-color; $tooltip-timeline-bg-color: $tagpanel-bg-color; $tooltip-timeline-fg-color: #ffffff; +$interactive-tooltip-bg-color: #27303a; +$interactive-tooltip-fg-color: #ffffff; + // ***** Mixins! ***** @define-mixin mx_DialogButton { From 80d73d84308411638681da11342c6c3d88c6f3e6 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 21 Jun 2019 13:48:20 +0100 Subject: [PATCH 122/188] Add optional rounded chevron for tooltip We'd like to have a rounded point on the chevron for an extra level of polish. This implements that look for browsers that support `clip-path`. Part of https://github.com/vector-im/riot-web/issues/9716 --- .../views/elements/_InteractiveTooltip.scss | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/res/css/views/elements/_InteractiveTooltip.scss b/res/css/views/elements/_InteractiveTooltip.scss index a949941dd8..a3f5b6edc2 100644 --- a/res/css/views/elements/_InteractiveTooltip.scss +++ b/res/css/views/elements/_InteractiveTooltip.scss @@ -55,6 +55,21 @@ limitations under the License. border-right: 8px solid transparent; } +// Adapted from https://codyhouse.co/blog/post/css-rounded-triangles-with-clip-path +// by Sebastiano Guerriero (@guerriero_se) +@supports (clip-path: polygon(0% 0%, 100% 100%, 0% 100%)) { + .mx_InteractiveTooltip_chevron_top { + height: 16px; + width: 16px; + background-color: inherit; + border: none; + clip-path: polygon(0% 0%, 100% 100%, 0% 100%); + transform: rotate(135deg); + border-radius: 0 0 0 3px; + top: calc(-8px / 1.414); // sqrt(2) because of rotation + } +} + .mx_InteractiveTooltip.mx_InteractiveTooltip_withChevron_bottom { bottom: 10px; // 8px chevron + 2px spacing } @@ -69,3 +84,18 @@ limitations under the License. border-top: 8px solid $interactive-tooltip-bg-color; border-right: 8px solid transparent; } + +// Adapted from https://codyhouse.co/blog/post/css-rounded-triangles-with-clip-path +// by Sebastiano Guerriero (@guerriero_se) +@supports (clip-path: polygon(0% 0%, 100% 100%, 0% 100%)) { + .mx_InteractiveTooltip_chevron_bottom { + height: 16px; + width: 16px; + background-color: inherit; + border: none; + clip-path: polygon(0% 0%, 100% 100%, 0% 100%); + transform: rotate(-45deg); + border-radius: 0 0 0 3px; + bottom: calc(-8px / 1.414); // sqrt(2) because of rotation + } +} From c443dd7a32665c67c6730f0099859ee11ecf0395 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 21 Jun 2019 16:37:29 +0200 Subject: [PATCH 123/188] clarify why use a BOM marker for the caret nodes --- src/editor/dom.js | 6 +++--- src/editor/render.js | 13 ++++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/editor/dom.js b/src/editor/dom.js index 5c873034b2..1b683c2c5e 100644 --- a/src/editor/dom.js +++ b/src/editor/dom.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ZERO_WIDTH_SPACE, isCaretNode} from "./render"; +import {CARET_NODE_CHAR, isCaretNode} from "./render"; export function walkDOMDepthFirst(rootNode, enterNodeCallback, leaveNodeCallback) { let node = rootNode.firstChild; @@ -57,7 +57,7 @@ export function getCaretOffsetAndText(editor, sel) { function getCaret(focusNode, focusNodeOffset, focusOffset) { let atNodeEnd = focusOffset === focusNode.textContent.length; if (focusNode.nodeType === Node.TEXT_NODE && isCaretNode(focusNode.parentElement)) { - const zwsIdx = focusNode.nodeValue.indexOf(ZERO_WIDTH_SPACE); + const zwsIdx = focusNode.nodeValue.indexOf(CARET_NODE_CHAR); if (zwsIdx !== -1 && zwsIdx < focusOffset) { focusOffset -= 1; } @@ -120,7 +120,7 @@ function getTextNodeValue(node) { // typed in the caret node, so there is now something more in it than the ZWS // so filter out the ZWS, and take the typed text into account if (nodeText.length !== 1) { - return nodeText.replace(ZERO_WIDTH_SPACE, ""); + return nodeText.replace(CARET_NODE_CHAR, ""); } else { // only contains ZWS, which is ignored, so return emtpy string return ""; diff --git a/src/editor/render.js b/src/editor/render.js index 690851c4ea..9d42bbe947 100644 --- a/src/editor/render.js +++ b/src/editor/render.js @@ -33,22 +33,25 @@ function insertAfter(node, nodeToInsert) { } } -// this is a BOM marker actually -export const ZERO_WIDTH_SPACE = "\ufeff"; +// Use a BOM marker for caret nodes. +// On a first test, they seem to be filtered out when copying text out of the editor, +// but this could be platform dependent. +// As a precautionary measure, I chose the character that slate also uses. +export const CARET_NODE_CHAR = "\ufeff"; // a caret node is a node that allows the caret to be placed // where otherwise it wouldn't be possible // (e.g. next to a pill span without adjacent text node) function createCaretNode() { const span = document.createElement("span"); span.className = "caretNode"; - span.appendChild(document.createTextNode(ZERO_WIDTH_SPACE)); + span.appendChild(document.createTextNode(CARET_NODE_CHAR)); return span; } function updateCaretNode(node) { // ensure the caret node contains only a zero-width space - if (node.textContent !== ZERO_WIDTH_SPACE) { - node.textContent = ZERO_WIDTH_SPACE; + if (node.textContent !== CARET_NODE_CHAR) { + node.textContent = CARET_NODE_CHAR; } } From 5fa3f70fb4ceaad13dca05def150f849efa605c7 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 21 Jun 2019 18:32:15 +0200 Subject: [PATCH 124/188] feature flag for displaying edits as well --- src/components/structures/MessagePanel.js | 3 ++- src/components/views/rooms/EventTile.js | 2 +- src/shouldHideEvent.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 8352872a2d..6713d41574 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -576,6 +576,7 @@ module.exports = React.createClass({ const scrollToken = mxEv.status ? undefined : eventId; const readReceipts = this._readReceiptsByEvent[eventId]; + const editingEnabled = SettingsStore.isFeatureEnabled("feature_message_editing"); ret.push(
  • Date: Sun, 23 Jun 2019 22:39:04 +0100 Subject: [PATCH 125/188] When joining from room directory, use auto_join Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/RoomDirectory.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 5342276e63..54d3235200 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -373,7 +373,10 @@ module.exports = React.createClass({ showRoom: function(room, room_alias) { this.props.onFinished(); - const payload = {action: 'view_room'}; + const payload = { + action: 'view_room', + auto_join: true, + }; if (room) { // Don't let the user view a room they won't be able to either // peek or join: fail earlier so they don't have to click back From 8394e162ad7d1b8b5b253b81401bce2e70518a47 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 24 Jun 2019 16:53:31 +0200 Subject: [PATCH 126/188] cache setting where it's easy --- src/components/structures/MessagePanel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 6713d41574..4238e22bd9 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -108,6 +108,7 @@ module.exports = React.createClass({ }, componentWillMount: function() { + this._editingEnabled = SettingsStore.isFeatureEnabled("feature_message_editing"); // the event after which we put a visible unread marker on the last // render cycle; null if readMarkerVisible was false or the RM was // suppressed (eg because it was at the end of the timeline) @@ -576,7 +577,6 @@ module.exports = React.createClass({ const scrollToken = mxEv.status ? undefined : eventId; const readReceipts = this._readReceiptsByEvent[eventId]; - const editingEnabled = SettingsStore.isFeatureEnabled("feature_message_editing"); ret.push(
  • Date: Mon, 24 Jun 2019 16:53:42 +0200 Subject: [PATCH 127/188] adjust comment --- src/shouldHideEvent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shouldHideEvent.js b/src/shouldHideEvent.js index d82647dbdf..7a98c0dba6 100644 --- a/src/shouldHideEvent.js +++ b/src/shouldHideEvent.js @@ -46,7 +46,7 @@ export default function shouldHideEvent(ev) { // Hide redacted events if (ev.isRedacted() && !isEnabled('showRedactions')) return true; - // Hide replacement events since they update the original tile + // Hide replacement events since they update the original tile (if enabled) if (ev.isRelation("m.replace") && SettingsStore.isFeatureEnabled("feature_message_editing")) return true; const eventDiff = memberEventDiff(ev); From 72bfc3b5ea2c5b79a3b454c5cb3418a51a1b97ff Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 24 Jun 2019 17:03:27 +0100 Subject: [PATCH 128/188] Improve API and interactivity of new tooltip This reworks the API the `InteractiveTooltip` component so that it's more natural to use just like other React components. You can now supply the target component as a child and the tooltip content as a prop. In addition, this tweaks the interactivity to keep the tooltip on screen until you move the mouse away from the tooltip and its target. Part of https://github.com/vector-im/riot-web/issues/9753 Part of https://github.com/vector-im/riot-web/issues/9716 --- .../views/elements/InteractiveTooltip.js | 151 ++++++++++++------ 1 file changed, 102 insertions(+), 49 deletions(-) diff --git a/src/components/views/elements/InteractiveTooltip.js b/src/components/views/elements/InteractiveTooltip.js index b3d0b32fa7..42b3561135 100644 --- a/src/components/views/elements/InteractiveTooltip.js +++ b/src/components/views/elements/InteractiveTooltip.js @@ -33,25 +33,30 @@ function getOrCreateContainer() { return container; } +function isInRect(x, y, rect, buffer = 10) { + const { top, right, bottom, left } = rect; + + if (x < (left - buffer) || x > (right + buffer)) { + return false; + } + + if (y < (top - buffer) || y > (bottom + buffer)) { + return false; + } + + return true; +} + /* - * This style of tooltip takes a `target` element's rect and centers the tooltip - * along one edge of the target. + * This style of tooltip takes a "target" element as its child and centers the + * tooltip along one edge of the target. */ export default class InteractiveTooltip extends React.Component { propTypes: { - // A DOMRect from the target element - targetRect: PropTypes.object.isRequired, - // Function to be called on menu close - onFinished: PropTypes.func, - // If true, insert an invisible screen-sized element behind the - // menu that when clicked will close it. - hasBackground: PropTypes.bool, - // The component to render as the context menu - elementClass: PropTypes.element.isRequired, - // on resize callback - windowResize: PropTypes.func, - // method to close menu - closeTooltip: PropTypes.func, + // Content to show in the tooltip + content: PropTypes.node.isRequired, + // Function to call when visibility of the tooltip changes + onVisibilityChange: PropTypes.func, }; constructor() { @@ -59,9 +64,20 @@ export default class InteractiveTooltip extends React.Component { this.state = { contentRect: null, + visible: false, }; } + componentDidUpdate() { + // Whenever this passthrough component updates, also render the tooltip + // in a separate DOM tree. This allows the tooltip content to participate + // the normal React rendering cycle: when this component re-renders, the + // tooltip content re-renders. + // Once we upgrade to React 16, this could be done a bit more naturally + // using the portals feature instead. + this.renderTooltip(); + } + collectContentRect = (element) => { // We don't need to clean up when unmounting, so ignore if (!element) return; @@ -71,9 +87,55 @@ export default class InteractiveTooltip extends React.Component { }); } - render() { - const props = this.props; - const { targetRect } = props; + collectTarget = (element) => { + this.target = element; + } + + onBackgroundClick = (ev) => { + this.hideTooltip(); + } + + onBackgroundMouseMove = (ev) => { + const { clientX: x, clientY: y } = ev; + const { contentRect } = this.state; + const targetRect = this.target.getBoundingClientRect(); + + if (!isInRect(x, y, contentRect) && !isInRect(x, y, targetRect)) { + this.hideTooltip(); + return; + } + } + + onTargetMouseOver = (ev) => { + this.showTooltip(); + } + + showTooltip() { + this.setState({ + visible: true, + }); + if (this.props.onVisibilityChange) { + this.props.onVisibilityChange(true); + } + } + + hideTooltip() { + this.setState({ + visible: false, + }); + if (this.props.onVisibilityChange) { + this.props.onVisibilityChange(false); + } + } + + renderTooltip() { + const { visible } = this.state; + if (!visible) { + ReactDOM.unmountComponentAtNode(getOrCreateContainer()); + return null; + } + + const targetRect = this.target.getBoundingClientRect(); // The window X and Y offsets are to adjust position when zoomed in to page const targetLeft = targetRect.left + window.pageXOffset; @@ -108,38 +170,29 @@ export default class InteractiveTooltip extends React.Component { menuStyle.left = `-${this.state.contentRect.width / 2}px`; } - const ElementClass = props.elementClass; - - return
    -
    - { chevron } - + const tooltip =
    +
    +
    + {chevron} + {this.props.content}
    - { props.hasBackground &&
    }
    ; + + ReactDOM.render(tooltip, getOrCreateContainer()); + } + + render() { + // We use `cloneElement` here to append some props to the child content + // without using a wrapper element which could disrupt layout. + return React.cloneElement(this.props.children, { + ref: this.collectTarget, + onMouseOver: this.onTargetMouseOver, + }); } } - -export function createTooltip(ElementClass, props, hasBackground=true) { - const closeTooltip = function(...args) { - ReactDOM.unmountComponentAtNode(getOrCreateContainer()); - - if (props && props.onFinished) { - props.onFinished.apply(null, args); - } - }; - - // We only reference closeTooltip once per call to createTooltip - const menu = ; - - ReactDOM.render(menu, getOrCreateContainer()); - - return {close: closeTooltip}; -} From 7391796eabb8037e91494ea87fb7d34f6338e04e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 25 Jun 2019 10:18:47 +0100 Subject: [PATCH 129/188] Only autojoin using the search box enter/join btn --- src/components/structures/RoomDirectory.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 54d3235200..a98afdce2a 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -333,7 +333,7 @@ module.exports = React.createClass({ if (alias.indexOf(':') == -1) { alias = alias + ':' + this.state.roomServer; } - this.showRoomAlias(alias); + this.showRoomAlias(alias, true); } else { // This is a 3rd party protocol. Let's see if we can join it const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId); @@ -349,7 +349,7 @@ module.exports = React.createClass({ } MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).done((resp) => { if (resp.length > 0 && resp[0].alias) { - this.showRoomAlias(resp[0].alias); + this.showRoomAlias(resp[0].alias, true); } else { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createTrackedDialog('Room not found', '', ErrorDialog, { @@ -367,15 +367,15 @@ module.exports = React.createClass({ } }, - showRoomAlias: function(alias) { - this.showRoom(null, alias); + showRoomAlias: function(alias, autoJoin=false) { + this.showRoom(null, alias, autoJoin); }, - showRoom: function(room, room_alias) { + showRoom: function(room, room_alias, autoJoin=false) { this.props.onFinished(); const payload = { action: 'view_room', - auto_join: true, + auto_join: autoJoin, }; if (room) { // Don't let the user view a room they won't be able to either From 67130cb45f0bbf2f54fa09f6e58446c96d445100 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 Jun 2019 11:57:55 +0100 Subject: [PATCH 130/188] Condense isInRect --- src/components/views/elements/InteractiveTooltip.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/components/views/elements/InteractiveTooltip.js b/src/components/views/elements/InteractiveTooltip.js index 42b3561135..b56fd62556 100644 --- a/src/components/views/elements/InteractiveTooltip.js +++ b/src/components/views/elements/InteractiveTooltip.js @@ -35,16 +35,8 @@ function getOrCreateContainer() { function isInRect(x, y, rect, buffer = 10) { const { top, right, bottom, left } = rect; - - if (x < (left - buffer) || x > (right + buffer)) { - return false; - } - - if (y < (top - buffer) || y > (bottom + buffer)) { - return false; - } - - return true; + return x >= (left - buffer) && x <= (right + buffer) + && y >= (top - buffer) && y <= (bottom + buffer); } /* From 8926992feb6549935c8f7a0d12a533df5d01962c Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 19 Jun 2019 14:45:01 +0100 Subject: [PATCH 131/188] Add react button to action bar This adds a (temporarily non-functional) react button to the action bar. Part of https://github.com/vector-im/riot-web/issues/9753 --- res/css/views/messages/_MessageActionBar.scss | 4 ++++ res/img/react.svg | 10 ++++++++++ src/components/views/messages/MessageActionBar.js | 13 +++++++++++++ src/i18n/strings/en_EN.json | 1 + 4 files changed, 28 insertions(+) create mode 100644 res/img/react.svg diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index 685c2bb018..7ac0e95e81 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -67,6 +67,10 @@ limitations under the License. background-color: $message-action-bar-fg-color; } +.mx_MessageActionBar_reactButton::after { + mask-image: url('$(res)/img/react.svg'); +} + .mx_MessageActionBar_replyButton::after { mask-image: url('$(res)/img/reply.svg'); } diff --git a/res/img/react.svg b/res/img/react.svg new file mode 100644 index 0000000000..dd23c41c2c --- /dev/null +++ b/res/img/react.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 80f0ba538c..3fb82febce 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -131,6 +131,16 @@ export default class MessageActionBar extends React.PureComponent { return SettingsStore.isFeatureEnabled("feature_message_editing"); } + renderReactButton() { + if (!this.isReactionsEnabled()) { + return null; + } + + return ; + } + renderAgreeDimension() { if (!this.isReactionsEnabled()) { return null; @@ -160,12 +170,14 @@ export default class MessageActionBar extends React.PureComponent { } render() { + let reactButton; let agreeDimensionReactionButtons; let likeDimensionReactionButtons; let replyButton; let editButton; if (isContentActionable(this.props.mxEvent)) { + reactButton = this.renderReactButton(); agreeDimensionReactionButtons = this.renderAgreeDimension(); likeDimensionReactionButtons = this.renderLikeDimension(); replyButton = + {reactButton} {agreeDimensionReactionButtons} {likeDimensionReactionButtons} {replyButton} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3edaaf6241..9564c45172 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -930,6 +930,7 @@ "Error decrypting audio": "Error decrypting audio", "Agree or Disagree": "Agree or Disagree", "Like or Dislike": "Like or Dislike", + "React": "React", "Reply": "Reply", "Edit": "Edit", "Options": "Options", From 91f707341a6d54ad7e1bfea85950bc1ccf5e365d Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 19 Jun 2019 15:08:49 +0100 Subject: [PATCH 132/188] Tweak handler name to match others --- src/components/views/messages/MessageActionBar.js | 4 ++-- src/components/views/rooms/EventTile.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 3fb82febce..aaedf46200 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -57,7 +57,7 @@ export default class MessageActionBar extends React.PureComponent { this.props.onFocusChange(focused); } - onCryptoClicked = () => { + onCryptoClick = () => { const event = this.props.mxEvent; Modal.createTrackedDialogAsync('Encrypted Event Dialog', '', import('../../../async-components/views/dialogs/EncryptedEventDialog'), @@ -89,7 +89,7 @@ export default class MessageActionBar extends React.PureComponent { let e2eInfoCallback = null; if (this.props.mxEvent.isEncrypted()) { - e2eInfoCallback = () => this.onCryptoClicked(); + e2eInfoCallback = () => this.onCryptoClick(); } const menuOptions = { diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index a997e7a3de..988bf7eb3c 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -404,7 +404,7 @@ module.exports = withMatrixClient(React.createClass({ }); }, - onCryptoClicked: function(e) { + onCryptoClick: function(e) { const event = this.props.mxEvent; Modal.createTrackedDialogAsync('Encrypted Event Dialog', '', @@ -440,7 +440,7 @@ module.exports = withMatrixClient(React.createClass({ _renderE2EPadlock: function() { const ev = this.props.mxEvent; - const props = {onClick: this.onCryptoClicked}; + const props = {onClick: this.onCryptoClick}; // event could not be decrypted if (ev.getContent().msgtype === 'm.bad.encrypted') { From fd2723585fb22d08f202652d4971c10a85106480 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 Jun 2019 15:10:41 +0100 Subject: [PATCH 133/188] Add quick reaction buttons in tooltip This adds the set of quick reactions as buttons in a new tooltip accessed via the react action in the message action bar. Part of https://github.com/vector-im/riot-web/issues/9753 --- res/css/_components.scss | 1 + .../messages/_ReactionTooltipButton.scss | 24 +++ .../views/messages/MessageActionBar.js | 9 +- .../views/messages/ReactMessageAction.js | 97 +++++++++++ .../views/messages/ReactionTooltipButton.js | 67 ++++++++ .../views/messages/ReactionsQuickTooltip.js | 151 ++++++++++++++++++ src/i18n/strings/en_EN.json | 7 +- 7 files changed, 353 insertions(+), 3 deletions(-) create mode 100644 res/css/views/messages/_ReactionTooltipButton.scss create mode 100644 src/components/views/messages/ReactMessageAction.js create mode 100644 src/components/views/messages/ReactionTooltipButton.js create mode 100644 src/components/views/messages/ReactionsQuickTooltip.js diff --git a/res/css/_components.scss b/res/css/_components.scss index fa388c4e6a..fac149380e 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -118,6 +118,7 @@ @import "./views/messages/_MessageActionBar.scss"; @import "./views/messages/_MessageTimestamp.scss"; @import "./views/messages/_ReactionDimension.scss"; +@import "./views/messages/_ReactionTooltipButton.scss"; @import "./views/messages/_ReactionsRow.scss"; @import "./views/messages/_ReactionsRowButton.scss"; @import "./views/messages/_ReactionsRowButtonTooltip.scss"; diff --git a/res/css/views/messages/_ReactionTooltipButton.scss b/res/css/views/messages/_ReactionTooltipButton.scss new file mode 100644 index 0000000000..7cb754a8fd --- /dev/null +++ b/res/css/views/messages/_ReactionTooltipButton.scss @@ -0,0 +1,24 @@ +/* +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_ReactionTooltipButton { + font-size: 16px; + user-select: none; +} + +.mx_ReactionTooltipButton_selected { + opacity: 0.4; +} diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index aaedf46200..410c0762b3 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -136,8 +136,13 @@ export default class MessageActionBar extends React.PureComponent { return null; } - return ; } diff --git a/src/components/views/messages/ReactMessageAction.js b/src/components/views/messages/ReactMessageAction.js new file mode 100644 index 0000000000..804a154c9c --- /dev/null +++ b/src/components/views/messages/ReactMessageAction.js @@ -0,0 +1,97 @@ +/* +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 sdk from '../../../index'; + +export default class ReactMessageAction extends React.PureComponent { + static propTypes = { + mxEvent: PropTypes.object.isRequired, + // The Relations model from the JS SDK for reactions to `mxEvent` + reactions: PropTypes.object, + onFocusChange: PropTypes.func, + } + + constructor(props) { + super(props); + + if (props.reactions) { + props.reactions.on("Relations.add", this.onReactionsChange); + props.reactions.on("Relations.remove", this.onReactionsChange); + props.reactions.on("Relations.redaction", this.onReactionsChange); + } + } + + onFocusChange = (focused) => { + if (!this.props.onFocusChange) { + return; + } + this.props.onFocusChange(focused); + } + + componentDidUpdate(prevProps) { + if (prevProps.reactions !== this.props.reactions) { + this.props.reactions.on("Relations.add", this.onReactionsChange); + this.props.reactions.on("Relations.remove", this.onReactionsChange); + this.props.reactions.on("Relations.redaction", this.onReactionsChange); + this.onReactionsChange(); + } + } + + componentWillUnmount() { + if (this.props.reactions) { + this.props.reactions.removeListener( + "Relations.add", + this.onReactionsChange, + ); + this.props.reactions.removeListener( + "Relations.remove", + this.onReactionsChange, + ); + this.props.reactions.removeListener( + "Relations.redaction", + this.onReactionsChange, + ); + } + } + + onReactionsChange = () => { + // Force a re-render of the tooltip because a change in the reactions + // set means the event tile's layout may have changed and possibly + // altered the location where the tooltip should be shown. + this.forceUpdate(); + } + + render() { + const ReactionsQuickTooltip = sdk.getComponent('messages.ReactionsQuickTooltip'); + const InteractiveTooltip = sdk.getComponent('elements.InteractiveTooltip'); + const { mxEvent, reactions } = this.props; + + const content = ; + + return + + ; + } +} diff --git a/src/components/views/messages/ReactionTooltipButton.js b/src/components/views/messages/ReactionTooltipButton.js new file mode 100644 index 0000000000..a3f0256758 --- /dev/null +++ b/src/components/views/messages/ReactionTooltipButton.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'; + +import MatrixClientPeg from '../../../MatrixClientPeg'; + +export default class ReactionTooltipButton extends React.PureComponent { + static propTypes = { + mxEvent: PropTypes.object.isRequired, + // The reaction content / key / emoji + content: PropTypes.string.isRequired, + title: PropTypes.string, + // A possible Matrix event if the current user has voted for this type + myReactionEvent: PropTypes.object, + }; + + onClick = (ev) => { + const { mxEvent, myReactionEvent, content } = this.props; + if (myReactionEvent) { + MatrixClientPeg.get().redactEvent( + mxEvent.getRoomId(), + myReactionEvent.getId(), + ); + } else { + MatrixClientPeg.get().sendEvent(mxEvent.getRoomId(), "m.reaction", { + "m.relates_to": { + "rel_type": "m.annotation", + "event_id": mxEvent.getId(), + "key": content, + }, + }); + } + } + + render() { + const { content, myReactionEvent } = this.props; + + const classes = classNames({ + mx_ReactionTooltipButton: true, + mx_ReactionTooltipButton_selected: !!myReactionEvent, + }); + + return + {content} + ; + } +} diff --git a/src/components/views/messages/ReactionsQuickTooltip.js b/src/components/views/messages/ReactionsQuickTooltip.js new file mode 100644 index 0000000000..74cfe82baf --- /dev/null +++ b/src/components/views/messages/ReactionsQuickTooltip.js @@ -0,0 +1,151 @@ +/* +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 { _t } from '../../../languageHandler'; +import sdk from '../../../index'; +import MatrixClientPeg from '../../../MatrixClientPeg'; + +export default class ReactionsQuickTooltip extends React.PureComponent { + static propTypes = { + mxEvent: PropTypes.object.isRequired, + // The Relations model from the JS SDK for reactions to `mxEvent` + reactions: PropTypes.object, + }; + + constructor(props) { + super(props); + + if (props.reactions) { + props.reactions.on("Relations.add", this.onReactionsChange); + props.reactions.on("Relations.remove", this.onReactionsChange); + props.reactions.on("Relations.redaction", this.onReactionsChange); + } + + this.state = { + myReactions: this.getMyReactions(), + }; + } + + componentDidUpdate(prevProps) { + if (prevProps.reactions !== this.props.reactions) { + this.props.reactions.on("Relations.add", this.onReactionsChange); + this.props.reactions.on("Relations.remove", this.onReactionsChange); + this.props.reactions.on("Relations.redaction", this.onReactionsChange); + this.onReactionsChange(); + } + } + + componentWillUnmount() { + if (this.props.reactions) { + this.props.reactions.removeListener( + "Relations.add", + this.onReactionsChange, + ); + this.props.reactions.removeListener( + "Relations.remove", + this.onReactionsChange, + ); + this.props.reactions.removeListener( + "Relations.redaction", + this.onReactionsChange, + ); + } + } + + onReactionsChange = () => { + this.setState({ + myReactions: this.getMyReactions(), + }); + } + + getMyReactions() { + const reactions = this.props.reactions; + if (!reactions) { + return null; + } + const userId = MatrixClientPeg.get().getUserId(); + const myReactions = reactions.getAnnotationsBySender()[userId]; + if (!myReactions) { + return null; + } + return [...myReactions.values()]; + } + + render() { + const { mxEvent } = this.props; + const { myReactions } = this.state; + const ReactionTooltipButton = sdk.getComponent('messages.ReactionTooltipButton'); + + const items = [ + { + content: "👍", + title: _t("Agree"), + }, + { + content: "👎", + title: _t("Disagree"), + }, + { + content: "😄", + title: _t("Happy"), + }, + { + content: "🎉", + title: _t("Party Popper"), + }, + { + content: "😕", + title: _t("Confused"), + }, + { + content: "❤️", + title: _t("Heart"), + }, + { + content: "🚀", + title: _t("Rocket"), + }, + { + content: "👀", + title: _t("Eyes"), + }, + ]; + + const buttons = items.map(({ content, title }) => { + const myReactionEvent = myReactions && myReactions.find(mxEvent => { + if (mxEvent.isRedacted()) { + return false; + } + return mxEvent.getRelation().key === content; + }); + + return ; + }); + + return
    + {buttons} +
    ; + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9564c45172..02c0658bcb 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -930,7 +930,6 @@ "Error decrypting audio": "Error decrypting audio", "Agree or Disagree": "Agree or Disagree", "Like or Dislike": "Like or Dislike", - "React": "React", "Reply": "Reply", "Edit": "Edit", "Options": "Options", @@ -941,6 +940,12 @@ "Invalid file%(extra)s": "Invalid file%(extra)s", "Error decrypting image": "Error decrypting image", "Error decrypting video": "Error decrypting video", + "Agree": "Agree", + "Disagree": "Disagree", + "Happy": "Happy", + "Party Popper": "Party Popper", + "Confused": "Confused", + "Eyes": "Eyes", "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.", From c6f2bb4ba7419b6788a35ad0faf25932eb888a67 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 Jun 2019 15:40:31 +0100 Subject: [PATCH 134/188] Arrange buttons in a grid --- res/css/_components.scss | 1 + .../views/messages/_ReactionQuickTooltip.scss | 20 +++++++++++++++++++ .../messages/_ReactionTooltipButton.scss | 1 + 3 files changed, 22 insertions(+) create mode 100644 res/css/views/messages/_ReactionQuickTooltip.scss diff --git a/res/css/_components.scss b/res/css/_components.scss index fac149380e..8b86579bf3 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -118,6 +118,7 @@ @import "./views/messages/_MessageActionBar.scss"; @import "./views/messages/_MessageTimestamp.scss"; @import "./views/messages/_ReactionDimension.scss"; +@import "./views/messages/_ReactionQuickTooltip.scss"; @import "./views/messages/_ReactionTooltipButton.scss"; @import "./views/messages/_ReactionsRow.scss"; @import "./views/messages/_ReactionsRowButton.scss"; diff --git a/res/css/views/messages/_ReactionQuickTooltip.scss b/res/css/views/messages/_ReactionQuickTooltip.scss new file mode 100644 index 0000000000..5fb7987b6f --- /dev/null +++ b/res/css/views/messages/_ReactionQuickTooltip.scss @@ -0,0 +1,20 @@ +/* +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_ReactionsQuickTooltip { + display: grid; + grid-template-columns: repeat(4, auto); +} diff --git a/res/css/views/messages/_ReactionTooltipButton.scss b/res/css/views/messages/_ReactionTooltipButton.scss index 7cb754a8fd..36b8abd2a4 100644 --- a/res/css/views/messages/_ReactionTooltipButton.scss +++ b/res/css/views/messages/_ReactionTooltipButton.scss @@ -16,6 +16,7 @@ limitations under the License. .mx_ReactionTooltipButton { font-size: 16px; + padding: 6px; user-select: none; } From 088bbbfb91e02f13ded6f9ccad9beedef26cfa64 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 Jun 2019 15:54:58 +0100 Subject: [PATCH 135/188] Scale up reaction buttons on hover --- res/css/views/messages/_ReactionTooltipButton.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/css/views/messages/_ReactionTooltipButton.scss b/res/css/views/messages/_ReactionTooltipButton.scss index 36b8abd2a4..bf1c25e126 100644 --- a/res/css/views/messages/_ReactionTooltipButton.scss +++ b/res/css/views/messages/_ReactionTooltipButton.scss @@ -18,6 +18,11 @@ limitations under the License. font-size: 16px; padding: 6px; user-select: none; + transition: transform 0.25s; + + &:hover { + transform: scale(1.2); + } } .mx_ReactionTooltipButton_selected { From c1821fabd394747c7f273119b5e3ec1ef3e65a97 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 Jun 2019 16:17:02 +0100 Subject: [PATCH 136/188] Remove toggling reaction dimensions This removes the v1 Reactions UX which only allowed you to choose only one emoji out of each pair. It is replaced by a different UX inside a tooltip and without these constraints. Part of https://github.com/vector-im/riot-web/issues/9753 --- res/css/_components.scss | 1 - .../views/messages/_ReactionDimension.scss | 25 --- .../views/messages/MessageActionBar.js | 34 ---- .../views/messages/ReactionDimension.js | 176 ------------------ src/i18n/strings/en_EN.json | 2 - 5 files changed, 238 deletions(-) delete mode 100644 res/css/views/messages/_ReactionDimension.scss delete mode 100644 src/components/views/messages/ReactionDimension.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 8b86579bf3..4986ca837f 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -117,7 +117,6 @@ @import "./views/messages/_MTextBody.scss"; @import "./views/messages/_MessageActionBar.scss"; @import "./views/messages/_MessageTimestamp.scss"; -@import "./views/messages/_ReactionDimension.scss"; @import "./views/messages/_ReactionQuickTooltip.scss"; @import "./views/messages/_ReactionTooltipButton.scss"; @import "./views/messages/_ReactionsRow.scss"; diff --git a/res/css/views/messages/_ReactionDimension.scss b/res/css/views/messages/_ReactionDimension.scss deleted file mode 100644 index 9a891d05cf..0000000000 --- a/res/css/views/messages/_ReactionDimension.scss +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2019 New Vector Ltd - -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_ReactionDimension { - width: 42px; - display: flex; - justify-content: space-evenly; -} - -.mx_ReactionDimension_disabled { - opacity: 0.4; -} diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index 410c0762b3..e7843c1505 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -146,45 +146,13 @@ export default class MessageActionBar extends React.PureComponent { />; } - renderAgreeDimension() { - if (!this.isReactionsEnabled()) { - return null; - } - - const ReactionDimension = sdk.getComponent('messages.ReactionDimension'); - return ; - } - - renderLikeDimension() { - if (!this.isReactionsEnabled()) { - return null; - } - - const ReactionDimension = sdk.getComponent('messages.ReactionDimension'); - return ; - } - render() { let reactButton; - let agreeDimensionReactionButtons; - let likeDimensionReactionButtons; let replyButton; let editButton; if (isContentActionable(this.props.mxEvent)) { reactButton = this.renderReactButton(); - agreeDimensionReactionButtons = this.renderAgreeDimension(); - likeDimensionReactionButtons = this.renderLikeDimension(); replyButton = {reactButton} - {agreeDimensionReactionButtons} - {likeDimensionReactionButtons} {replyButton} {editButton} { - this.setState(this.getSelection()); - } - - getSelection() { - const myReactions = this.getMyReactions(); - if (!myReactions) { - return { - selectedOption: null, - selectedReactionEvent: null, - }; - } - const { options } = this.props; - let selectedOption = null; - let selectedReactionEvent = null; - for (const option of options) { - const reactionForOption = myReactions.find(mxEvent => { - if (mxEvent.isRedacted()) { - return false; - } - return mxEvent.getRelation().key === option; - }); - if (!reactionForOption) { - continue; - } - if (selectedOption) { - // If there are multiple selected values (only expected to occur via - // non-Riot clients), then act as if none are selected. - return { - selectedOption: null, - selectedReactionEvent: null, - }; - } - selectedOption = option; - selectedReactionEvent = reactionForOption; - } - return { selectedOption, selectedReactionEvent }; - } - - getMyReactions() { - const reactions = this.props.reactions; - if (!reactions) { - return null; - } - const userId = MatrixClientPeg.get().getUserId(); - const myReactions = reactions.getAnnotationsBySender()[userId]; - if (!myReactions) { - return null; - } - return [...myReactions.values()]; - } - - onOptionClick = (ev) => { - const { key } = ev.target.dataset; - this.toggleDimension(key); - } - - toggleDimension(key) { - const { selectedOption, selectedReactionEvent } = this.state; - const newSelectedOption = selectedOption !== key ? key : null; - this.setState({ - selectedOption: newSelectedOption, - }); - if (selectedReactionEvent) { - MatrixClientPeg.get().redactEvent( - this.props.mxEvent.getRoomId(), - selectedReactionEvent.getId(), - ); - } - if (newSelectedOption) { - MatrixClientPeg.get().sendEvent(this.props.mxEvent.getRoomId(), "m.reaction", { - "m.relates_to": { - "rel_type": "m.annotation", - "event_id": this.props.mxEvent.getId(), - "key": newSelectedOption, - }, - }); - } - } - - render() { - const { selectedOption } = this.state; - const { options } = this.props; - - const items = options.map(option => { - const disabled = selectedOption && selectedOption !== option; - const classes = classNames({ - mx_ReactionDimension_disabled: disabled, - }); - return - {option} - ; - }); - - return - {items} - ; - } -} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 02c0658bcb..010ad29da0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -928,8 +928,6 @@ "Today": "Today", "Yesterday": "Yesterday", "Error decrypting audio": "Error decrypting audio", - "Agree or Disagree": "Agree or Disagree", - "Like or Dislike": "Like or Dislike", "Reply": "Reply", "Edit": "Edit", "Options": "Options", From 93384f91f585d898fae9fd5591411128e2d4bac7 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 Jun 2019 18:15:03 +0100 Subject: [PATCH 137/188] Show reaction title and shortcode on hover This shows the title and shortcode for the hovered reaction at the bottom of the tooltip. If nothing is hovered, a blank space is shown for now, but will eventually become a link to a full emoji picker in future work. Part of https://github.com/vector-im/riot-web/issues/9753 --- .../views/messages/_ReactionQuickTooltip.scss | 11 +++- .../views/messages/ReactionTooltipButton.js | 1 + .../views/messages/ReactionsQuickTooltip.js | 60 ++++++++++++++++--- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/res/css/views/messages/_ReactionQuickTooltip.scss b/res/css/views/messages/_ReactionQuickTooltip.scss index 5fb7987b6f..7b1611483b 100644 --- a/res/css/views/messages/_ReactionQuickTooltip.scss +++ b/res/css/views/messages/_ReactionQuickTooltip.scss @@ -14,7 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_ReactionsQuickTooltip { +.mx_ReactionsQuickTooltip_buttons { display: grid; grid-template-columns: repeat(4, auto); } + +.mx_ReactionsQuickTooltip_label { + text-align: center; +} + +.mx_ReactionsQuickTooltip_shortcode { + padding-left: 6px; + opacity: 0.7; +} diff --git a/src/components/views/messages/ReactionTooltipButton.js b/src/components/views/messages/ReactionTooltipButton.js index a3f0256758..e09b9ade69 100644 --- a/src/components/views/messages/ReactionTooltipButton.js +++ b/src/components/views/messages/ReactionTooltipButton.js @@ -57,6 +57,7 @@ export default class ReactionTooltipButton extends React.PureComponent { }); return { + const { key } = ev.target.dataset; + const item = this.items.find(({ content }) => content === key); + this.setState({ + hoveredItem: item, + }); + } - const items = [ + onMouseOut = (ev) => { + this.setState({ + hoveredItem: null, + }); + } + + get items() { + return [ { content: "👍", title: _t("Agree"), @@ -126,8 +138,14 @@ export default class ReactionsQuickTooltip extends React.PureComponent { title: _t("Eyes"), }, ]; + } - const buttons = items.map(({ content, title }) => { + render() { + const { mxEvent } = this.props; + const { myReactions, hoveredItem } = this.state; + const ReactionTooltipButton = sdk.getComponent('messages.ReactionTooltipButton'); + + const buttons = this.items.map(({ content, title }) => { const myReactionEvent = myReactions && myReactions.find(mxEvent => { if (mxEvent.isRedacted()) { return false; @@ -144,8 +162,34 @@ export default class ReactionsQuickTooltip extends React.PureComponent { />; }); - return
    - {buttons} + let label = " "; // non-breaking space to keep layout the same when empty + if (hoveredItem) { + const { content, title } = hoveredItem; + + let shortcodeLabel; + const shortcode = unicodeToShortcode(content); + if (shortcode) { + shortcodeLabel = + {shortcode} + ; + } + + label =
    + + {title} + + {shortcodeLabel} +
    ; + } + + return
    +
    + {buttons} +
    + {label}
    ; } } From b6242dbad2ef370fed9ba7e02562a29ac21abda9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 25 Jun 2019 13:54:48 -0600 Subject: [PATCH 138/188] Fix spelling --- src/components/views/dialogs/RoomUpgradeDialog.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/RoomUpgradeDialog.js b/src/components/views/dialogs/RoomUpgradeDialog.js index ce8b93f693..45c242fea5 100644 --- a/src/components/views/dialogs/RoomUpgradeDialog.js +++ b/src/components/views/dialogs/RoomUpgradeDialog.js @@ -92,7 +92,7 @@ export default React.createClass({

    {_t( "Upgrading this room requires closing down the current " + - "instance of the room and creating a new room it its place. " + + "instance of the room and creating a new room in its place. " + "To give room members the best possible experience, we will:", )}

    diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3edaaf6241..76447831aa 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1204,7 +1204,7 @@ "The room upgrade could not be completed": "The room upgrade could not be completed", "Upgrade this room to version %(version)s": "Upgrade this room to version %(version)s", "Upgrade Room Version": "Upgrade Room Version", - "Upgrading this room requires closing down the current instance of the room and creating a new room it its place. To give room members the best possible experience, we will:": "Upgrading this room requires closing down the current instance of the room and creating a new room it its place. To give room members the best possible experience, we will:", + "Upgrading this room requires closing down the current instance of the room and creating a new room in its place. To give room members the best possible experience, we will:": "Upgrading this room requires closing down the current instance of the room and creating a new room in its place. To give room members the best possible experience, we will:", "Create a new room with the same name, description and avatar": "Create a new room with the same name, description and avatar", "Update any local room aliases to point to the new room": "Update any local room aliases to point to the new room", "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room": "Stop users from speaking in the old version of the room, and post a message advising users to move to the new room", From 0701d89bbeb7f6c6cfee9e583ed7cb16cfc05e06 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 25 Jun 2019 15:40:22 -0600 Subject: [PATCH 139/188] Fix upgrade warning being chopped off The flex box was behaving a bit strange, so we just wrap the content and change `height: 235px` to `max-height: 235px` to get scrollbars. --- .../views/rooms/_RoomUpgradeWarningBar.scss | 17 +++++++---- .../views/rooms/RoomUpgradeWarningBar.js | 30 ++++++++++--------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/res/css/views/rooms/_RoomUpgradeWarningBar.scss b/res/css/views/rooms/_RoomUpgradeWarningBar.scss index fe81d3801a..1c477cedfe 100644 --- a/res/css/views/rooms/_RoomUpgradeWarningBar.scss +++ b/res/css/views/rooms/_RoomUpgradeWarningBar.scss @@ -15,17 +15,22 @@ limitations under the License. */ .mx_RoomUpgradeWarningBar { + max-height: 235px; + background-color: $preview-bar-bg-color; + padding-left: 20px; + padding-right: 20px; + overflow: scroll; +} + +.mx_RoomUpgradeWarningBar_wrapped { + width: 100%; + height: 100%; + display: flex; text-align: center; - height: 235px; - background-color: $event-selected-color; align-items: center; flex-direction: column; justify-content: center; - display: flex; - background-color: $preview-bar-bg-color; -webkit-align-items: center; - padding-left: 20px; - padding-right: 20px; } .mx_RoomUpgradeWarningBar_header { diff --git a/src/components/views/rooms/RoomUpgradeWarningBar.js b/src/components/views/rooms/RoomUpgradeWarningBar.js index c2e2ba89d4..edde0a6865 100644 --- a/src/components/views/rooms/RoomUpgradeWarningBar.js +++ b/src/components/views/rooms/RoomUpgradeWarningBar.js @@ -97,20 +97,22 @@ module.exports = React.createClass({ return (
    -
    - {_t( - "This room is running room version , which this homeserver has " + - "marked as unstable.", - {}, - { - "roomVersion": () => {this.props.room.getVersion()}, - "i": (sub) => {sub}, - }, - )} -
    - {doUpgradeWarnings} -
    - {_t("Only room administrators will see this warning")} +
    +
    + {_t( + "This room is running room version , which this homeserver has " + + "marked as unstable.", + {}, + { + "roomVersion": () => {this.props.room.getVersion()}, + "i": (sub) => {sub}, + }, + )} +
    + {doUpgradeWarnings} +
    + {_t("Only room administrators will see this warning")} +
    ); From 7b00d29ea68f80dd0ca761038d35c54060ada4c9 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 25 Jun 2019 16:08:11 -0600 Subject: [PATCH 140/188] Don't boost trackpad users in breadcrumbs Fixes https://github.com/vector-im/riot-web/issues/10005 --- src/components/structures/IndicatorScrollbar.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/structures/IndicatorScrollbar.js b/src/components/structures/IndicatorScrollbar.js index 03ce258d37..b11e655f0d 100644 --- a/src/components/structures/IndicatorScrollbar.js +++ b/src/components/structures/IndicatorScrollbar.js @@ -38,6 +38,8 @@ export default class IndicatorScrollbar extends React.Component { this.checkOverflow = this.checkOverflow.bind(this); this._scrollElement = null; this._autoHideScrollbar = null; + this._likelyTrackpadUser = null; + this._checkAgainForTrackpad = 0; // ts in milliseconds to recheck this._likelyTrackpadUser this.state = { leftIndicatorOffset: 0, @@ -129,6 +131,16 @@ export default class IndicatorScrollbar extends React.Component { // the harshness of the scroll behaviour. Should be a value between 0 and 1. const yRetention = 1.0; + // Check for trackpad users every so often to avoid boosting their scroll. + // See https://github.com/vector-im/riot-web/issues/10005 + const now = new Date().getTime(); + if (now >= this._checkAgainForTrackpad) { + this._likelyTrackpadUser = Math.abs(e.deltaX) > 0; + this._checkAgainForTrackpad = now + (15 * 60 * 1000); // 15min + } + + const safeToBoost = !this._likelyTrackpadUser; + if (Math.abs(e.deltaX) <= xyThreshold) { // HACK: We increase the amount of scroll to counteract smooth scrolling browsers. // Smooth scrolling browsers (Firefox) use the relative area to determine the scroll @@ -140,7 +152,7 @@ export default class IndicatorScrollbar extends React.Component { const additionalScroll = e.deltaY < 0 ? -50 : 50; // noinspection JSSuspiciousNameCombination - const val = Math.abs(e.deltaY) < 25 ? (e.deltaY + additionalScroll) : e.deltaY; + const val = Math.abs(e.deltaY) < 25 && safeToBoost ? (e.deltaY + additionalScroll) : e.deltaY; this._scrollElement.scrollLeft += val * yRetention; } } From a73436e1a18467c2954dcfccae426f74c0bcf7e4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 25 Jun 2019 20:31:20 -0600 Subject: [PATCH 141/188] Don't use oobData if there is none Fixes the "buttons don't work" problem on https://github.com/vector-im/riot-web/issues/10114 --- src/components/views/rooms/RoomPreviewBar.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js index cbc44d0933..ac52f60347 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.js @@ -238,9 +238,9 @@ module.exports = React.createClass({ params: { email: this.props.invitedEmail, signurl: this.props.signUrl, - room_name: this.props.oobData.room_name, - room_avatar_url: this.props.oobData.avatarUrl, - inviter_name: this.props.oobData.inviterName, + room_name: this.props.oobData ? this.props.oobData.room_name : null, + room_avatar_url: this.props.oobData ? this.props.oobData.avatarUrl : null, + inviter_name: this.props.oobData ? this.props.oobData.inviterName : null, } }; }, From ca6ddf324fb12ac72179bd10432591bc23783685 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 25 Jun 2019 20:56:33 -0600 Subject: [PATCH 142/188] Show a loading state for slow peeks --- res/css/views/rooms/_RoomPreviewBar.scss | 10 ++++++++++ src/components/structures/RoomView.js | 1 + src/components/views/rooms/RoomPreviewBar.js | 16 +++++++++++++++- src/i18n/strings/en_EN.json | 1 + 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_RoomPreviewBar.scss b/res/css/views/rooms/_RoomPreviewBar.scss index ea3b787971..6ac5546f78 100644 --- a/res/css/views/rooms/_RoomPreviewBar.scss +++ b/res/css/views/rooms/_RoomPreviewBar.scss @@ -39,6 +39,16 @@ limitations under the License. margin: 10px 10px 10px 0; flex: 0 0 auto; } + + .mx_RoomPreviewBar_footer { + font-size: 12px; + line-height: 20px; + + .mx_Spinner { + vertical-align: middle; + display: inline-block; + } + } } .mx_RoomPreviewBar_dark { diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index cda3f60fce..f02c4d45c3 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1523,6 +1523,7 @@ module.exports = React.createClass({
    + + {_t("Loading room preview")} +
    + ); + } break; } case MessageCase.Kicked: { @@ -433,7 +445,6 @@ module.exports = React.createClass({ } const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - const Spinner = sdk.getComponent('elements.Spinner'); let subTitleElements; if (subTitle) { @@ -484,6 +495,9 @@ module.exports = React.createClass({ { secondaryButton } { primaryButton }
    +
    + { footer } +
    ); }, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3edaaf6241..8a54a4ba53 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -835,6 +835,7 @@ "Join the conversation with an account": "Join the conversation with an account", "Sign Up": "Sign Up", "Sign In": "Sign In", + "Loading room preview": "Loading room preview", "You were kicked from %(roomName)s by %(memberName)s": "You were kicked from %(roomName)s by %(memberName)s", "Reason: %(reason)s": "Reason: %(reason)s", "Forget this room": "Forget this room", From e5c3e5988b37084d8534adc43dd8883d2c785576 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 25 Jun 2019 20:31:20 -0600 Subject: [PATCH 143/188] Revert "Don't use oobData if there is none" This reverts commit a73436e1a18467c2954dcfccae426f74c0bcf7e4. --- src/components/views/rooms/RoomPreviewBar.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js index ac52f60347..cbc44d0933 100644 --- a/src/components/views/rooms/RoomPreviewBar.js +++ b/src/components/views/rooms/RoomPreviewBar.js @@ -238,9 +238,9 @@ module.exports = React.createClass({ params: { email: this.props.invitedEmail, signurl: this.props.signUrl, - room_name: this.props.oobData ? this.props.oobData.room_name : null, - room_avatar_url: this.props.oobData ? this.props.oobData.avatarUrl : null, - inviter_name: this.props.oobData ? this.props.oobData.inviterName : null, + room_name: this.props.oobData.room_name, + room_avatar_url: this.props.oobData.avatarUrl, + inviter_name: this.props.oobData.inviterName, } }; }, From 5f242f02857c25c2fd9fe47c352781368a4d7df8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 25 Jun 2019 20:57:07 -0600 Subject: [PATCH 144/188] Supply oobData instead of erroring --- src/components/structures/RoomView.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index cda3f60fce..724450d965 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -1526,6 +1526,7 @@ module.exports = React.createClass({ error={this.state.roomLoadError} loading={loading} joining={this.state.joining} + oobData={this.props.oobData} />
    ); From 2d9fbcab70a2f941de95d5aabfc3d57cd3417c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=BCrmann?= Date: Wed, 26 Jun 2019 10:49:46 +0200 Subject: [PATCH 145/188] Fix the scrollbar in the community bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently the scrollbar is always visible because the inner container is 5px bigger in height than the outer container. This is hereby fixed. Signed-off-by: Jonas Schürmann --- res/css/structures/_TagPanel.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/structures/_TagPanel.scss b/res/css/structures/_TagPanel.scss index a818f52125..a01e5dd838 100644 --- a/res/css/structures/_TagPanel.scss +++ b/res/css/structures/_TagPanel.scss @@ -65,7 +65,7 @@ limitations under the License. align-items: center; margin-top: 5px; - height: 100%; + height: calc(100% - 5px); } .mx_TagPanel .mx_TagPanel_tagTileContainer > div { height: 40px; From ef71e6fd4fc2701d1fd462647f67b09262a8b19c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 29 May 2019 18:03:05 +0200 Subject: [PATCH 146/188] very basic & hackish edit history dialog --- res/css/_components.scss | 1 + .../dialogs/_MessageEditHistoryDialog.scss | 38 +++++++++ .../views/dialogs/MessageEditHistoryDialog.js | 80 +++++++++++++++++++ src/i18n/strings/en_EN.json | 1 + 4 files changed, 120 insertions(+) create mode 100644 res/css/views/dialogs/_MessageEditHistoryDialog.scss create mode 100644 src/components/views/dialogs/MessageEditHistoryDialog.js diff --git a/res/css/_components.scss b/res/css/_components.scss index 4986ca837f..d30684993d 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -61,6 +61,7 @@ @import "./views/dialogs/_EncryptedEventDialog.scss"; @import "./views/dialogs/_GroupAddressPicker.scss"; @import "./views/dialogs/_IncomingSasDialog.scss"; +@import "./views/dialogs/_MessageEditHistoryDialog.scss"; @import "./views/dialogs/_RestoreKeyBackupDialog.scss"; @import "./views/dialogs/_RoomSettingsDialog.scss"; @import "./views/dialogs/_RoomUpgradeDialog.scss"; diff --git a/res/css/views/dialogs/_MessageEditHistoryDialog.scss b/res/css/views/dialogs/_MessageEditHistoryDialog.scss new file mode 100644 index 0000000000..ad51f4237f --- /dev/null +++ b/res/css/views/dialogs/_MessageEditHistoryDialog.scss @@ -0,0 +1,38 @@ +/* +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_MessageEditHistoryDialog ul { + list-style-type: none; + + &>li.edit { + display: flex; + + &>strong { + flex: 0 0 100px; + font-weight: bold; + } + + &>p { + + } + + } + + ul, ol { + list-style-type: circle; + } +} + diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.js b/src/components/views/dialogs/MessageEditHistoryDialog.js new file mode 100644 index 0000000000..958c7457e0 --- /dev/null +++ b/src/components/views/dialogs/MessageEditHistoryDialog.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 { _t } from '../../../languageHandler'; +import sdk from "../../../index"; +import * as HtmlUtils from '../../../HtmlUtils'; +import {wantsDateSeparator, formatTime} from '../../../DateUtils'; + +export default class MessageEditHistoryDialog extends React.Component { + static propTypes = { + mxEvent: PropTypes.object.isRequired, + }; + + componentWillMount() { + this.setState({edits: [this.props.mxEvent], isLoading: true}); + } + + async componentDidMount() { + const roomId = this.props.mxEvent.getRoomId(); + const eventId = this.props.mxEvent.getId(); + let edits = await MatrixClientPeg.get(). + relations(roomId, eventId, "m.replace", "m.room.message"); + edits = edits.slice().reverse(); + edits.unshift(this.props.mxEvent); + this.setState({edits, isLoading: false}); + } + + _renderEdit(event) { + const timestamp = formatTime(new Date(event.getTs()), true); + const content = event.event.content["m.new_content"] || event.event.content; + return
  • {timestamp}

    {HtmlUtils.bodyToHtml(content)}

  • ; + } + + _renderEdits() { + const DateSeparator = sdk.getComponent('messages.DateSeparator'); + const nodes = []; + let lastEvent; + this.state.edits.forEach(e => { + if (!lastEvent || wantsDateSeparator(lastEvent.getDate(), e.getDate())) { + nodes.push(
  • ); + } + nodes.push(this._renderEdit(e)); + lastEvent = e; + }); + return nodes; + } + + render() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + let spinner; + const edits = this._renderEdits(); + if (this.state.isLoading) { + const Spinner = sdk.getComponent("elements.Spinner"); + spinner = ; + } + return ( + +
      {edits}
    + {spinner} +
    + ); + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 010ad29da0..01d84324ce 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1199,6 +1199,7 @@ "Manually export keys": "Manually export keys", "You'll lose access to your encrypted messages": "You'll lose access to your encrypted messages", "Are you sure you want to sign out?": "Are you sure you want to sign out?", + "Message edits": "Message edits", "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.", "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.": "To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.", "Report bugs & give feedback": "Report bugs & give feedback", From 8b5f07e63d20cc72f780eadd08eec0ee954253fe Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 29 May 2019 18:23:18 +0200 Subject: [PATCH 147/188] open edit dialog on clicking (edited) --- src/components/views/messages/TextualBody.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index d76956d193..d95f0d88df 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -454,6 +454,11 @@ module.exports = React.createClass({ this.setState({editedMarkerHovered: false}); }, + _openHistoryDialog: async function() { + const MessageEditHistoryDialog = sdk.getComponent("views.dialogs.MessageEditHistoryDialog"); + Modal.createDialog(MessageEditHistoryDialog, {mxEvent: this.props.mxEvent}); + }, + _renderEditedMarker: function() { let editedTooltip; if (this.state.editedMarkerHovered) { @@ -468,6 +473,7 @@ module.exports = React.createClass({ return (
    {editedTooltip}{`(${_t("edited")})`}
    From e54881aa2486f688a91461277648f9ca7b8bd095 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 31 May 2019 16:52:27 +0200 Subject: [PATCH 148/188] WIP --- src/components/views/messages/TextualBody.js | 103 +------------------ src/utils/pillify.js | 103 +++++++++++++++++++ 2 files changed, 105 insertions(+), 101 deletions(-) create mode 100644 src/utils/pillify.js diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index d95f0d88df..db5d178c96 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -30,12 +30,11 @@ import Modal from '../../../Modal'; import SdkConfig from '../../../SdkConfig'; import dis from '../../../dispatcher'; import { _t } from '../../../languageHandler'; -import MatrixClientPeg from '../../../MatrixClientPeg'; import * as ContextualMenu from '../../structures/ContextualMenu'; import SettingsStore from "../../../settings/SettingsStore"; -import PushProcessor from 'matrix-js-sdk/lib/pushprocessor'; import ReplyThread from "../elements/ReplyThread"; import {host as matrixtoHost} from '../../../matrix-to'; +import {pillifyLinks} from '../../../utils/pillify'; module.exports = React.createClass({ displayName: 'TextualBody', @@ -99,7 +98,7 @@ module.exports = React.createClass({ // pillifyLinks BEFORE linkifyElement because plain room/user URLs in the composer // are still sent as plaintext URLs. If these are ever pillified in the composer, // we should be pillify them here by doing the linkifying BEFORE the pillifying. - this.pillifyLinks(this.refs.content.children); + pillifyLinks(this.refs.content.children, this.props.mxEvent); HtmlUtils.linkifyElement(this.refs.content); this.calculateUrlPreview(); @@ -184,104 +183,6 @@ module.exports = React.createClass({ } }, - pillifyLinks: function(nodes) { - const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar"); - let node = nodes[0]; - while (node) { - let pillified = false; - - if (node.tagName === "A" && node.getAttribute("href")) { - const href = node.getAttribute("href"); - - // If the link is a (localised) matrix.to link, replace it with a pill - const Pill = sdk.getComponent('elements.Pill'); - if (Pill.isMessagePillUrl(href)) { - const pillContainer = document.createElement('span'); - - const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); - const pill = ; - - ReactDOM.render(pill, pillContainer); - node.parentNode.replaceChild(pillContainer, node); - // Pills within pills aren't going to go well, so move on - pillified = true; - - // update the current node with one that's now taken its place - node = pillContainer; - } - } else if ( - node.nodeType === Node.TEXT_NODE && - // as applying pills happens outside of react, make sure we're not doubly - // applying @room pills here, as a rerender with the same content won't touch the DOM - // to clear the pills from the last run of pillifyLinks - !node.parentElement.classList.contains("mx_AtRoomPill") - ) { - const Pill = sdk.getComponent('elements.Pill'); - - let currentTextNode = node; - const roomNotifTextNodes = []; - - // Take a textNode and break it up to make all the instances of @room their - // own textNode, adding those nodes to roomNotifTextNodes - while (currentTextNode !== null) { - const roomNotifPos = Pill.roomNotifPos(currentTextNode.textContent); - let nextTextNode = null; - if (roomNotifPos > -1) { - let roomTextNode = currentTextNode; - - if (roomNotifPos > 0) roomTextNode = roomTextNode.splitText(roomNotifPos); - if (roomTextNode.textContent.length > Pill.roomNotifLen()) { - nextTextNode = roomTextNode.splitText(Pill.roomNotifLen()); - } - roomNotifTextNodes.push(roomTextNode); - } - currentTextNode = nextTextNode; - } - - if (roomNotifTextNodes.length > 0) { - const pushProcessor = new PushProcessor(MatrixClientPeg.get()); - const atRoomRule = pushProcessor.getPushRuleById(".m.rule.roomnotif"); - if (atRoomRule && pushProcessor.ruleMatchesEvent(atRoomRule, this.props.mxEvent)) { - // Now replace all those nodes with Pills - for (const roomNotifTextNode of roomNotifTextNodes) { - // Set the next node to be processed to the one after the node - // we're adding now, since we've just inserted nodes into the structure - // we're iterating over. - // Note we've checked roomNotifTextNodes.length > 0 so we'll do this at least once - node = roomNotifTextNode.nextSibling; - - const pillContainer = document.createElement('span'); - const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); - const pill = ; - - ReactDOM.render(pill, pillContainer); - roomNotifTextNode.parentNode.replaceChild(pillContainer, roomNotifTextNode); - } - // Nothing else to do for a text node (and we don't need to advance - // the loop pointer because we did it above) - continue; - } - } - } - - if (node.childNodes && node.childNodes.length && !pillified) { - this.pillifyLinks(node.childNodes); - } - - node = node.nextSibling; - } - }, - findLinks: function(nodes) { let links = []; diff --git a/src/utils/pillify.js b/src/utils/pillify.js new file mode 100644 index 0000000000..7744b6c15d --- /dev/null +++ b/src/utils/pillify.js @@ -0,0 +1,103 @@ +import ReactDOM from 'react-dom'; +import MatrixClientPeg from '../MatrixClientPeg'; +import SettingsStore from "../settings/SettingsStore"; +import PushProcessor from 'matrix-js-sdk/lib/pushprocessor'; +import sdk from '../index'; + + +export function pillifyLinks(nodes, mxEvent) { + const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId()); + const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar"); + let node = nodes[0]; + while (node) { + let pillified = false; + + if (node.tagName === "A" && node.getAttribute("href")) { + const href = node.getAttribute("href"); + + // If the link is a (localised) matrix.to link, replace it with a pill + const Pill = sdk.getComponent('elements.Pill'); + if (Pill.isMessagePillUrl(href)) { + const pillContainer = document.createElement('span'); + + const pill = ; + + ReactDOM.render(pill, pillContainer); + node.parentNode.replaceChild(pillContainer, node); + // Pills within pills aren't going to go well, so move on + pillified = true; + + // update the current node with one that's now taken its place + node = pillContainer; + } + } else if ( + node.nodeType === Node.TEXT_NODE && + // as applying pills happens outside of react, make sure we're not doubly + // applying @room pills here, as a rerender with the same content won't touch the DOM + // to clear the pills from the last run of pillifyLinks + !node.parentElement.classList.contains("mx_AtRoomPill") + ) { + const Pill = sdk.getComponent('elements.Pill'); + + let currentTextNode = node; + const roomNotifTextNodes = []; + + // Take a textNode and break it up to make all the instances of @room their + // own textNode, adding those nodes to roomNotifTextNodes + while (currentTextNode !== null) { + const roomNotifPos = Pill.roomNotifPos(currentTextNode.textContent); + let nextTextNode = null; + if (roomNotifPos > -1) { + let roomTextNode = currentTextNode; + + if (roomNotifPos > 0) roomTextNode = roomTextNode.splitText(roomNotifPos); + if (roomTextNode.textContent.length > Pill.roomNotifLen()) { + nextTextNode = roomTextNode.splitText(Pill.roomNotifLen()); + } + roomNotifTextNodes.push(roomTextNode); + } + currentTextNode = nextTextNode; + } + + if (roomNotifTextNodes.length > 0) { + const pushProcessor = new PushProcessor(MatrixClientPeg.get()); + const atRoomRule = pushProcessor.getPushRuleById(".m.rule.roomnotif"); + if (atRoomRule && pushProcessor.ruleMatchesEvent(atRoomRule, mxEvent)) { + // Now replace all those nodes with Pills + for (const roomNotifTextNode of roomNotifTextNodes) { + // Set the next node to be processed to the one after the node + // we're adding now, since we've just inserted nodes into the structure + // we're iterating over. + // Note we've checked roomNotifTextNodes.length > 0 so we'll do this at least once + node = roomNotifTextNode.nextSibling; + + const pillContainer = document.createElement('span'); + const pill = ; + + ReactDOM.render(pill, pillContainer); + roomNotifTextNode.parentNode.replaceChild(pillContainer, roomNotifTextNode); + } + // Nothing else to do for a text node (and we don't need to advance + // the loop pointer because we did it above) + continue; + } + } + } + + if (node.childNodes && node.childNodes.length && !pillified) { + pillifyLinks(node.childNodes); + } + + node = node.nextSibling; + } +} From 19b4699bc2259f185031af1ddbdcbeacebace0f9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 3 Jun 2019 14:31:09 +0200 Subject: [PATCH 149/188] WIP for showing pills in edit history --- .../views/elements/EditHistoryMessage.js | 51 +++++++++++++++++++ src/utils/pillify.js | 2 +- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/components/views/elements/EditHistoryMessage.js diff --git a/src/components/views/elements/EditHistoryMessage.js b/src/components/views/elements/EditHistoryMessage.js new file mode 100644 index 0000000000..2b8ecb87aa --- /dev/null +++ b/src/components/views/elements/EditHistoryMessage.js @@ -0,0 +1,51 @@ +/* +Copyright 2019 New Vector 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. +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 * as HtmlUtils from '../../../HtmlUtils'; +import {formatTime} from '../../../DateUtils'; +import {MatrixEvent} from 'matrix-js-sdk'; +import {pillifyLinks} from '../../../utils/pillify'; + +export default class EditHistoryMessage extends React.Component { + static propTypes = { + // the message event being edited + mxEvent: PropTypes.instanceOf(MatrixEvent).isRequired, + }; + + constructor(props, context) { + super(props, context); + } + + componentDidMount() { + pillifyLinks(this.refs.content.children, this.props.mxEvent); + } + + componentDidUpdate() { + pillifyLinks(this.refs.content.children, this.props.mxEvent); + } + + render() { + const event = this.props.mxEvent; + const timestamp = formatTime(new Date(event.getTs()), true); + const content = event.event.content["m.new_content"] || event.event.content; + return
  • + {timestamp} +

    {HtmlUtils.bodyToHtml(content)}

    +
  • ; + } +} diff --git a/src/utils/pillify.js b/src/utils/pillify.js index 7744b6c15d..74eac560d3 100644 --- a/src/utils/pillify.js +++ b/src/utils/pillify.js @@ -95,7 +95,7 @@ export function pillifyLinks(nodes, mxEvent) { } if (node.childNodes && node.childNodes.length && !pillified) { - pillifyLinks(node.childNodes); + pillifyLinks(node.childNodes, mxEvent); } node = node.nextSibling; From c9aa7efe54be3bb1bcfc22236e8b85d26bab0d81 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 24 Jun 2019 17:45:29 +0200 Subject: [PATCH 150/188] don't require EventTile for default timestamp style --- res/css/views/messages/_MessageTimestamp.scss | 2 ++ res/css/views/rooms/_EventTile.scss | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/messages/_MessageTimestamp.scss b/res/css/views/messages/_MessageTimestamp.scss index e21189c59e..e5c228aa68 100644 --- a/res/css/views/messages/_MessageTimestamp.scss +++ b/res/css/views/messages/_MessageTimestamp.scss @@ -15,4 +15,6 @@ limitations under the License. */ .mx_MessageTimestamp { + color: $event-timestamp-color; + font-size: 10px; } diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 62632eab27..1bd62e393e 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -93,8 +93,6 @@ limitations under the License. display: block; visibility: hidden; white-space: nowrap; - color: $event-timestamp-color; - font-size: 10px; left: 0px; width: 46px; /* 8 + 30 (avatar) + 8 */ text-align: center; From e7fc84d5dab3320a68e50446d913f577ad116b58 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 24 Jun 2019 17:45:57 +0200 Subject: [PATCH 151/188] render history items in own component including: - respect 12/24 hour setting - pillify --- .../views/dialogs/MessageEditHistoryDialog.js | 19 +++++++++---------- .../views/elements/EditHistoryMessage.js | 6 +++--- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.js b/src/components/views/dialogs/MessageEditHistoryDialog.js index 958c7457e0..d6198abea4 100644 --- a/src/components/views/dialogs/MessageEditHistoryDialog.js +++ b/src/components/views/dialogs/MessageEditHistoryDialog.js @@ -19,8 +19,8 @@ import PropTypes from 'prop-types'; import MatrixClientPeg from "../../../MatrixClientPeg"; import { _t } from '../../../languageHandler'; import sdk from "../../../index"; -import * as HtmlUtils from '../../../HtmlUtils'; -import {wantsDateSeparator, formatTime} from '../../../DateUtils'; +import {wantsDateSeparator} from '../../../DateUtils'; +import SettingsStore from '../../../settings/SettingsStore'; export default class MessageEditHistoryDialog extends React.Component { static propTypes = { @@ -28,7 +28,11 @@ export default class MessageEditHistoryDialog extends React.Component { }; componentWillMount() { - this.setState({edits: [this.props.mxEvent], isLoading: true}); + this.setState({ + edits: [this.props.mxEvent], + isLoading: true, + isTwelveHour: SettingsStore.getValue("showTwelveHourTimestamps"), + }); } async componentDidMount() { @@ -41,13 +45,8 @@ export default class MessageEditHistoryDialog extends React.Component { this.setState({edits, isLoading: false}); } - _renderEdit(event) { - const timestamp = formatTime(new Date(event.getTs()), true); - const content = event.event.content["m.new_content"] || event.event.content; - return
  • {timestamp}

    {HtmlUtils.bodyToHtml(content)}

  • ; - } - _renderEdits() { + const EditHistoryMessage = sdk.getComponent('elements.EditHistoryMessage'); const DateSeparator = sdk.getComponent('messages.DateSeparator'); const nodes = []; let lastEvent; @@ -55,7 +54,7 @@ export default class MessageEditHistoryDialog extends React.Component { if (!lastEvent || wantsDateSeparator(lastEvent.getDate(), e.getDate())) { nodes.push(
  • ); } - nodes.push(this._renderEdit(e)); + nodes.push(); lastEvent = e; }); return nodes; diff --git a/src/components/views/elements/EditHistoryMessage.js b/src/components/views/elements/EditHistoryMessage.js index 2b8ecb87aa..1f18072fd8 100644 --- a/src/components/views/elements/EditHistoryMessage.js +++ b/src/components/views/elements/EditHistoryMessage.js @@ -41,10 +41,10 @@ export default class EditHistoryMessage extends React.Component { render() { const event = this.props.mxEvent; - const timestamp = formatTime(new Date(event.getTs()), true); + const timestamp = formatTime(new Date(event.getTs()), this.props.isTwelveHour); const content = event.event.content["m.new_content"] || event.event.content; - return
  • - {timestamp} + return
  • + {timestamp}

    {HtmlUtils.bodyToHtml(content)}

  • ; } From beb003b2d65f1b8e3a11953a0e69e9c5a27a6a95 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 24 Jun 2019 17:46:30 +0200 Subject: [PATCH 152/188] some preliminary styling --- res/css/views/dialogs/_MessageEditHistoryDialog.scss | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/res/css/views/dialogs/_MessageEditHistoryDialog.scss b/res/css/views/dialogs/_MessageEditHistoryDialog.scss index ad51f4237f..b917fe87fd 100644 --- a/res/css/views/dialogs/_MessageEditHistoryDialog.scss +++ b/res/css/views/dialogs/_MessageEditHistoryDialog.scss @@ -16,17 +16,18 @@ limitations under the License. .mx_MessageEditHistoryDialog ul { list-style-type: none; + font-size: 14px; &>li.edit { + margin: 10px 0; display: flex; - &>strong { - flex: 0 0 100px; - font-weight: bold; + &>.mx_MessageTimestamp { + flex: 0 0 50px; } &>p { - + margin: 0; } } From 0fe28cba4363125b52ec6351743b5f7248a2e9e6 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 25 Jun 2019 10:18:27 +0200 Subject: [PATCH 153/188] support emotes in edit history --- .../views/elements/EditHistoryMessage.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/components/views/elements/EditHistoryMessage.js b/src/components/views/elements/EditHistoryMessage.js index 1f18072fd8..749f478ba4 100644 --- a/src/components/views/elements/EditHistoryMessage.js +++ b/src/components/views/elements/EditHistoryMessage.js @@ -40,12 +40,23 @@ export default class EditHistoryMessage extends React.Component { } render() { - const event = this.props.mxEvent; - const timestamp = formatTime(new Date(event.getTs()), this.props.isTwelveHour); - const content = event.event.content["m.new_content"] || event.event.content; + const {mxEvent} = this.props; + const content = mxEvent.event.content["m.new_content"] || mxEvent.event.content; + const contentElements = HtmlUtils.bodyToHtml(content); + let contentContainer; + if (mxEvent.getContent().msgtype === "m.emote") { + const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); + contentContainer = (

    *  + { name } +  {contentElements} +

    ); + } else { + contentContainer = (

    {contentElements}

    ); + } + const timestamp = formatTime(new Date(mxEvent.getTs()), this.props.isTwelveHour); return
  • {timestamp} -

    {HtmlUtils.bodyToHtml(content)}

    + { contentContainer }
  • ; } } From 8c9a6ddf96e66b6239a9e672cc8c6acfd1eb8335 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 25 Jun 2019 14:50:56 +0200 Subject: [PATCH 154/188] support edits pagination in a ScrollPanel --- .../dialogs/_MessageEditHistoryDialog.scss | 11 ++++ .../views/dialogs/MessageEditHistoryDialog.js | 60 ++++++++++++++----- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/res/css/views/dialogs/_MessageEditHistoryDialog.scss b/res/css/views/dialogs/_MessageEditHistoryDialog.scss index b917fe87fd..bc4c2680ed 100644 --- a/res/css/views/dialogs/_MessageEditHistoryDialog.scss +++ b/res/css/views/dialogs/_MessageEditHistoryDialog.scss @@ -15,6 +15,17 @@ limitations under the License. */ .mx_MessageEditHistoryDialog ul { +.mx_MessageEditHistoryDialog { + display: flex; + flex-direction: column; + max-height: 60vh; +} + +.mx_MessageEditHistoryDialog_scrollPanel { + flex: 1 1 auto; +} + +.mx_MessageEditHistoryDialog_edits { list-style-type: none; font-size: 14px; diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.js b/src/components/views/dialogs/MessageEditHistoryDialog.js index d6198abea4..37f49026ac 100644 --- a/src/components/views/dialogs/MessageEditHistoryDialog.js +++ b/src/components/views/dialogs/MessageEditHistoryDialog.js @@ -28,21 +28,41 @@ export default class MessageEditHistoryDialog extends React.Component { }; componentWillMount() { + this.loadMoreEdits = this.loadMoreEdits.bind(this); this.setState({ - edits: [this.props.mxEvent], + events: [], + nextBatch: null, isLoading: true, isTwelveHour: SettingsStore.getValue("showTwelveHourTimestamps"), }); } - async componentDidMount() { + async loadMoreEdits(backwards) { + if (backwards || (!this.state.nextBatch && !this.state.isLoading)) { + // bail out on backwards as we only paginate in one direction + return false; + } + const opts = {token: this.state.nextBatch}; const roomId = this.props.mxEvent.getRoomId(); const eventId = this.props.mxEvent.getId(); - let edits = await MatrixClientPeg.get(). - relations(roomId, eventId, "m.replace", "m.room.message"); - edits = edits.slice().reverse(); - edits.unshift(this.props.mxEvent); - this.setState({edits, isLoading: false}); + const result = await MatrixClientPeg.get().relations( + roomId, eventId, "m.replace", "m.room.message", opts); + //console.log(`loadMoreEdits: got ${result.}`) + let resolve; + const promise = new Promise(r => resolve = r); + this.setState({ + events: this.state.events.concat(result.events), + nextBatch: result.nextBatch, + isLoading: false, + }, () => { + const hasMoreResults = !!this.state.nextBatch; + resolve(hasMoreResults); + }); + return promise; + } + + componentDidMount() { + this.loadMoreEdits(); } _renderEdits() { @@ -50,7 +70,7 @@ export default class MessageEditHistoryDialog extends React.Component { const DateSeparator = sdk.getComponent('messages.DateSeparator'); const nodes = []; let lastEvent; - this.state.edits.forEach(e => { + this.state.events.forEach(e => { if (!lastEvent || wantsDateSeparator(lastEvent.getDate(), e.getDate())) { nodes.push(
  • ); } @@ -61,18 +81,28 @@ export default class MessageEditHistoryDialog extends React.Component { } render() { - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - let spinner; - const edits = this._renderEdits(); - if (this.state.isLoading) { + let content; + if (this.state.error) { + content = this.state.error; + } else if (this.state.isLoading) { const Spinner = sdk.getComponent("elements.Spinner"); - spinner = ; + content = ; + } else { + const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); + content = ( +
      {this._renderEdits()}
    +
    ); } + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return ( -
      {edits}
    - {spinner} + {content}
    ); } From ee03a0f31dd5c2f5f390e23cbe6a54a646dc139d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 11:56:58 +0200 Subject: [PATCH 155/188] recycle EventTile css to make history items look mostly similar --- .../dialogs/_MessageEditHistoryDialog.scss | 20 ++++--------------- .../views/elements/EditHistoryMessage.js | 14 +++++++------ 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/res/css/views/dialogs/_MessageEditHistoryDialog.scss b/res/css/views/dialogs/_MessageEditHistoryDialog.scss index bc4c2680ed..2da72a042e 100644 --- a/res/css/views/dialogs/_MessageEditHistoryDialog.scss +++ b/res/css/views/dialogs/_MessageEditHistoryDialog.scss @@ -28,23 +28,11 @@ limitations under the License. .mx_MessageEditHistoryDialog_edits { list-style-type: none; font-size: 14px; + padding: 0; + color: $primary-fg-color; - &>li.edit { - margin: 10px 0; - display: flex; - - &>.mx_MessageTimestamp { - flex: 0 0 50px; - } - - &>p { - margin: 0; - } - - } - - ul, ol { - list-style-type: circle; + .mx_EventTile_line, .mx_EventTile_content { + margin-right: 0px; } } diff --git a/src/components/views/elements/EditHistoryMessage.js b/src/components/views/elements/EditHistoryMessage.js index 749f478ba4..a6cbfbdfd4 100644 --- a/src/components/views/elements/EditHistoryMessage.js +++ b/src/components/views/elements/EditHistoryMessage.js @@ -46,17 +46,19 @@ export default class EditHistoryMessage extends React.Component { let contentContainer; if (mxEvent.getContent().msgtype === "m.emote") { const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); - contentContainer = (

    *  + contentContainer = (

    { name }  {contentElements} -

    ); +
    ); } else { - contentContainer = (

    {contentElements}

    ); + contentContainer = (
    {contentElements}
    ); } const timestamp = formatTime(new Date(mxEvent.getTs()), this.props.isTwelveHour); - return
  • - {timestamp} - { contentContainer } + return
  • +
    + {timestamp} + { contentContainer } +
  • ; } } From fffdfde8baa91e5bb6368427dd25a73370b3cd12 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 11:57:29 +0200 Subject: [PATCH 156/188] center dialog title --- res/css/views/dialogs/_MessageEditHistoryDialog.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/res/css/views/dialogs/_MessageEditHistoryDialog.scss b/res/css/views/dialogs/_MessageEditHistoryDialog.scss index 2da72a042e..b80742bd24 100644 --- a/res/css/views/dialogs/_MessageEditHistoryDialog.scss +++ b/res/css/views/dialogs/_MessageEditHistoryDialog.scss @@ -14,7 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_MessageEditHistoryDialog ul { +.mx_MessageEditHistoryDialog .mx_Dialog_header > .mx_Dialog_title { + text-align: center; +} + .mx_MessageEditHistoryDialog { display: flex; flex-direction: column; From fe3be39fe772a1d5b6f80b633d23a5739a826e17 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 11:57:49 +0200 Subject: [PATCH 157/188] don't hide timestamps --- src/components/views/dialogs/MessageEditHistoryDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.js b/src/components/views/dialogs/MessageEditHistoryDialog.js index 37f49026ac..1f5408e94c 100644 --- a/src/components/views/dialogs/MessageEditHistoryDialog.js +++ b/src/components/views/dialogs/MessageEditHistoryDialog.js @@ -95,7 +95,7 @@ export default class MessageEditHistoryDialog extends React.Component { stickyBottom={false} startAtBottom={false} > -
      {this._renderEdits()}
    +
      {this._renderEdits()}
    ); } const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); From ddae4de7bd11c541c25190ce62010ded43f7ce9d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 11:58:03 +0200 Subject: [PATCH 158/188] cleanup: RoomDirectory doesn't use gemini anymore --- res/css/structures/_RoomDirectory.scss | 7 ------- 1 file changed, 7 deletions(-) diff --git a/res/css/structures/_RoomDirectory.scss b/res/css/structures/_RoomDirectory.scss index bcfe3aefd6..1df0a61a2b 100644 --- a/res/css/structures/_RoomDirectory.scss +++ b/res/css/structures/_RoomDirectory.scss @@ -35,13 +35,6 @@ limitations under the License. flex: 1; } -.mx_RoomDirectory .gm-scroll-view { - // little hack because gemini doesn't seem to detect - // the scrollbar width well in this instance - // when using css scrollbars - scrollbar-width: thin; -} - .mx_RoomDirectory_createRoom { background-color: $button-bg-color; border-radius: 4px; From 498db2597dd87d52784f6d35ccf4a3f1934fcc1b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 12:15:13 +0200 Subject: [PATCH 159/188] show hand on hovering (edited) marker --- res/css/views/rooms/_EventTile.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 1bd62e393e..1f75373be8 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -401,6 +401,7 @@ limitations under the License. color: $roomtopic-color; display: inline-block; margin-left: 9px; + cursor: pointer; } /* Various markdown overrides */ From d20b765e2742ba345f83cd6cd99b8bc059b4b413 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 14:34:26 +0200 Subject: [PATCH 160/188] rename $accent-color-50pct to $accent-color-darker we'll use $accent-color-50pct for 50% transparent accent color --- res/css/views/auth/_InteractiveAuthEntryComponents.scss | 2 +- res/themes/light/css/_light.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/views/auth/_InteractiveAuthEntryComponents.scss b/res/css/views/auth/_InteractiveAuthEntryComponents.scss index 972acdc2ab..85007aeecb 100644 --- a/res/css/views/auth/_InteractiveAuthEntryComponents.scss +++ b/res/css/views/auth/_InteractiveAuthEntryComponents.scss @@ -49,7 +49,7 @@ limitations under the License. } .mx_InteractiveAuthEntryComponents_termsSubmit:disabled { - background-color: $accent-color-50pct; + background-color: $accent-color-darker; cursor: default; } diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 361f6fa408..3ef2ddc97f 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -28,7 +28,7 @@ $focus-bg-color: #dddddd; // button UI (white-on-green in light skin) $accent-fg-color: #ffffff; -$accent-color-50pct: #92caad; +$accent-color-darker: #92caad; $accent-color-alt: #238CF5; $selection-fg-color: $primary-bg-color; From 2e3a6b3c0ba2c1dc3cdf562299b36e8d2788365b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 14:40:33 +0200 Subject: [PATCH 161/188] set 50% transparent accent color as editor focus border --- res/css/views/elements/_MessageEditor.scss | 4 ++++ res/themes/light/css/_light.scss | 1 + 2 files changed, 5 insertions(+) diff --git a/res/css/views/elements/_MessageEditor.scss b/res/css/views/elements/_MessageEditor.scss index e721b267fa..7fd99bae17 100644 --- a/res/css/views/elements/_MessageEditor.scss +++ b/res/css/views/elements/_MessageEditor.scss @@ -34,6 +34,10 @@ limitations under the License. max-height: 200px; overflow-x: auto; + &:focus { + border-color: $accent-color-50pct; + } + span.mx_UserPill, span.mx_RoomPill { padding-left: 21px; position: relative; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 3ef2ddc97f..2dd193b8c5 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -28,6 +28,7 @@ $focus-bg-color: #dddddd; // button UI (white-on-green in light skin) $accent-fg-color: #ffffff; +$accent-color-50pct: rgba(3, 179, 129, 0.5); //#03b381 in rgb $accent-color-darker: #92caad; $accent-color-alt: #238CF5; From 92c2a119c94b1ead60909d780e79556884f5a374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=BCrmann?= Date: Wed, 26 Jun 2019 15:27:52 +0200 Subject: [PATCH 162/188] Remove top margin of community panel container MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a simpler fix for the scrollbar problem than changing the definition for height, which was my previous attempt at solving this problem. Additionally, the top and bottom margins are now consistent. Signed-off-by: Jonas Schürmann --- res/css/structures/_TagPanel.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/res/css/structures/_TagPanel.scss b/res/css/structures/_TagPanel.scss index a01e5dd838..b03d36a592 100644 --- a/res/css/structures/_TagPanel.scss +++ b/res/css/structures/_TagPanel.scss @@ -63,9 +63,8 @@ limitations under the License. display: flex; flex-direction: column; align-items: center; - margin-top: 5px; - height: calc(100% - 5px); + height: 100%; } .mx_TagPanel .mx_TagPanel_tagTileContainer > div { height: 40px; From fa0319f14b005fc72d5d9368bf590b5afa69f848 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 15:49:24 +0200 Subject: [PATCH 163/188] apply renamed (token -> from) option --- src/components/views/dialogs/MessageEditHistoryDialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.js b/src/components/views/dialogs/MessageEditHistoryDialog.js index 1f5408e94c..dd6ef0d866 100644 --- a/src/components/views/dialogs/MessageEditHistoryDialog.js +++ b/src/components/views/dialogs/MessageEditHistoryDialog.js @@ -42,7 +42,7 @@ export default class MessageEditHistoryDialog extends React.Component { // bail out on backwards as we only paginate in one direction return false; } - const opts = {token: this.state.nextBatch}; + const opts = {from: this.state.nextBatch}; const roomId = this.props.mxEvent.getRoomId(); const eventId = this.props.mxEvent.getId(); const result = await MatrixClientPeg.get().relations( From f4b86ca2658f70017c34037f183f8f6b63c5bd5f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 15:51:28 +0200 Subject: [PATCH 164/188] don't bind --- src/components/views/dialogs/MessageEditHistoryDialog.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.js b/src/components/views/dialogs/MessageEditHistoryDialog.js index dd6ef0d866..45d46b49f2 100644 --- a/src/components/views/dialogs/MessageEditHistoryDialog.js +++ b/src/components/views/dialogs/MessageEditHistoryDialog.js @@ -28,7 +28,6 @@ export default class MessageEditHistoryDialog extends React.Component { }; componentWillMount() { - this.loadMoreEdits = this.loadMoreEdits.bind(this); this.setState({ events: [], nextBatch: null, @@ -37,7 +36,7 @@ export default class MessageEditHistoryDialog extends React.Component { }); } - async loadMoreEdits(backwards) { + loadMoreEdits = async (backwards) => { if (backwards || (!this.state.nextBatch && !this.state.isLoading)) { // bail out on backwards as we only paginate in one direction return false; From 39c96b15d8bfb98723c0164230a6a38ac78859d4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 15:51:46 +0200 Subject: [PATCH 165/188] set state in ctor --- src/components/views/dialogs/MessageEditHistoryDialog.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.js b/src/components/views/dialogs/MessageEditHistoryDialog.js index 45d46b49f2..9b11fadf83 100644 --- a/src/components/views/dialogs/MessageEditHistoryDialog.js +++ b/src/components/views/dialogs/MessageEditHistoryDialog.js @@ -27,13 +27,14 @@ export default class MessageEditHistoryDialog extends React.Component { mxEvent: PropTypes.object.isRequired, }; - componentWillMount() { - this.setState({ + constructor(props) { + super(props); + this.state = { events: [], nextBatch: null, isLoading: true, isTwelveHour: SettingsStore.getValue("showTwelveHourTimestamps"), - }); + }; } loadMoreEdits = async (backwards) => { From 929020a13964f956c423d53d50f61acf1fa39741 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 15:51:55 +0200 Subject: [PATCH 166/188] remove leftover logging --- src/components/views/dialogs/MessageEditHistoryDialog.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.js b/src/components/views/dialogs/MessageEditHistoryDialog.js index 9b11fadf83..81a3732fe6 100644 --- a/src/components/views/dialogs/MessageEditHistoryDialog.js +++ b/src/components/views/dialogs/MessageEditHistoryDialog.js @@ -47,7 +47,6 @@ export default class MessageEditHistoryDialog extends React.Component { const eventId = this.props.mxEvent.getId(); const result = await MatrixClientPeg.get().relations( roomId, eventId, "m.replace", "m.room.message", opts); - //console.log(`loadMoreEdits: got ${result.}`) let resolve; const promise = new Promise(r => resolve = r); this.setState({ From d606c966ea7194854af5d22bd91e77bf76315de4 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 16:12:44 +0200 Subject: [PATCH 167/188] use PureComponent --- src/components/views/dialogs/MessageEditHistoryDialog.js | 2 +- src/components/views/elements/EditHistoryMessage.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.js b/src/components/views/dialogs/MessageEditHistoryDialog.js index 81a3732fe6..786f1f43a0 100644 --- a/src/components/views/dialogs/MessageEditHistoryDialog.js +++ b/src/components/views/dialogs/MessageEditHistoryDialog.js @@ -22,7 +22,7 @@ import sdk from "../../../index"; import {wantsDateSeparator} from '../../../DateUtils'; import SettingsStore from '../../../settings/SettingsStore'; -export default class MessageEditHistoryDialog extends React.Component { +export default class MessageEditHistoryDialog extends React.PureComponent { static propTypes = { mxEvent: PropTypes.object.isRequired, }; diff --git a/src/components/views/elements/EditHistoryMessage.js b/src/components/views/elements/EditHistoryMessage.js index a6cbfbdfd4..b4b87bd246 100644 --- a/src/components/views/elements/EditHistoryMessage.js +++ b/src/components/views/elements/EditHistoryMessage.js @@ -21,7 +21,7 @@ import {formatTime} from '../../../DateUtils'; import {MatrixEvent} from 'matrix-js-sdk'; import {pillifyLinks} from '../../../utils/pillify'; -export default class EditHistoryMessage extends React.Component { +export default class EditHistoryMessage extends React.PureComponent { static propTypes = { // the message event being edited mxEvent: PropTypes.instanceOf(MatrixEvent).isRequired, From c987f4e8d8ac71289820d1636d6721ef32320b54 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 16:12:57 +0200 Subject: [PATCH 168/188] remove passthrough ctor --- src/components/views/elements/EditHistoryMessage.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/views/elements/EditHistoryMessage.js b/src/components/views/elements/EditHistoryMessage.js index b4b87bd246..2807bedeb8 100644 --- a/src/components/views/elements/EditHistoryMessage.js +++ b/src/components/views/elements/EditHistoryMessage.js @@ -27,10 +27,6 @@ export default class EditHistoryMessage extends React.PureComponent { mxEvent: PropTypes.instanceOf(MatrixEvent).isRequired, }; - constructor(props, context) { - super(props, context); - } - componentDidMount() { pillifyLinks(this.refs.content.children, this.props.mxEvent); } From a1548285b55688097a743a3dd2132308664dbd0a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 16:13:15 +0200 Subject: [PATCH 169/188] fix copyright header and whitespace --- .../views/elements/EditHistoryMessage.js | 2 +- src/utils/pillify.js | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/EditHistoryMessage.js b/src/components/views/elements/EditHistoryMessage.js index 2807bedeb8..85a704641a 100644 --- a/src/components/views/elements/EditHistoryMessage.js +++ b/src/components/views/elements/EditHistoryMessage.js @@ -1,5 +1,4 @@ /* -Copyright 2019 New Vector Ltd Copyright 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +13,7 @@ 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 * as HtmlUtils from '../../../HtmlUtils'; diff --git a/src/utils/pillify.js b/src/utils/pillify.js index 74eac560d3..e943cfe657 100644 --- a/src/utils/pillify.js +++ b/src/utils/pillify.js @@ -1,10 +1,25 @@ +/* +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 ReactDOM from 'react-dom'; import MatrixClientPeg from '../MatrixClientPeg'; import SettingsStore from "../settings/SettingsStore"; import PushProcessor from 'matrix-js-sdk/lib/pushprocessor'; import sdk from '../index'; - export function pillifyLinks(nodes, mxEvent) { const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId()); const shouldShowPillAvatar = SettingsStore.getValue("Pill.shouldShowPillAvatar"); From 54de0b298b36bdfc68d48b628373b21ed2779e75 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 16:13:32 +0200 Subject: [PATCH 170/188] add "Click to see edits." to tooltip --- src/components/views/messages/TextualBody.js | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index db5d178c96..25316844df 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -368,7 +368,7 @@ module.exports = React.createClass({ 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 01d84324ce..15dbd95d8c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -954,7 +954,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. Click to view edits.": "Edited at %(date)s. Click to view edits.", "edited": "edited", "Removed or unknown message type": "Removed or unknown message type", "Message removed by %(userId)s": "Message removed by %(userId)s", From c9c84016cbc0a438e3a2c02b7d01987c0b77da7d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 16:17:25 +0200 Subject: [PATCH 171/188] move EditHistoryMessage to messages directory --- src/components/views/dialogs/MessageEditHistoryDialog.js | 2 +- .../views/{elements => messages}/EditHistoryMessage.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/components/views/{elements => messages}/EditHistoryMessage.js (100%) diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.js b/src/components/views/dialogs/MessageEditHistoryDialog.js index 786f1f43a0..9d533eab56 100644 --- a/src/components/views/dialogs/MessageEditHistoryDialog.js +++ b/src/components/views/dialogs/MessageEditHistoryDialog.js @@ -65,7 +65,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent { } _renderEdits() { - const EditHistoryMessage = sdk.getComponent('elements.EditHistoryMessage'); + const EditHistoryMessage = sdk.getComponent('messages.EditHistoryMessage'); const DateSeparator = sdk.getComponent('messages.DateSeparator'); const nodes = []; let lastEvent; diff --git a/src/components/views/elements/EditHistoryMessage.js b/src/components/views/messages/EditHistoryMessage.js similarity index 100% rename from src/components/views/elements/EditHistoryMessage.js rename to src/components/views/messages/EditHistoryMessage.js From f6e0cd9a03914546dc3abe130c359a14cbb55d65 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 26 Jun 2019 18:54:15 +0200 Subject: [PATCH 172/188] don't show error dialog when user has no webcam instead, retry with just audio. Also when mounted, check if the user has given enough permissions to return non-empty labels for the devices, something both ff & chrome do if you haven't going through the permissions popup yet. If not, show the permissions button. --- src/CallMediaHandler.js | 5 ++ .../tabs/user/VoiceUserSettingsTab.js | 77 +++++++++++-------- src/i18n/strings/en_EN.json | 3 - 3 files changed, 48 insertions(+), 37 deletions(-) diff --git a/src/CallMediaHandler.js b/src/CallMediaHandler.js index 55b8764a9e..a0364f798a 100644 --- a/src/CallMediaHandler.js +++ b/src/CallMediaHandler.js @@ -18,6 +18,11 @@ import * as Matrix from 'matrix-js-sdk'; import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; export default { + hasAnyLabeledDevices: async function() { + const devices = await navigator.mediaDevices.enumerateDevices(); + return devices.some(d => !!d.label); + }, + getDevices: function() { // Only needed for Electron atm, though should work in modern browsers // once permission has been granted to the webapp diff --git a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js index 31a11b13ea..0cafbacf8d 100644 --- a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js @@ -29,48 +29,64 @@ export default class VoiceUserSettingsTab extends React.Component { super(); this.state = { - mediaDevices: null, + mediaDevices: false, activeAudioOutput: null, activeAudioInput: null, activeVideoInput: null, }; } - componentWillMount(): void { - this._refreshMediaDevices(); + async componentDidMount() { + const canSeeDeviceLabels = await CallMediaHandler.hasAnyLabeledDevices(); + if (canSeeDeviceLabels) { + this._refreshMediaDevices(); + } } _refreshMediaDevices = async (stream) => { - if (stream) { - // kill stream so that we don't leave it lingering around with webcam enabled etc - // as here we called gUM to ask user for permission to their device names only - stream.getTracks().forEach((track) => track.stop()); - } - this.setState({ mediaDevices: await CallMediaHandler.getDevices(), activeAudioOutput: CallMediaHandler.getAudioOutput(), activeAudioInput: CallMediaHandler.getAudioInput(), activeVideoInput: CallMediaHandler.getVideoInput(), }); + if (stream) { + // kill stream (after we've enumerated the devices, otherwise we'd get empty labels again) + // so that we don't leave it lingering around with webcam enabled etc + // as here we called gUM to ask user for permission to their device names only + stream.getTracks().forEach((track) => track.stop()); + } }; - _requestMediaPermissions = () => { - const getUserMedia = ( - window.navigator.getUserMedia || window.navigator.webkitGetUserMedia || window.navigator.mozGetUserMedia - ); - if (getUserMedia) { - return getUserMedia.apply(window.navigator, [ - { video: true, audio: true }, - this._refreshMediaDevices, - function() { - const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); - Modal.createTrackedDialog('No media permissions', '', ErrorDialog, { - title: _t('No media permissions'), - description: _t('You may need to manually permit Riot to access your microphone/webcam'), - }); - }, - ]); + _requestMediaPermissions = async () => { + let constraints; + let stream; + let error; + try { + constraints = {video: true, audio: true}; + stream = await navigator.mediaDevices.getUserMedia(constraints); + } catch (err) { + // user likely doesn't have a webcam, + // we should still allow to select a microphone + if (err.name === "NotFoundError") { + constraints = { audio: true }; + try { + stream = await navigator.mediaDevices.getUserMedia(constraints); + } catch (err) { + error = err; + } + } else { + error = err; + } + } + if (error) { + const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); + Modal.createTrackedDialog('No media permissions', '', ErrorDialog, { + title: _t('No media permissions'), + description: _t('You may need to manually permit Riot to access your microphone/webcam'), + }); + } else { + this._refreshMediaDevices(stream); } }; @@ -101,15 +117,7 @@ export default class VoiceUserSettingsTab extends React.Component { _renderDeviceOptions(devices, category) { return devices.map((d) => { - let label = d.label; - if (!label) { - switch (d.kind) { - case "audioinput": label = _t("Unnamed microphone"); break; - case "audiooutput": label = _t("Unnamed audio output"); break; - case "videoinput": label = _t("Unnamed camera"); break; - } - } - return (); + return (); }); } @@ -120,6 +128,7 @@ export default class VoiceUserSettingsTab extends React.Component { let speakerDropdown = null; let microphoneDropdown = null; let webcamDropdown = null; + console.log({mediaDevices: this.state.mediaDevices}); if (this.state.mediaDevices === false) { requestButton = (
    diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 010ad29da0..d3d32a6e89 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -615,9 +615,6 @@ "Learn more about how we use analytics.": "Learn more about how we use analytics.", "No media permissions": "No media permissions", "You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam", - "Unnamed microphone": "Unnamed microphone", - "Unnamed audio output": "Unnamed audio output", - "Unnamed camera": "Unnamed camera", "Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.", "Request media permissions": "Request media permissions", "No Audio Outputs detected": "No Audio Outputs detected", From e8fba4f77091cfe58d7dff32ea7ea0ef3ed84f38 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 26 Jun 2019 18:16:38 +0100 Subject: [PATCH 173/188] Change interactive tooltip to only flip when required This changes the interactive tooltip to only flip around when the tooltip content would be near the window edge. Fixes https://github.com/vector-im/riot-web/issues/10176 --- .../views/elements/InteractiveTooltip.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/components/views/elements/InteractiveTooltip.js b/src/components/views/elements/InteractiveTooltip.js index b56fd62556..52d51e0b39 100644 --- a/src/components/views/elements/InteractiveTooltip.js +++ b/src/components/views/elements/InteractiveTooltip.js @@ -21,6 +21,10 @@ import classNames from 'classnames'; const InteractiveTooltipContainerId = "mx_InteractiveTooltip_Container"; +// If the distance from tooltip to window edge is below this value, the tooltip +// will flip around to the other side of the target. +const MIN_SAFE_DISTANCE_TO_WINDOW_EDGE = 20; + function getOrCreateContainer() { let container = document.getElementById(InteractiveTooltipContainerId); @@ -121,7 +125,7 @@ export default class InteractiveTooltip extends React.Component { } renderTooltip() { - const { visible } = this.state; + const { contentRect, visible } = this.state; if (!visible) { ReactDOM.unmountComponentAtNode(getOrCreateContainer()); return null; @@ -134,11 +138,12 @@ export default class InteractiveTooltip extends React.Component { const targetBottom = targetRect.bottom + window.pageYOffset; const targetTop = targetRect.top + window.pageYOffset; - // Align the tooltip vertically on whichever side of the target has more - // space available. + // Place the tooltip above the target by default. If we find that the + // tooltip content would extend past the safe area towards the window + // edge, flip around to below the target. const position = {}; let chevronFace = null; - if (targetBottom < window.innerHeight / 2) { + if (contentRect && (targetTop - contentRect.height <= MIN_SAFE_DISTANCE_TO_WINDOW_EDGE)) { position.top = targetBottom; chevronFace = "top"; } else { @@ -158,8 +163,8 @@ export default class InteractiveTooltip extends React.Component { }); const menuStyle = {}; - if (this.state.contentRect) { - menuStyle.left = `-${this.state.contentRect.width / 2}px`; + if (contentRect) { + menuStyle.left = `-${contentRect.width / 2}px`; } const tooltip =
    From debcafd7606ecf569192faa84fd93c45b1bf3416 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 26 Jun 2019 18:37:59 +0100 Subject: [PATCH 174/188] if on trackpad, don't mess with horizontal scrolling. trackpad heuristic is 'if 15 minutes of no horizontal scrollwheel events, assume user may have switched to mousewheel' --- .../structures/IndicatorScrollbar.js | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/components/structures/IndicatorScrollbar.js b/src/components/structures/IndicatorScrollbar.js index b11e655f0d..52e0c9b8ac 100644 --- a/src/components/structures/IndicatorScrollbar.js +++ b/src/components/structures/IndicatorScrollbar.js @@ -131,17 +131,41 @@ export default class IndicatorScrollbar extends React.Component { // the harshness of the scroll behaviour. Should be a value between 0 and 1. const yRetention = 1.0; - // Check for trackpad users every so often to avoid boosting their scroll. + // whenever see horizontal scrolling, assume the user is on a trackpad + // for at least the next 15 minutes. + const now = new Date().getTime(); + if (Math.abs(e.deltaX) > 0) { + this._likelyTrackpadUser = true; + this._checkAgainForTrackpad = now + (15 * 60 * 1000); // 15min + } + else { + // if we haven't seen any horizontal scrolling for >15 minutes, assume + // the user might have plugged in a mousewheel + if (this._likelyTrackpadUser && now >= this._checkAgainForTrackpad) { + this._likelyTrackpadUser = false; + } + } + + // don't mess with the horizontal scroll for trackpad users + // See https://github.com/vector-im/riot-web/issues/10005 + if (this._likelyTrackpadUser) { + return; + } + + // Every 15 minutes, start checking to Check for trackpad users every so often to messing with their scroll // See https://github.com/vector-im/riot-web/issues/10005 const now = new Date().getTime(); - if (now >= this._checkAgainForTrackpad) { - this._likelyTrackpadUser = Math.abs(e.deltaX) > 0; + if (this._likelyTrackpadUser && now >= this._checkAgainForTrackpad) { + if (Math.abs(e.deltaX) > 0) { + this._likelyTrackpadUser = true; + } this._checkAgainForTrackpad = now + (15 * 60 * 1000); // 15min } - const safeToBoost = !this._likelyTrackpadUser; + if (this._likelyTrackpadUser) return; + + if (Math.abs(e.deltaX) <= xyThreshold) { // we are vertically scrolling. - if (Math.abs(e.deltaX) <= xyThreshold) { // HACK: We increase the amount of scroll to counteract smooth scrolling browsers. // Smooth scrolling browsers (Firefox) use the relative area to determine the scroll // amount, which means the likely small area of content results in a small amount of @@ -152,7 +176,7 @@ export default class IndicatorScrollbar extends React.Component { const additionalScroll = e.deltaY < 0 ? -50 : 50; // noinspection JSSuspiciousNameCombination - const val = Math.abs(e.deltaY) < 25 && safeToBoost ? (e.deltaY + additionalScroll) : e.deltaY; + const val = Math.abs(e.deltaY) < 25 ? (e.deltaY + additionalScroll) : 0; this._scrollElement.scrollLeft += val * yRetention; } } From 3d11eb430b59c77a53a925506f1a22cb34c061c7 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 26 Jun 2019 18:38:46 +0100 Subject: [PATCH 175/188] oops, remove old code --- src/components/structures/IndicatorScrollbar.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/components/structures/IndicatorScrollbar.js b/src/components/structures/IndicatorScrollbar.js index 52e0c9b8ac..762a626c02 100644 --- a/src/components/structures/IndicatorScrollbar.js +++ b/src/components/structures/IndicatorScrollbar.js @@ -152,18 +152,6 @@ export default class IndicatorScrollbar extends React.Component { return; } - // Every 15 minutes, start checking to Check for trackpad users every so often to messing with their scroll - // See https://github.com/vector-im/riot-web/issues/10005 - const now = new Date().getTime(); - if (this._likelyTrackpadUser && now >= this._checkAgainForTrackpad) { - if (Math.abs(e.deltaX) > 0) { - this._likelyTrackpadUser = true; - } - this._checkAgainForTrackpad = now + (15 * 60 * 1000); // 15min - } - - if (this._likelyTrackpadUser) return; - if (Math.abs(e.deltaX) <= xyThreshold) { // we are vertically scrolling. // HACK: We increase the amount of scroll to counteract smooth scrolling browsers. From 7fc5d229d64823c142b74074b537760ea5324106 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 26 Jun 2019 21:13:17 +0100 Subject: [PATCH 176/188] fix stuff as per review --- src/components/structures/IndicatorScrollbar.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/structures/IndicatorScrollbar.js b/src/components/structures/IndicatorScrollbar.js index 762a626c02..21a23e5670 100644 --- a/src/components/structures/IndicatorScrollbar.js +++ b/src/components/structures/IndicatorScrollbar.js @@ -131,15 +131,15 @@ export default class IndicatorScrollbar extends React.Component { // the harshness of the scroll behaviour. Should be a value between 0 and 1. const yRetention = 1.0; - // whenever see horizontal scrolling, assume the user is on a trackpad - // for at least the next 15 minutes. + // whenever we see horizontal scrolling, assume the user is on a trackpad + // for at least the next 1 minute. const now = new Date().getTime(); if (Math.abs(e.deltaX) > 0) { this._likelyTrackpadUser = true; - this._checkAgainForTrackpad = now + (15 * 60 * 1000); // 15min + this._checkAgainForTrackpad = now + (1 * 60 * 1000); } else { - // if we haven't seen any horizontal scrolling for >15 minutes, assume + // if we haven't seen any horizontal scrolling for a while, assume // the user might have plugged in a mousewheel if (this._likelyTrackpadUser && now >= this._checkAgainForTrackpad) { this._likelyTrackpadUser = false; @@ -164,7 +164,7 @@ export default class IndicatorScrollbar extends React.Component { const additionalScroll = e.deltaY < 0 ? -50 : 50; // noinspection JSSuspiciousNameCombination - const val = Math.abs(e.deltaY) < 25 ? (e.deltaY + additionalScroll) : 0; + const val = Math.abs(e.deltaY) < 25 ? (e.deltaY + additionalScroll) : e.deltaY; this._scrollElement.scrollLeft += val * yRetention; } } From 3873dc724aaa4072d367e0980293d11e33239b2a Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 26 Jun 2019 21:47:55 +0100 Subject: [PATCH 177/188] stupid linter >:( --- src/components/structures/IndicatorScrollbar.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/structures/IndicatorScrollbar.js b/src/components/structures/IndicatorScrollbar.js index 21a23e5670..1895d2089d 100644 --- a/src/components/structures/IndicatorScrollbar.js +++ b/src/components/structures/IndicatorScrollbar.js @@ -137,8 +137,7 @@ export default class IndicatorScrollbar extends React.Component { if (Math.abs(e.deltaX) > 0) { this._likelyTrackpadUser = true; this._checkAgainForTrackpad = now + (1 * 60 * 1000); - } - else { + } else { // if we haven't seen any horizontal scrolling for a while, assume // the user might have plugged in a mousewheel if (this._likelyTrackpadUser && now >= this._checkAgainForTrackpad) { From e287362a8b63ea7feef7bc1c531241efbe390277 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 27 Jun 2019 11:19:36 +0100 Subject: [PATCH 178/188] Reaction buttons should use pointer cursor --- res/css/views/messages/_ReactionTooltipButton.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/messages/_ReactionTooltipButton.scss b/res/css/views/messages/_ReactionTooltipButton.scss index bf1c25e126..59244ab63b 100644 --- a/res/css/views/messages/_ReactionTooltipButton.scss +++ b/res/css/views/messages/_ReactionTooltipButton.scss @@ -18,6 +18,7 @@ limitations under the License. font-size: 16px; padding: 6px; user-select: none; + cursor: pointer; transition: transform 0.25s; &:hover { From c0e9edcf409a71ea14db9fe368c8f1f753be240a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 27 Jun 2019 12:33:29 +0200 Subject: [PATCH 179/188] get decrypted content if needed --- src/components/views/messages/EditHistoryMessage.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/EditHistoryMessage.js b/src/components/views/messages/EditHistoryMessage.js index 85a704641a..fef9c362c6 100644 --- a/src/components/views/messages/EditHistoryMessage.js +++ b/src/components/views/messages/EditHistoryMessage.js @@ -37,7 +37,8 @@ export default class EditHistoryMessage extends React.PureComponent { render() { const {mxEvent} = this.props; - const content = mxEvent.event.content["m.new_content"] || mxEvent.event.content; + const originalContent = mxEvent.getOriginalContent(); + const content = originalContent["m.new_content"] || originalContent; const contentElements = HtmlUtils.bodyToHtml(content); let contentContainer; if (mxEvent.getContent().msgtype === "m.emote") { From 25aa65ac0d22d6cd8365f735d71a443185268ec9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 27 Jun 2019 14:13:37 +0200 Subject: [PATCH 180/188] remove leftover logging --- src/components/views/settings/tabs/user/VoiceUserSettingsTab.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js index 0cafbacf8d..eb85fe4e44 100644 --- a/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/VoiceUserSettingsTab.js @@ -128,7 +128,6 @@ export default class VoiceUserSettingsTab extends React.Component { let speakerDropdown = null; let microphoneDropdown = null; let webcamDropdown = null; - console.log({mediaDevices: this.state.mediaDevices}); if (this.state.mediaDevices === false) { requestButton = (
    From 59b4a3398d60469dfaddf5e3fd3ac2dabb538676 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 27 Jun 2019 13:05:04 +0100 Subject: [PATCH 181/188] Limit reactions row on initial display This limits the reactions row below messages to initially show at most 8 keys. For those messages with more than that, a "Show all" option appears to reveal all the keys. Fixes https://github.com/vector-im/riot-web/issues/9570 --- res/css/views/messages/_ReactionsRow.scss | 14 +++++++++ src/components/views/messages/ReactionsRow.js | 30 +++++++++++++++++-- src/i18n/strings/en_EN.json | 1 + 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/res/css/views/messages/_ReactionsRow.scss b/res/css/views/messages/_ReactionsRow.scss index 3b764e97b4..57c02ed3e5 100644 --- a/res/css/views/messages/_ReactionsRow.scss +++ b/res/css/views/messages/_ReactionsRow.scss @@ -18,3 +18,17 @@ limitations under the License. margin: 6px 0; color: $primary-fg-color; } + +.mx_ReactionsRow_showAll { + text-decoration: none; + font-size: 10px; + font-weight: 600; + margin-left: 6px; + vertical-align: top; + + &:hover, + &:link, + &:visited { + color: $accent-color; + } +} diff --git a/src/components/views/messages/ReactionsRow.js b/src/components/views/messages/ReactionsRow.js index 51f62807a5..f210266c66 100644 --- a/src/components/views/messages/ReactionsRow.js +++ b/src/components/views/messages/ReactionsRow.js @@ -18,10 +18,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import sdk from '../../../index'; +import { _t } from '../../../languageHandler'; import { isContentActionable } from '../../../utils/EventUtils'; import { isSingleEmoji } from '../../../HtmlUtils'; import MatrixClientPeg from '../../../MatrixClientPeg'; +// The maximum number of reactions to initially show on a message. +const MAX_ITEMS_WHEN_LIMITED = 8; + export default class ReactionsRow extends React.PureComponent { static propTypes = { // The event we're displaying reactions for @@ -41,6 +45,7 @@ export default class ReactionsRow extends React.PureComponent { this.state = { myReactions: this.getMyReactions(), + showAll: false, }; } @@ -94,16 +99,22 @@ export default class ReactionsRow extends React.PureComponent { return [...myReactions.values()]; } + onShowAllClick = () => { + this.setState({ + showAll: true, + }); + } + render() { const { mxEvent, reactions } = this.props; - const { myReactions } = this.state; + const { myReactions, showAll } = this.state; if (!reactions || !isContentActionable(mxEvent)) { return null; } const ReactionsRowButton = sdk.getComponent('messages.ReactionsRowButton'); - const items = reactions.getSortedAnnotationsByKey().map(([content, events]) => { + let items = reactions.getSortedAnnotationsByKey().map(([content, events]) => { if (!isSingleEmoji(content)) { return null; } @@ -125,10 +136,23 @@ export default class ReactionsRow extends React.PureComponent { reactionEvents={events} myReactionEvent={myReactionEvent} />; - }); + }).filter(item => !!item); + + let showAllLink; + if (items.length > MAX_ITEMS_WHEN_LIMITED && !showAll) { + items = items.slice(0, MAX_ITEMS_WHEN_LIMITED); + showAllLink = + {_t("Show all")} + ; + } return
    {items} + {showAllLink}
    ; } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index de6a06e9e4..2e9f746054 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -944,6 +944,7 @@ "Party Popper": "Party Popper", "Confused": "Confused", "Eyes": "Eyes", + "Show all": "Show all", "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.", From 804a0c8507674277c66c9dacf69c88892c836e28 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 27 Jun 2019 18:56:22 +0100 Subject: [PATCH 182/188] Fix weird scrollbar when devtools is in a narrow browser Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/views/dialogs/_DevtoolsDialog.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/res/css/views/dialogs/_DevtoolsDialog.scss b/res/css/views/dialogs/_DevtoolsDialog.scss index 8e669acd10..c63a1b8e7d 100644 --- a/res/css/views/dialogs/_DevtoolsDialog.scss +++ b/res/css/views/dialogs/_DevtoolsDialog.scss @@ -81,10 +81,6 @@ limitations under the License. padding: 10px; } -.mx_DevTools_content .mx_Field_input { - display: inline-block; -} - .mx_DevTools_eventTypeStateKeyGroup { display: flex; flex-wrap: wrap; From 7e8eda33fce2153e1ee64f410ca9dab5e0013a88 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 27 Jun 2019 12:10:58 -0600 Subject: [PATCH 183/188] Flexboxify generic error page --- res/css/structures/_GenericErrorPage.scss | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/res/css/structures/_GenericErrorPage.scss b/res/css/structures/_GenericErrorPage.scss index 2b9e9f5e7d..7e9d7bbdaa 100644 --- a/res/css/structures/_GenericErrorPage.scss +++ b/res/css/structures/_GenericErrorPage.scss @@ -2,17 +2,15 @@ width: 100%; height: 100%; background-color: #fff; + display: flex; + align-items: center; + justify-content: center; } .mx_GenericErrorPage_box { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - margin: auto; + display: inline; width: 500px; - height: 125px; + min-height: 125px; border: 1px solid #f22; padding: 10px 10px 20px; background-color: #fcc; From 5473f7ba452e60c47c493bf5ce3397ab87227aac Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 27 Jun 2019 20:36:45 +0100 Subject: [PATCH 184/188] Unpin HLJS Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 96ec129f28..14d833df96 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "gemini-scrollbar": "github:matrix-org/gemini-scrollbar#b302279", "gfm.css": "^1.1.1", "glob": "^5.0.14", - "highlight.js": "9.14.2", + "highlight.js": "^9.15.8", "is-ip": "^2.0.0", "isomorphic-fetch": "^2.2.1", "linkifyjs": "^2.1.6", diff --git a/yarn.lock b/yarn.lock index 62868d0a92..7b949781d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3694,10 +3694,10 @@ he@1.1.1: resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= -highlight.js@9.14.2: - version "9.14.2" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.14.2.tgz#efbfb22dc701406e4da406056ef8c2b70ebe5b26" - integrity sha512-Nc6YNECYpxyJABGYJAyw7dBAYbXEuIzwzkqoJnwbc1nIpCiN+3ioYf0XrBnLiyyG0JLuJhpPtt2iTSbXiKLoyA== +highlight.js@^9.15.8: + version "9.15.8" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.8.tgz#f344fda123f36f1a65490e932cf90569e4999971" + integrity sha512-RrapkKQWwE+wKdF73VsOa2RQdIoO3mxwJ4P8mhbI6KYJUraUHRKM5w5zQQKXNk0xNL4UVRdulV9SBJcmzJNzVA== hmac-drbg@^1.0.0: version "1.0.1" From 527e1e94a4372e3f586dca8842d195e54e9bfbb4 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 28 Jun 2019 13:44:14 +0100 Subject: [PATCH 185/188] Update config.json docs location Part of https://github.com/vector-im/riot-web/pull/10195 --- src/utils/AutoDiscoveryUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/AutoDiscoveryUtils.js b/src/utils/AutoDiscoveryUtils.js index 06823e5d2a..e83e0348ca 100644 --- a/src/utils/AutoDiscoveryUtils.js +++ b/src/utils/AutoDiscoveryUtils.js @@ -67,7 +67,7 @@ export default class AutoDiscoveryUtils { {}, { a: (sub) => { return {sub}; From 04398b7853d9fcf3ad38376bac61d533943231e7 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 28 Jun 2019 14:46:57 +0100 Subject: [PATCH 186/188] Tweak limits so show all reveals more space than itself --- src/components/views/messages/ReactionsRow.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/ReactionsRow.js b/src/components/views/messages/ReactionsRow.js index f210266c66..4bae8c0ca1 100644 --- a/src/components/views/messages/ReactionsRow.js +++ b/src/components/views/messages/ReactionsRow.js @@ -138,8 +138,11 @@ export default class ReactionsRow extends React.PureComponent { />; }).filter(item => !!item); + // Show the first MAX_ITEMS if there are MAX_ITEMS + 1 or more items. + // The "+ 1" ensure that the "show all" reveals something that takes up + // more space than the button itself. let showAllLink; - if (items.length > MAX_ITEMS_WHEN_LIMITED && !showAll) { + if ((items.length > MAX_ITEMS_WHEN_LIMITED + 1) && !showAll) { items = items.slice(0, MAX_ITEMS_WHEN_LIMITED); showAllLink = Date: Fri, 28 Jun 2019 14:47:41 +0100 Subject: [PATCH 187/188] Rename link to button --- src/components/views/messages/ReactionsRow.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/messages/ReactionsRow.js b/src/components/views/messages/ReactionsRow.js index 4bae8c0ca1..57d2afc429 100644 --- a/src/components/views/messages/ReactionsRow.js +++ b/src/components/views/messages/ReactionsRow.js @@ -141,10 +141,10 @@ export default class ReactionsRow extends React.PureComponent { // Show the first MAX_ITEMS if there are MAX_ITEMS + 1 or more items. // The "+ 1" ensure that the "show all" reveals something that takes up // more space than the button itself. - let showAllLink; + let showAllButton; if ((items.length > MAX_ITEMS_WHEN_LIMITED + 1) && !showAll) { items = items.slice(0, MAX_ITEMS_WHEN_LIMITED); - showAllLink = {items} - {showAllLink} + {showAllButton}
    ; } } From 00dfdfe7f171fad3822efde3a9bdf2851f1cd910 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 28 Jun 2019 15:16:44 +0100 Subject: [PATCH 188/188] Fix linter warning --- src/components/structures/IndicatorScrollbar.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/structures/IndicatorScrollbar.js b/src/components/structures/IndicatorScrollbar.js index 1895d2089d..d6efe8bee2 100644 --- a/src/components/structures/IndicatorScrollbar.js +++ b/src/components/structures/IndicatorScrollbar.js @@ -152,7 +152,6 @@ export default class IndicatorScrollbar extends React.Component { } if (Math.abs(e.deltaX) <= xyThreshold) { // we are vertically scrolling. - // HACK: We increase the amount of scroll to counteract smooth scrolling browsers. // Smooth scrolling browsers (Firefox) use the relative area to determine the scroll // amount, which means the likely small area of content results in a small amount of