From 7d22bbc00f49356cd5fec3565a19376ef8b0ef05 Mon Sep 17 00:00:00 2001 From: Bryan Kok Date: Sat, 17 Oct 2020 23:52:18 +0800 Subject: [PATCH 0001/2197] Trim spurious whitespace of nicknames --- src/components/views/settings/ProfileSettings.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/settings/ProfileSettings.js b/src/components/views/settings/ProfileSettings.js index 92851ccaa0..294f80acd1 100644 --- a/src/components/views/settings/ProfileSettings.js +++ b/src/components/views/settings/ProfileSettings.js @@ -77,10 +77,12 @@ export default class ProfileSettings extends React.Component { const client = MatrixClientPeg.get(); const newState = {}; + const displayName = this.state.displayName.trim(); try { if (this.state.originalDisplayName !== this.state.displayName) { - await client.setDisplayName(this.state.displayName); - newState.originalDisplayName = this.state.displayName; + await client.setDisplayName(displayName); + newState.originalDisplayName = displayName; + newState.displayName = displayName; } if (this.state.avatarFile) { From fcbaea640daf3a036d55cb1bda5d7fed552c2d4e Mon Sep 17 00:00:00 2001 From: Bryan Kok Date: Sun, 18 Oct 2020 14:36:50 +0800 Subject: [PATCH 0002/2197] Trim room names changed through the UI --- src/components/views/room_settings/RoomProfileSettings.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/room_settings/RoomProfileSettings.js b/src/components/views/room_settings/RoomProfileSettings.js index ca09c3093a..b894663c16 100644 --- a/src/components/views/room_settings/RoomProfileSettings.js +++ b/src/components/views/room_settings/RoomProfileSettings.js @@ -95,10 +95,11 @@ export default class RoomProfileSettings extends React.Component { const newState = {}; // TODO: What do we do about errors? - + const displayName = this.state.displayName.trim(); if (this.state.originalDisplayName !== this.state.displayName) { - await client.setRoomName(this.props.roomId, this.state.displayName); - newState.originalDisplayName = this.state.displayName; + await client.setRoomName(this.props.roomId, displayName); + newState.originalDisplayName = displayName; + newState.displayName = displayName; } if (this.state.avatarFile) { From e33f4ba9c0e8c29cf074f566cbb69c27007f6c57 Mon Sep 17 00:00:00 2001 From: Resynth Date: Sun, 25 Oct 2020 22:04:39 +0000 Subject: [PATCH 0003/2197] Warn on Access Token reveal Signed-off-by: Resynth --- .../views/dialogs/AccessTokenDialog.tsx | 39 +++++++++++++++++++ .../settings/tabs/user/HelpUserSettingsTab.js | 14 ++++++- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/components/views/dialogs/AccessTokenDialog.tsx diff --git a/src/components/views/dialogs/AccessTokenDialog.tsx b/src/components/views/dialogs/AccessTokenDialog.tsx new file mode 100644 index 0000000000..81c48f219a --- /dev/null +++ b/src/components/views/dialogs/AccessTokenDialog.tsx @@ -0,0 +1,39 @@ + /* +Copyright 2017 Vector Creations 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. +*/ + +import React from 'react'; +import { _t } from '../../../languageHandler'; +import QuestionDialog from './QuestionDialog'; + +type IProps = Exclude< + React.ComponentProps, + "title" | "danger" | "description" + >; + +export default function AccessTokenDialog (props: IProps) { + return ( + + ); +} diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js index 85ba22a353..585a54ff86 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js @@ -27,6 +27,7 @@ import * as sdk from "../../../../../"; import PlatformPeg from "../../../../../PlatformPeg"; import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts"; import UpdateCheckButton from "../../UpdateCheckButton"; +import AccessTokenDialog from '../../../dialogs/AccessTokenDialog'; export default class HelpUserSettingsTab extends React.Component { static propTypes = { @@ -148,6 +149,17 @@ export default class HelpUserSettingsTab extends React.Component { ); } + onAccessTokenSpoilerClick = async (event) => { + // React throws away the event before we can use it (we are async, after all). + event.persist(); + + // We make the user accept a scary popup to combat Social Engineering. No peeking! + await Modal.createTrackedDialog('Reveal Access Token', '', AccessTokenDialog).finished; + + // Pass it onto the handler. + this._showSpoiler(event); + } + render() { const brand = SdkConfig.get().brand; @@ -266,7 +278,7 @@ export default class HelpUserSettingsTab extends React.Component { {_t("Homeserver is")} {MatrixClientPeg.get().getHomeserverUrl()}
{_t("Identity Server is")} {MatrixClientPeg.get().getIdentityServerUrl()}
{_t("Access Token:") + ' '} - <{ _t("click to reveal") }> From ae29168e077be47a83628f10b7765d6bcc9a7a0a Mon Sep 17 00:00:00 2001 From: Resynth Date: Sun, 25 Oct 2020 22:05:44 +0000 Subject: [PATCH 0004/2197] Lint Signed-off-by: Resynth --- src/components/views/dialogs/AccessTokenDialog.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/dialogs/AccessTokenDialog.tsx b/src/components/views/dialogs/AccessTokenDialog.tsx index 81c48f219a..f95effd523 100644 --- a/src/components/views/dialogs/AccessTokenDialog.tsx +++ b/src/components/views/dialogs/AccessTokenDialog.tsx @@ -1,4 +1,4 @@ - /* +/* Copyright 2017 Vector Creations Ltd Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,7 @@ type IProps = Exclude< "title" | "danger" | "description" >; -export default function AccessTokenDialog (props: IProps) { +export default function AccessTokenDialog(props: IProps) { return ( ); From 76edd551e5833cb1c94c1025fa069aeb79a9faa2 Mon Sep 17 00:00:00 2001 From: Resynth Date: Mon, 26 Oct 2020 00:34:14 +0000 Subject: [PATCH 0005/2197] Fix i18n Signed-off-by: Resynth --- src/i18n/strings/en_EN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index eda69d68ea..ad6593f704 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1585,6 +1585,7 @@ "Add a new server...": "Add a new server...", "%(networkName)s rooms": "%(networkName)s rooms", "Matrix rooms": "Matrix rooms", + "Do not reveal your Access Token to anyone, under any circumstances. Sharing your Access Token with someone would allow them to login to your account, and access your private information.": "Do not reveal your Access Token to anyone, under any circumstances. Sharing your Access Token with someone would allow them to login to your account, and access your private information.", "Matrix ID": "Matrix ID", "Matrix Room ID": "Matrix Room ID", "email address": "email address", From a3212c0477ec0fc9166a56a49a9579bae4712d5f Mon Sep 17 00:00:00 2001 From: Resynth Date: Mon, 26 Oct 2020 23:46:53 +0000 Subject: [PATCH 0006/2197] Fix weird formatting Signed-off-by: Resynth --- src/components/views/dialogs/AccessTokenDialog.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/dialogs/AccessTokenDialog.tsx b/src/components/views/dialogs/AccessTokenDialog.tsx index f95effd523..2d96d8ec20 100644 --- a/src/components/views/dialogs/AccessTokenDialog.tsx +++ b/src/components/views/dialogs/AccessTokenDialog.tsx @@ -1,5 +1,6 @@ /* Copyright 2017 Vector Creations Ltd +Copyright 2020 Resynth Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,7 +22,7 @@ import QuestionDialog from './QuestionDialog'; type IProps = Exclude< React.ComponentProps, "title" | "danger" | "description" - >; +>; export default function AccessTokenDialog(props: IProps) { return ( From 29a81bbe85ca1ca1c4c54534fe94e9d477aef22c Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Tue, 10 Nov 2020 15:04:01 -0600 Subject: [PATCH 0007/2197] Warn when you attempt to leave room that you are the only member of Signed-off-by: Aaron Raimist --- src/components/structures/MatrixChat.tsx | 17 ++++++++++++++++- src/i18n/strings/en_EN.json | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 22cd73eff7..43e1798c6e 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1056,8 +1056,21 @@ export default class MatrixChat extends React.PureComponent { private leaveRoomWarnings(roomId: string) { const roomToLeave = MatrixClientPeg.get().getRoom(roomId); // Show a warning if there are additional complications. - const joinRules = roomToLeave.currentState.getStateEvents('m.room.join_rules', ''); const warnings = []; + + const memberCount = roomToLeave.currentState.getJoinedMemberCount(); + if (memberCount === 1) { + warnings.push( + + {' '/* Whitespace, otherwise the sentences get smashed together */ } + { _t("You are the only member of this room. This room will become unjoinable if you leave.") } + + ); + + return warnings; + } + + const joinRules = roomToLeave.currentState.getStateEvents('m.room.join_rules', ''); if (joinRules) { const rule = joinRules.getContent().join_rule; if (rule !== "public") { @@ -1076,6 +1089,7 @@ export default class MatrixChat extends React.PureComponent { const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); const roomToLeave = MatrixClientPeg.get().getRoom(roomId); const warnings = this.leaveRoomWarnings(roomId); + const hasWarnings = warnings.length > 0; Modal.createTrackedDialog('Leave room', '', QuestionDialog, { title: _t("Leave room"), @@ -1086,6 +1100,7 @@ export default class MatrixChat extends React.PureComponent { ), button: _t("Leave"), + danger: hasWarnings, onFinished: (shouldLeave) => { if (shouldLeave) { const d = leaveRoomBehaviour(roomId); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 78340447f3..b412db5ca0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2317,6 +2317,7 @@ "Cannot create rooms in this community": "Cannot create rooms in this community", "You do not have permission to create rooms in this community.": "You do not have permission to create rooms in this community.", "This room is not public. You will not be able to rejoin without an invite.": "This room is not public. You will not be able to rejoin without an invite.", + "You are the only member of this room. This room will become unjoinable if you leave.": "You are the only member of this room. This room will become unjoinable if you leave.", "Are you sure you want to leave the room '%(roomName)s'?": "Are you sure you want to leave the room '%(roomName)s'?", "Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s", "Signed Out": "Signed Out", From 80c4d54ccc56396e15a4979de84d9d18c83a70ad Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Wed, 18 Nov 2020 13:54:49 -0600 Subject: [PATCH 0008/2197] Fix lint Signed-off-by: Aaron Raimist --- src/components/structures/MatrixChat.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 43e1798c6e..17c21a2016 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1060,12 +1060,12 @@ export default class MatrixChat extends React.PureComponent { const memberCount = roomToLeave.currentState.getJoinedMemberCount(); if (memberCount === 1) { - warnings.push( + warnings.push(( {' '/* Whitespace, otherwise the sentences get smashed together */ } { _t("You are the only member of this room. This room will become unjoinable if you leave.") } - ); + )); return warnings; } From 34fbed3fbbbbb0ffc17a15631d256b855080b1ee Mon Sep 17 00:00:00 2001 From: Qt Resynth Date: Fri, 20 Nov 2020 01:15:58 +0000 Subject: [PATCH 0009/2197] Update src/components/views/dialogs/AccessTokenDialog.tsx --- src/components/views/dialogs/AccessTokenDialog.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/views/dialogs/AccessTokenDialog.tsx b/src/components/views/dialogs/AccessTokenDialog.tsx index 2d96d8ec20..6a943eb5a8 100644 --- a/src/components/views/dialogs/AccessTokenDialog.tsx +++ b/src/components/views/dialogs/AccessTokenDialog.tsx @@ -31,9 +31,7 @@ export default function AccessTokenDialog(props: IProps) { title="Reveal Access Token" danger={true} description={_t( - "Do not reveal your Access Token to anyone, under any circumstances. " + - "Sharing your Access Token with someone would allow them to login to " + - "your account, and access your private information.", + "Your access token gives full access to your account. Do not share it with anyone." )} > ); From 0e4d656e4bfc0fd16d4628ac44668f6aa2f5ae83 Mon Sep 17 00:00:00 2001 From: Resynth Date: Fri, 20 Nov 2020 18:21:39 +0000 Subject: [PATCH 0010/2197] Update src/i18n/strings/en_EN.json --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ad6593f704..8005f1cdef 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1585,7 +1585,7 @@ "Add a new server...": "Add a new server...", "%(networkName)s rooms": "%(networkName)s rooms", "Matrix rooms": "Matrix rooms", - "Do not reveal your Access Token to anyone, under any circumstances. Sharing your Access Token with someone would allow them to login to your account, and access your private information.": "Do not reveal your Access Token to anyone, under any circumstances. Sharing your Access Token with someone would allow them to login to your account, and access your private information.", + "Your access token gives full access to your account. Do not share it with anyone.", "Matrix ID": "Matrix ID", "Matrix Room ID": "Matrix Room ID", "email address": "email address", From 1b48b08525f75035b9a09993124a12d442d0f2e2 Mon Sep 17 00:00:00 2001 From: Resynth Date: Fri, 20 Nov 2020 18:23:18 +0000 Subject: [PATCH 0011/2197] Update src/i18n/strings/en_EN.json --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8005f1cdef..a57a68e7b4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1585,7 +1585,7 @@ "Add a new server...": "Add a new server...", "%(networkName)s rooms": "%(networkName)s rooms", "Matrix rooms": "Matrix rooms", - "Your access token gives full access to your account. Do not share it with anyone.", + "Your access token gives full access to your account. Do not share it with anyone.": "Your access token gives full access to your account. Do not share it with anyone.", "Matrix ID": "Matrix ID", "Matrix Room ID": "Matrix Room ID", "email address": "email address", From d44aab6d321d04deb6733d9ace5f54b5fe1104bf Mon Sep 17 00:00:00 2001 From: Resynth Date: Mon, 23 Nov 2020 12:57:06 +0000 Subject: [PATCH 0012/2197] Update src/components/views/dialogs/AccessTokenDialog.tsx Co-authored-by: Michael Telatynski <7t3chguy@googlemail.com> --- src/components/views/dialogs/AccessTokenDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/AccessTokenDialog.tsx b/src/components/views/dialogs/AccessTokenDialog.tsx index 6a943eb5a8..c0b1b929df 100644 --- a/src/components/views/dialogs/AccessTokenDialog.tsx +++ b/src/components/views/dialogs/AccessTokenDialog.tsx @@ -31,7 +31,7 @@ export default function AccessTokenDialog(props: IProps) { title="Reveal Access Token" danger={true} description={_t( - "Your access token gives full access to your account. Do not share it with anyone." + "Your access token gives full access to your account. Do not share it with anyone.", )} >
); From 2f988bc97fc1e414bd70149ac5f77301a3ef2833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 26 Nov 2020 13:51:03 +0100 Subject: [PATCH 0013/2197] Added UI --- .../views/settings/SpellCheckSettings.tsx | 111 ++++++++++++++++++ .../tabs/user/GeneralUserSettingsTab.js | 18 +++ 2 files changed, 129 insertions(+) create mode 100644 src/components/views/settings/SpellCheckSettings.tsx diff --git a/src/components/views/settings/SpellCheckSettings.tsx b/src/components/views/settings/SpellCheckSettings.tsx new file mode 100644 index 0000000000..1bdcd882c9 --- /dev/null +++ b/src/components/views/settings/SpellCheckSettings.tsx @@ -0,0 +1,111 @@ +/* +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. +*/ + +import React from 'react'; +import LanguageDropdown from "../../../components/views/elements/LanguageDropdown"; +import AccessibleButton from "../../../components/views/elements/AccessibleButton"; +import {_t} from "../../../languageHandler"; + +interface ExistingSpellCheckLanguageIProps { + language: string, + onRemoved(language: string), +}; + +interface SpellCheckLanguagesIProps { + languages: Array, + onLanguagesChange(languages: Array), +}; + +interface SpellCheckLanguagesIState { + newLanguage: string, +} + +export class ExistingSpellCheckLanguage extends React.Component { + _onRemove = (e) => { + e.stopPropagation(); + e.preventDefault(); + + return this.props.onRemoved(this.props.language); + }; + + render() { + return ( +
+ {this.props.language} + + {_t("Remove")} + +
+ ); + } +} + +export default class SpellCheckLanguages extends React.Component { + constructor(props) { + super(props); + this.state = { + newLanguage: "", + } + } + + _onRemoved = (language) => { + const languages = this.props.languages.filter((e) => e !== language); + this.props.onLanguagesChange(languages); + }; + + _onAddClick = (e) => { + e.stopPropagation(); + e.preventDefault(); + + const language = this.state.newLanguage; + + if (!language) return; + if (this.props.languages.includes(language)) return; + + this.props.languages.push(language) + this.props.onLanguagesChange(this.props.languages); + }; + + _onNewLanguageChange = (language: string) => { + if (this.state.newLanguage === language) return; + this.setState({newLanguage: language}); + }; + + render() { + const existingSpellCheckLanguages = this.props.languages.map((e) => { + return ; + }); + + let addButton = ( + + {_t("Add")} + + ); + + return ( +
+ {existingSpellCheckLanguages} +
+ + {addButton} + +
+ ); + }; +} diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 35285351ab..6d04d83047 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -22,6 +22,7 @@ import ProfileSettings from "../../ProfileSettings"; import * as languageHandler from "../../../../../languageHandler"; import SettingsStore from "../../../../../settings/SettingsStore"; import LanguageDropdown from "../../../elements/LanguageDropdown"; +import SpellCheckSettings from "../../SpellCheckSettings" import AccessibleButton from "../../../elements/AccessibleButton"; import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog"; import PropTypes from "prop-types"; @@ -49,6 +50,7 @@ export default class GeneralUserSettingsTab extends React.Component { this.state = { language: languageHandler.getCurrentLanguage(), + spellCheckLanguages: [], haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()), serverSupportsSeparateAddAndBind: null, idServerHasUnsignedTerms: false, @@ -182,6 +184,10 @@ export default class GeneralUserSettingsTab extends React.Component { PlatformPeg.get().reload(); }; + _onSpellCheckLanguagesChange = (languages) => { + this.setState({spellCheckLanguages: languages}) + }; + _onPasswordChangeError = (err) => { // TODO: Figure out a design that doesn't involve replacing the current dialog let errMsg = err.error || ""; @@ -303,6 +309,17 @@ export default class GeneralUserSettingsTab extends React.Component { ); } + _renderSpellCheckSection() { + return ( +
+ {_t("Spell checking")} + +
+ ); + } + _renderDiscoverySection() { const SetIdServer = sdk.getComponent("views.settings.SetIdServer"); @@ -409,6 +426,7 @@ export default class GeneralUserSettingsTab extends React.Component { {this._renderProfileSection()} {this._renderAccountSection()} {this._renderLanguageSection()} + {this._renderSpellCheckSection()} { discoverySection } {this._renderIntegrationManagerSection() /* Has its own title */} { accountManagementSection } From 051368eaab50e4a7d6f2ce554ddff0ed957bb965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 26 Nov 2020 13:53:22 +0100 Subject: [PATCH 0014/2197] Fix i18n --- src/i18n/strings/en_EN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0d50128f32..7fbcc1a350 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1109,6 +1109,7 @@ "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Use an Integration Manager to manage bots, widgets, and sticker packs.", "Manage integrations": "Manage integrations", "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.", + "Add": "Add", "Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).", "Checking for an update...": "Checking for an update...", "No update available.": "No update available.", @@ -1140,6 +1141,7 @@ "Set a new account password...": "Set a new account password...", "Account": "Account", "Language and region": "Language and region", + "Spell checking": "Spell checking", "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.", "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", @@ -1337,7 +1339,6 @@ "Invalid Email Address": "Invalid Email Address", "This doesn't appear to be a valid email address": "This doesn't appear to be a valid email address", "Unable to add email address": "Unable to add email address", - "Add": "Add", "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "We've sent you an email to verify your address. Please follow the instructions there and then click the button below.", "Email Address": "Email Address", "Remove %(phone)s?": "Remove %(phone)s?", From 557e650a2c2e2eb24584f21bd4175e69cc7500a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 28 Nov 2020 19:37:49 +0100 Subject: [PATCH 0015/2197] Added spell-check-languages setting --- src/settings/Settings.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 31e133be72..409cd293d2 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -402,6 +402,10 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, default: "en", }, + "spell-check-languages": { + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, + default: [], + }, "breadcrumb_rooms": { // not really a setting supportedLevels: [SettingLevel.ACCOUNT], From 43daec03e24820a485f97da0b5cb0311f137e729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 28 Nov 2020 19:38:52 +0100 Subject: [PATCH 0016/2197] Added setSpellCheckLanguages() method --- src/languageHandler.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index b61f57d4b3..9b9e304294 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -346,6 +346,13 @@ export function setLanguage(preferredLangs: string | string[]) { }); } +export function setSpellCheckLanguages(preferredLangs: string[]) { + const plaf = PlatformPeg.get(); + if (plaf) { + plaf.setLanguage(preferredLangs); + } +} + export function getAllLanguagesFromJson() { return getLangsJson().then((langsObject) => { const langs = []; From 5e4f9907cf87e02b41791ce729b38d7474dcbf01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 28 Nov 2020 19:39:09 +0100 Subject: [PATCH 0017/2197] Added persistance --- .../views/settings/tabs/user/GeneralUserSettingsTab.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 6d04d83047..585f4fd5b7 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -50,7 +50,7 @@ export default class GeneralUserSettingsTab extends React.Component { this.state = { language: languageHandler.getCurrentLanguage(), - spellCheckLanguages: [], + spellCheckLanguages: SettingsStore.getValue("spell-check-languages", null, /*excludeDefault=*/true), haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()), serverSupportsSeparateAddAndBind: null, idServerHasUnsignedTerms: false, @@ -185,7 +185,9 @@ export default class GeneralUserSettingsTab extends React.Component { }; _onSpellCheckLanguagesChange = (languages) => { + SettingsStore.setValue("spell-check-languages", null, SettingLevel.DEVICE, languages); this.setState({spellCheckLanguages: languages}) + PlatformPeg.get().reload(); }; _onPasswordChangeError = (err) => { From f0bbed0c44270f8411c0ce0f4ee0cf08142a1c1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 29 Nov 2020 12:17:07 +0100 Subject: [PATCH 0018/2197] Allow default value --- .../views/settings/tabs/user/GeneralUserSettingsTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 585f4fd5b7..68a16463b0 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -50,7 +50,7 @@ export default class GeneralUserSettingsTab extends React.Component { this.state = { language: languageHandler.getCurrentLanguage(), - spellCheckLanguages: SettingsStore.getValue("spell-check-languages", null, /*excludeDefault=*/true), + spellCheckLanguages: SettingsStore.getValue("spell-check-languages", null, false), haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()), serverSupportsSeparateAddAndBind: null, idServerHasUnsignedTerms: false, From 8f40cd39fda1ee83ec6b177ba7935f913ed5a45b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 29 Nov 2020 12:29:05 +0100 Subject: [PATCH 0019/2197] Added styling --- res/css/_components.scss | 1 + .../views/settings/_SpellCheckLanguages.scss | 36 +++++++++++++++++++ .../views/settings/SpellCheckSettings.tsx | 9 +++-- .../tabs/user/GeneralUserSettingsTab.js | 7 ++-- 4 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 res/css/views/settings/_SpellCheckLanguages.scss diff --git a/res/css/_components.scss b/res/css/_components.scss index 445ed70ff4..1eb4b91a31 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -207,6 +207,7 @@ @import "./views/settings/_DevicesPanel.scss"; @import "./views/settings/_E2eAdvancedPanel.scss"; @import "./views/settings/_EmailAddresses.scss"; +@import "./views/settings/_SpellCheckLanguages.scss"; @import "./views/settings/_IntegrationManager.scss"; @import "./views/settings/_Notifications.scss"; @import "./views/settings/_PhoneNumbers.scss"; diff --git a/res/css/views/settings/_SpellCheckLanguages.scss b/res/css/views/settings/_SpellCheckLanguages.scss new file mode 100644 index 0000000000..734f669f0e --- /dev/null +++ b/res/css/views/settings/_SpellCheckLanguages.scss @@ -0,0 +1,36 @@ +/* +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. +*/ + +.mx_ExistingSpellCheckLanguage { + display: flex; + align-items: center; + margin-bottom: 5px; +} + +.mx_ExistingSpellCheckLanguage_language { + flex: 1; + margin-right: 10px; +} + +.mx_GeneralUserSettingsTab_spellCheckLanguageInput { + margin-top: 1em; + margin-bottom: 1em; +} + +.mx_SpellCheckLanguages { + @mixin mx_Settings_fullWidthField; +} \ No newline at end of file diff --git a/src/components/views/settings/SpellCheckSettings.tsx b/src/components/views/settings/SpellCheckSettings.tsx index 1bdcd882c9..befd98112e 100644 --- a/src/components/views/settings/SpellCheckSettings.tsx +++ b/src/components/views/settings/SpellCheckSettings.tsx @@ -43,8 +43,8 @@ export class ExistingSpellCheckLanguage extends React.Component - {this.props.language} +
+ {this.props.language} {_t("Remove")} @@ -96,10 +96,9 @@ export default class SpellCheckLanguages extends React.Component +
{existingSpellCheckLanguages} -
+ diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 68a16463b0..258ff6d318 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -313,11 +313,10 @@ export default class GeneralUserSettingsTab extends React.Component { _renderSpellCheckSection() { return ( -
+
{_t("Spell checking")} - +
); } From 7609f2004e6a004c22e0da189f58cee823bf4468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 29 Nov 2020 13:22:50 +0100 Subject: [PATCH 0020/2197] Added newline to end --- res/css/views/settings/_SpellCheckLanguages.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/settings/_SpellCheckLanguages.scss b/res/css/views/settings/_SpellCheckLanguages.scss index 734f669f0e..ddfa0bf9e0 100644 --- a/res/css/views/settings/_SpellCheckLanguages.scss +++ b/res/css/views/settings/_SpellCheckLanguages.scss @@ -33,4 +33,4 @@ limitations under the License. .mx_SpellCheckLanguages { @mixin mx_Settings_fullWidthField; -} \ No newline at end of file +} From ead00dcdede9e6a24904599baba39bc91de0681a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 29 Nov 2020 14:46:09 +0100 Subject: [PATCH 0021/2197] Set spell-check languages without reloading --- .../views/settings/tabs/user/GeneralUserSettingsTab.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 258ff6d318..ad7e04d677 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -187,7 +187,11 @@ export default class GeneralUserSettingsTab extends React.Component { _onSpellCheckLanguagesChange = (languages) => { SettingsStore.setValue("spell-check-languages", null, SettingLevel.DEVICE, languages); this.setState({spellCheckLanguages: languages}) - PlatformPeg.get().reload(); + + const plaf = PlatformPeg.get(); + if (plaf) { + plaf.setLanguage(languages); + } }; _onPasswordChangeError = (err) => { From 38080c5b2bccafb7dae5c9737e69ad7f295f1d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 29 Nov 2020 20:46:47 +0100 Subject: [PATCH 0022/2197] Added getAvailableSpellCheckLanguages() methods --- src/BasePlatform.ts | 4 ++++ src/languageHandler.tsx | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 0a1f06f0b3..9ac35092a7 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -237,6 +237,10 @@ export default abstract class BasePlatform { setLanguage(preferredLangs: string[]) {} + getAvailableSpellCheckLanguages(): Promise | null { + return null; + } + protected getSSOCallbackUrl(fragmentAfterLogin: string): URL { const url = new URL(window.location.href); url.hash = fragmentAfterLogin || ""; diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 9b9e304294..b827e83ded 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -353,6 +353,11 @@ export function setSpellCheckLanguages(preferredLangs: string[]) { } } +export async function getAvailableSpellCheckLanguages(): Promise { + const plaf = PlatformPeg.get(); + return plaf.getAvailableSpellCheckLanguages(); +} + export function getAllLanguagesFromJson() { return getLangsJson().then((langsObject) => { const langs = []; From 5d9f5ba979d3fc89bf6eec5ad2f319bea7168aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 30 Nov 2020 08:35:51 +0100 Subject: [PATCH 0023/2197] Fix indentation --- src/components/views/elements/LanguageDropdown.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/LanguageDropdown.js b/src/components/views/elements/LanguageDropdown.js index e37109caff..03ec456af5 100644 --- a/src/components/views/elements/LanguageDropdown.js +++ b/src/components/views/elements/LanguageDropdown.js @@ -100,10 +100,10 @@ export default class LanguageDropdown extends React.Component { let language = SettingsStore.getValue("language", null, /*excludeDefault:*/true); let value = null; if (language) { - value = this.props.value || language; + value = this.props.value || language; } else { - language = navigator.language || navigator.userLanguage; - value = this.props.value || language; + language = navigator.language || navigator.userLanguage; + value = this.props.value || language; } return Date: Tue, 1 Dec 2020 16:59:02 +0100 Subject: [PATCH 0024/2197] Change label --- .../views/settings/tabs/user/GeneralUserSettingsTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index ad7e04d677..8d06ea3b36 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -318,7 +318,7 @@ export default class GeneralUserSettingsTab extends React.Component { _renderSpellCheckSection() { return (
- {_t("Spell checking")} + {_t("Spell check dictionaries")}
From cf61d50df40614a45c36c0d9886c3583ca69513e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 1 Dec 2020 16:59:21 +0100 Subject: [PATCH 0025/2197] Added SpellCheckLanguagesDropdown --- .../elements/SpellCheckLanguagesDropdown.tsx | 125 ++++++++++++++++++ .../views/settings/SpellCheckSettings.tsx | 4 +- 2 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 src/components/views/elements/SpellCheckLanguagesDropdown.tsx diff --git a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx new file mode 100644 index 0000000000..db158fa3dd --- /dev/null +++ b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx @@ -0,0 +1,125 @@ +/* +Copyright 2017 Marcel Radzio (MTRNord) +Copyright 2017 Vector Creations 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. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; + +import Dropdown from "../../views/elements/Dropdown" +import PlatformPeg from "../../../PlatformPeg"; +import * as sdk from '../../../index'; +import * as languageHandler from '../../../languageHandler'; +import SettingsStore from "../../../settings/SettingsStore"; +import { _t } from "../../../languageHandler"; + +function languageMatchesSearchQuery(query, language) { + if (language.label.toUpperCase().includes(query.toUpperCase())) return true; + if (language.value.toUpperCase() === query.toUpperCase()) return true; + return false; +} + +interface SpellCheckLanguagesDropdownIProps { + className: string, + value: string, + onOptionChange(language: string), +}; + +interface SpellCheckLanguagesDropdownIState { + searchQuery: string, + languages: any, +} + +export default class SpellCheckLanguagesDropdown extends React.Component { + constructor(props) { + super(props); + this._onSearchChange = this._onSearchChange.bind(this); + + this.state = { + searchQuery: '', + languages: null, + }; + } + + componentDidMount() { + languageHandler.getAvailableSpellCheckLanguages().then((languages) => { + languages.sort(function(a, b) { + if (a < b) return -1; + if (a > b) return 1; + return 0; + }); + var langs = []; + languages.forEach((language) => { + langs.push({ + label: language, + value: language, + }) + }) + this.setState({languages: langs}); + }).catch((e) => { + this.setState({languages: ['en']}); + }); + } + + _onSearchChange(search) { + this.setState({ + searchQuery: search, + }); + } + + render() { + if (this.state.languages === null) { + const Spinner = sdk.getComponent('elements.Spinner'); + return ; + } + + let displayedLanguages; + if (this.state.searchQuery) { + displayedLanguages = this.state.languages.filter((lang) => { + return languageMatchesSearchQuery(this.state.searchQuery, lang); + }); + } else { + displayedLanguages = this.state.languages; + } + + const options = displayedLanguages.map((language) => { + return
+ { language.label } +
; + }); + + // default value here too, otherwise we need to handle null / undefined; + // values between mounting and the initial value propgating + let language = SettingsStore.getValue("language", null, /*excludeDefault:*/true); + let value = null; + if (language) { + value = this.props.value || language; + } else { + language = navigator.language || navigator.userLanguage; + value = this.props.value || language; + } + + return + { options } + ; + } +} diff --git a/src/components/views/settings/SpellCheckSettings.tsx b/src/components/views/settings/SpellCheckSettings.tsx index befd98112e..37476d5f34 100644 --- a/src/components/views/settings/SpellCheckSettings.tsx +++ b/src/components/views/settings/SpellCheckSettings.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React from 'react'; -import LanguageDropdown from "../../../components/views/elements/LanguageDropdown"; +import SpellCheckLanguagesDropdown from "../../../components/views/elements/SpellCheckLanguagesDropdown"; import AccessibleButton from "../../../components/views/elements/AccessibleButton"; import {_t} from "../../../languageHandler"; @@ -99,7 +99,7 @@ export default class SpellCheckLanguages extends React.Component {existingSpellCheckLanguages} - {addButton} From a6d6af1a937fb6bc6cec2e320fbff453bef3c680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 1 Dec 2020 17:19:45 +0100 Subject: [PATCH 0026/2197] Added defaults --- src/settings/Settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 409cd293d2..c83dbab897 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -404,7 +404,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "spell-check-languages": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, - default: [], + default: ["en"], }, "breadcrumb_rooms": { // not really a setting From e9203d75715dbd6a677849dbb83a3d4706b2e6e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 1 Dec 2020 17:21:23 +0100 Subject: [PATCH 0027/2197] Removed unnecessary imports --- src/components/views/elements/SpellCheckLanguagesDropdown.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx index db158fa3dd..5e0fe3132c 100644 --- a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx +++ b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx @@ -16,10 +16,8 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import Dropdown from "../../views/elements/Dropdown" -import PlatformPeg from "../../../PlatformPeg"; import * as sdk from '../../../index'; import * as languageHandler from '../../../languageHandler'; import SettingsStore from "../../../settings/SettingsStore"; From 8287f197f40869941d402e45da87c88d19514545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 1 Dec 2020 19:49:31 +0100 Subject: [PATCH 0028/2197] Fix i18n --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 282c1ce686..9ccd0e1e75 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1149,7 +1149,7 @@ "Set a new account password...": "Set a new account password...", "Account": "Account", "Language and region": "Language and region", - "Spell checking": "Spell checking", + "Spell check dictionaries": "Spell check dictionaries", "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.", "Account management": "Account management", "Deactivating your account is a permanent action - be careful!": "Deactivating your account is a permanent action - be careful!", From 44a363f188fb95927fff942b4c6b5a3914dbe31d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 1 Dec 2020 20:16:48 +0100 Subject: [PATCH 0029/2197] Fix default value --- src/settings/Settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index c83dbab897..3540767a99 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -404,7 +404,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { }, "spell-check-languages": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, - default: ["en"], + default: ["en-US"], }, "breadcrumb_rooms": { // not really a setting From 3c2bb6e4f6d19e337d902613adbddf42fcba2f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 1 Dec 2020 20:17:24 +0100 Subject: [PATCH 0030/2197] Cleanup --- src/BasePlatform.ts | 2 ++ .../views/settings/tabs/user/GeneralUserSettingsTab.js | 5 +---- src/languageHandler.tsx | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 9ac35092a7..2af2ea51c5 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -237,6 +237,8 @@ export default abstract class BasePlatform { setLanguage(preferredLangs: string[]) {} + setSpellCheckLanguages(preferredLangs: string[]) {} + getAvailableSpellCheckLanguages(): Promise | null { return null; } diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 8d06ea3b36..6ed887d749 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -188,10 +188,7 @@ export default class GeneralUserSettingsTab extends React.Component { SettingsStore.setValue("spell-check-languages", null, SettingLevel.DEVICE, languages); this.setState({spellCheckLanguages: languages}) - const plaf = PlatformPeg.get(); - if (plaf) { - plaf.setLanguage(languages); - } + languageHandler.setSpellCheckLanguages(languages); }; _onPasswordChangeError = (err) => { diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index b827e83ded..38d3c8347a 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -349,7 +349,7 @@ export function setLanguage(preferredLangs: string | string[]) { export function setSpellCheckLanguages(preferredLangs: string[]) { const plaf = PlatformPeg.get(); if (plaf) { - plaf.setLanguage(preferredLangs); + plaf.setSpellCheckLanguages(preferredLangs); } } From db5bc0cb7ade92fc056283af639e3c782d384e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 1 Dec 2020 20:36:25 +0100 Subject: [PATCH 0031/2197] Fix formatting --- .../elements/SpellCheckLanguagesDropdown.tsx | 7 ++++--- .../views/settings/SpellCheckSettings.tsx | 17 +++++++++-------- .../tabs/user/GeneralUserSettingsTab.js | 6 +++--- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx index 5e0fe3132c..53c3f310b7 100644 --- a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx +++ b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx @@ -33,14 +33,15 @@ interface SpellCheckLanguagesDropdownIProps { className: string, value: string, onOptionChange(language: string), -}; +} interface SpellCheckLanguagesDropdownIState { searchQuery: string, languages: any, } -export default class SpellCheckLanguagesDropdown extends React.Component { +export default class SpellCheckLanguagesDropdown extends React.Component { constructor(props) { super(props); this._onSearchChange = this._onSearchChange.bind(this); @@ -58,7 +59,7 @@ export default class SpellCheckLanguagesDropdown extends React.Component b) return 1; return 0; }); - var langs = []; + const langs = []; languages.forEach((language) => { langs.push({ label: language, diff --git a/src/components/views/settings/SpellCheckSettings.tsx b/src/components/views/settings/SpellCheckSettings.tsx index 37476d5f34..bfe0774570 100644 --- a/src/components/views/settings/SpellCheckSettings.tsx +++ b/src/components/views/settings/SpellCheckSettings.tsx @@ -22,12 +22,12 @@ import {_t} from "../../../languageHandler"; interface ExistingSpellCheckLanguageIProps { language: string, onRemoved(language: string), -}; +} interface SpellCheckLanguagesIProps { languages: Array, onLanguagesChange(languages: Array), -}; +} interface SpellCheckLanguagesIState { newLanguage: string, @@ -71,7 +71,7 @@ export default class SpellCheckLanguages extends React.Component; }); - let addButton = ( + const addButton = ( {_t("Add")} @@ -99,12 +99,13 @@ export default class SpellCheckLanguages extends React.Component {existingSpellCheckLanguages} - + {addButton}
); - }; + } } diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 6ed887d749..95a8abbb24 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -22,7 +22,7 @@ import ProfileSettings from "../../ProfileSettings"; import * as languageHandler from "../../../../../languageHandler"; import SettingsStore from "../../../../../settings/SettingsStore"; import LanguageDropdown from "../../../elements/LanguageDropdown"; -import SpellCheckSettings from "../../SpellCheckSettings" +import SpellCheckSettings from "../../SpellCheckSettings"; import AccessibleButton from "../../../elements/AccessibleButton"; import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog"; import PropTypes from "prop-types"; @@ -186,8 +186,8 @@ export default class GeneralUserSettingsTab extends React.Component { _onSpellCheckLanguagesChange = (languages) => { SettingsStore.setValue("spell-check-languages", null, SettingLevel.DEVICE, languages); - this.setState({spellCheckLanguages: languages}) - + this.setState({spellCheckLanguages: languages}); + languageHandler.setSpellCheckLanguages(languages); }; From bab541a652e402c1aede8caa00b22b13a2adb0c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 2 Dec 2020 20:14:58 +0100 Subject: [PATCH 0032/2197] Hide spell-check settings if not using Electron --- src/BasePlatform.ts | 8 ++++++++ .../views/settings/tabs/user/GeneralUserSettingsTab.js | 5 ++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 2af2ea51c5..54d15675cb 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -128,6 +128,14 @@ export default abstract class BasePlatform { hideUpdateToast(); } + /** + * Return true if platform supports multi-language + * spell-checking, otherwise false. + */ + supportsMultiLanguageSpellCheck(): boolean { + return false; + } + /** * Returns true if the platform supports displaying * notifications, otherwise false. diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 95a8abbb24..4d1210dc40 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -400,6 +400,9 @@ export default class GeneralUserSettingsTab extends React.Component { } render() { + const plaf = PlatformPeg.get(); + const supportsMultiLanguageSpellCheck = plaf.supportsMultiLanguageSpellCheck() ? true : false; + const discoWarning = this.state.requiredPolicyInfo.hasTerms ? Date: Thu, 3 Dec 2020 11:50:08 +0100 Subject: [PATCH 0033/2197] Simplifie --- .../views/settings/tabs/user/GeneralUserSettingsTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 4d1210dc40..febbcc8e36 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -401,7 +401,7 @@ export default class GeneralUserSettingsTab extends React.Component { render() { const plaf = PlatformPeg.get(); - const supportsMultiLanguageSpellCheck = plaf.supportsMultiLanguageSpellCheck() ? true : false; + const supportsMultiLanguageSpellCheck = plaf.supportsMultiLanguageSpellCheck(); const discoWarning = this.state.requiredPolicyInfo.hasTerms ? Date: Thu, 3 Dec 2020 11:50:20 +0100 Subject: [PATCH 0034/2197] Added in if statement --- src/languageHandler.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 38d3c8347a..985719fce7 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -355,7 +355,9 @@ export function setSpellCheckLanguages(preferredLangs: string[]) { export async function getAvailableSpellCheckLanguages(): Promise { const plaf = PlatformPeg.get(); - return plaf.getAvailableSpellCheckLanguages(); + if (plaf) { + return plaf.getAvailableSpellCheckLanguages(); + } } export function getAllLanguagesFromJson() { From 89bc4435945bfb207355cf5e5e290925f7d7f7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 16 Dec 2020 16:02:27 +0100 Subject: [PATCH 0035/2197] Fix file drop UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/structures/_MainSplit.scss | 2 +- res/css/structures/_RoomView.scss | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index ad1656efbb..3de68b000d 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -22,7 +22,7 @@ limitations under the License. } .mx_MainSplit > .mx_RightPanel_ResizeWrapper { - padding: 5px; + padding: 0 5px 5px 5px; // margin left to not allow the handle to not encroach on the space for the scrollbar margin-left: 8px; height: calc(100vh - 51px); // height of .mx_RoomHeader.light-panel diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 572c7166d2..0a70b027ae 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -23,24 +23,21 @@ limitations under the License. .mx_RoomView_fileDropTarget { min-width: 0px; width: 100%; + height: 100%; + + margin-left: 6.25px; + font-size: $font-18px; text-align: center; pointer-events: none; - padding-left: 12px; - padding-right: 12px; - margin-left: -12px; - border-top-left-radius: 10px; border-top-right-radius: 10px; background-color: $droptarget-bg-color; - border: 2px #e1dddd solid; - border-bottom: none; + position: absolute; - top: 52px; - bottom: 0px; z-index: 3000; } From 41e2ffdf0df43104ef171b690b344a6e22b286f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 16 Dec 2020 19:51:49 +0100 Subject: [PATCH 0036/2197] Added background MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/structures/_RoomView.scss | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 0a70b027ae..9292a400bc 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -39,13 +39,19 @@ limitations under the License. position: absolute; z-index: 3000; + + display: flex; + justify-content: center; + align-items: center; } .mx_RoomView_fileDropTargetLabel { - top: 50%; - width: 100%; - margin-top: -50px; position: absolute; + + border-radius: 10px; + padding: 10px; + + background-color: $menu-bg-color; } .mx_RoomView_auxPanel { From da97d18332c5740499913506b0e059e5b4c7616c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 16 Dec 2020 21:33:05 +0100 Subject: [PATCH 0037/2197] Added a comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/structures/_RoomView.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 9292a400bc..dd63be3a11 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -25,6 +25,7 @@ limitations under the License. width: 100%; height: 100%; + // This is an ugly fix for centering this element margin-left: 6.25px; font-size: $font-18px; From dcb30b72b0ed1adc6fb075ee9cc26ca0338177bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 17 Dec 2020 13:25:22 +0100 Subject: [PATCH 0038/2197] Fix left panel resizer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/structures/_MainSplit.scss | 32 ++++++++++++++++---------- res/css/structures/_RoomView.scss | 12 ++++++---- src/components/structures/RoomView.tsx | 28 +++++++++++----------- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index 3de68b000d..6875ef12e0 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -22,22 +22,30 @@ limitations under the License. } .mx_MainSplit > .mx_RightPanel_ResizeWrapper { - padding: 0 5px 5px 5px; - // margin left to not allow the handle to not encroach on the space for the scrollbar - margin-left: 8px; + padding: 0 5px 5px 0px; height: calc(100vh - 51px); // height of .mx_RoomHeader.light-panel + + .mx_RightPanel_ResizeHandle { + width: 9px; + } &:hover .mx_RightPanel_ResizeHandle { - // Need to use important to override element style attributes - // set by re-resizable - top: 50% !important; - transform: translate(0, -50%); + &::before { + position: absolute; + left: 6px; + top: 50%; + transform: translate(0, -50%); - height: 64px !important; // to match width of the ones on roomlist - width: 4px !important; - border-radius: 4px !important; + height: 64px; + width: 4px; + border-radius: 4px; - background-color: $primary-fg-color; - opacity: 0.8; + content: ' '; + + background-color: $primary-fg-color; + opacity: 0.8; + + margin-left: -10px; + } } } diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index dd63be3a11..0a12a86c33 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -25,9 +25,6 @@ limitations under the License. width: 100%; height: 100%; - // This is an ugly fix for centering this element - margin-left: 6.25px; - font-size: $font-18px; text-align: center; @@ -120,16 +117,23 @@ limitations under the License. height: 50px; } -.mx_RoomView_body { +.mx_RoomView_container { position: relative; //for .mx_RoomView_auxPanel_fullHeight display: flex; flex-direction: column; +} + +.mx_RoomView_body { + display: flex; + flex-direction: column; flex: 1; min-width: 0; .mx_RoomView_messagePanel, .mx_RoomView_messagePanelSpinner, .mx_RoomView_messagePanelSearchSpinner { order: 2; } + + margin-right: 10px; } .mx_RoomView_body .mx_RoomView_timeline { diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 0ee847fbc9..3d62c06e4b 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2003,22 +2003,24 @@ export default class RoomView extends React.Component { appsShown={this.state.showApps} /> -
+
{auxPanel} -
- {topUnreadMessagesBar} - {jumpToBottom} - {messagePanel} - {searchResultsPanel} -
-
-
-
- {statusBar} +
+
+ {topUnreadMessagesBar} + {jumpToBottom} + {messagePanel} + {searchResultsPanel}
+
+
+
+ {statusBar} +
+
+ {previewBar} + {messageComposer}
- {previewBar} - {messageComposer}
From e70dee08d0ea7b303a51fb807929376b2dad79dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 17 Dec 2020 19:50:59 +0100 Subject: [PATCH 0039/2197] Fix flickering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/RoomView.tsx | 42 ++++++++++++++++++++------ 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 3d62c06e4b..67f9663597 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -187,6 +187,7 @@ export interface IState { rejecting?: boolean; rejectError?: Error; hasPinnedWidgets?: boolean; + dragCounter: number; } export default class RoomView extends React.Component { @@ -237,6 +238,7 @@ export default class RoomView extends React.Component { canReply: false, useIRCLayout: SettingsStore.getValue("useIRCLayout"), matrixClientIsReady: this.context && this.context.isInitialSyncComplete(), + dragCounter: 0, }; this.dispatcherRef = dis.register(this.onAction); @@ -525,8 +527,8 @@ export default class RoomView extends React.Component { if (!roomView.ondrop) { roomView.addEventListener('drop', this.onDrop); roomView.addEventListener('dragover', this.onDragOver); - roomView.addEventListener('dragleave', this.onDragLeaveOrEnd); - roomView.addEventListener('dragend', this.onDragLeaveOrEnd); + roomView.addEventListener('dragenter', this.onDragEnter); + roomView.addEventListener('dragleave', this.onDragLeave); } } @@ -1108,6 +1110,31 @@ export default class RoomView extends React.Component { this.updateTopUnreadMessagesBar(); }; + private onDragEnter = ev => { + ev.stopPropagation(); + ev.preventDefault(); + + this.setState({ + dragCounter: this.state.dragCounter + 1, + draggingFile: true, + }); + }; + + private onDragLeave = ev => { + ev.stopPropagation(); + ev.preventDefault(); + + this.setState({ + dragCounter: this.state.dragCounter - 1, + }); + + if (this.state.dragCounter == 0) { + this.setState({ + draggingFile: false, + }); + } + }; + private onDragOver = ev => { ev.stopPropagation(); ev.preventDefault(); @@ -1115,7 +1142,6 @@ export default class RoomView extends React.Component { ev.dataTransfer.dropEffect = 'none'; if (ev.dataTransfer.types.includes("Files") || ev.dataTransfer.types.includes("application/x-moz-file")) { - this.setState({ draggingFile: true }); ev.dataTransfer.dropEffect = 'copy'; } }; @@ -1126,14 +1152,12 @@ export default class RoomView extends React.Component { ContentMessages.sharedInstance().sendContentListToRoom( ev.dataTransfer.files, this.state.room.roomId, this.context, ); - this.setState({ draggingFile: false }); dis.fire(Action.FocusComposer); - }; - private onDragLeaveOrEnd = ev => { - ev.stopPropagation(); - ev.preventDefault(); - this.setState({ draggingFile: false }); + this.setState({ + draggingFile: false, + dragCounter: this.state.dragCounter - 1, + }); }; private injectSticker(url, info, text) { From 044e02b06ad46b417d3aa8fc33f24c1374fdcb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 17 Dec 2020 20:16:53 +0100 Subject: [PATCH 0040/2197] Remove spaces in empty line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/structures/_MainSplit.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index 6875ef12e0..f05f24d0d7 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -24,7 +24,7 @@ limitations under the License. .mx_MainSplit > .mx_RightPanel_ResizeWrapper { padding: 0 5px 5px 0px; height: calc(100vh - 51px); // height of .mx_RoomHeader.light-panel - + .mx_RightPanel_ResizeHandle { width: 9px; } From 365d252d3f0eb64755f502318c95f855a4404f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 17 Dec 2020 20:25:12 +0100 Subject: [PATCH 0041/2197] Fix removing event listeners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/RoomView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 67f9663597..d910940a73 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -572,8 +572,8 @@ export default class RoomView extends React.Component { const roomView = this.roomView.current; roomView.removeEventListener('drop', this.onDrop); roomView.removeEventListener('dragover', this.onDragOver); - roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd); - roomView.removeEventListener('dragend', this.onDragLeaveOrEnd); + roomView.removeEventListener('dragenter', this.onDragEnter); + roomView.removeEventListener('dragleave', this.onDragLeave); } dis.unregister(this.dispatcherRef); if (this.context) { From 5d7e45e6cf85e14f4143923f7e29642f97965fc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 17 Dec 2020 20:29:33 +0100 Subject: [PATCH 0042/2197] Added dragCounter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/contexts/RoomContext.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts index 082dcc4e6b..1b9097e337 100644 --- a/src/contexts/RoomContext.ts +++ b/src/contexts/RoomContext.ts @@ -42,6 +42,7 @@ const RoomContext = createContext({ canReply: false, useIRCLayout: false, matrixClientIsReady: false, + dragCounter: 0, }); RoomContext.displayName = "RoomContext"; export default RoomContext; From 8aabe1f33077ef95f4414c32b3b625b4297369ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 19 Dec 2020 09:26:09 +0100 Subject: [PATCH 0043/2197] Reorganized elements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_ImageView.scss | 114 +++++---------------- src/components/views/elements/ImageView.js | 60 +++++------ 2 files changed, 54 insertions(+), 120 deletions(-) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index 0a4ed2a194..77fa597d66 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -22,45 +22,46 @@ limitations under the License. display: flex; width: 100%; height: 100%; - align-items: center; -} - -.mx_ImageView_lhs { - order: 1; - flex: 1 1 10%; - min-width: 60px; - // background-color: #080; - // height: 20px; } .mx_ImageView_content { - order: 2; - /* min-width hack needed for FF */ - min-width: 0px; - height: 90%; - flex: 15 15 0; + width: 100%; + height: 100%; + + display: flex; + flex-direction: column; +} + +.mx_ImageView_imageBox { + overflow: auto; + margin: 0 50px 50px 50px; + flex: 1; display: flex; - align-items: center; - justify-content: center; } .mx_ImageView_content img { - max-width: 100%; + flex: 1; + //max-width: 100%; /* XXX: max-height interacts badly with flex on Chrome and doesn't relayout properly until you refresh */ - max-height: 100%; + //max-height: 100%; /* object-fit hack needed for Chrome due to Chrome not re-laying-out until you refresh */ object-fit: contain; /* background-image: url('$(res)/img/trans.png'); */ pointer-events: all; } -.mx_ImageView_labelWrapper { - position: absolute; - top: 0px; - right: 0px; - height: 100%; - overflow: auto; - pointer-events: all; +.mx_ImageView_panel { + display: flex; + justify-content: space-between; + padding: 50px; +} + +.mx_ImageView_toolbar { + display: flex; +} + +.mx_ImageView_button { + padding: 5px; } .mx_ImageView_label { @@ -68,39 +69,11 @@ limitations under the License. display: flex; justify-content: center; flex-direction: column; - padding-left: 30px; - padding-right: 30px; min-height: 100%; max-width: 240px; color: $lightbox-fg-color; } -.mx_ImageView_cancel { - position: absolute; - // hack for mx_Dialog having a top padding of 40px - top: 40px; - right: 0px; - padding-top: 35px; - padding-right: 35px; - cursor: pointer; -} - -.mx_ImageView_rotateClockwise { - position: absolute; - top: 40px; - right: 70px; - padding-top: 35px; - cursor: pointer; -} - -.mx_ImageView_rotateCounterClockwise { - position: absolute; - top: 40px; - right: 105px; - padding-top: 35px; - cursor: pointer; -} - .mx_ImageView_name { font-size: $font-18px; margin-bottom: 6px; @@ -112,41 +85,6 @@ limitations under the License. opacity: 0.5; } -.mx_ImageView_download { - display: table; - margin-top: 24px; - margin-bottom: 6px; - border-radius: 5px; - background-color: $lightbox-bg-color; - font-size: $font-14px; - padding: 9px; - border: 1px solid $lightbox-border-color; -} - .mx_ImageView_size { font-size: $font-11px; } - -.mx_ImageView_link { - color: $lightbox-fg-color !important; - text-decoration: none !important; -} - -.mx_ImageView_button { - font-size: $font-15px; - opacity: 0.5; - margin-top: 18px; - cursor: pointer; -} - -.mx_ImageView_shim { - height: 30px; -} - -.mx_ImageView_rhs { - order: 3; - flex: 1 1 10%; - min-width: 300px; - // background-color: #800; - // height: 20px; -} diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index e39075cedc..a66d2e2e6f 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -153,7 +153,7 @@ export default class ImageView extends React.Component { let mayRedact = false; const showEventMeta = !!this.props.mxEvent; - let eventMeta; + let metadata; if (showEventMeta) { // Figure out the sender, defaulting to mxid let sender = this.props.mxEvent.getSender(); @@ -165,7 +165,7 @@ export default class ImageView extends React.Component { if (member) sender = member.name; } - eventMeta = (
+ metadata = (
{ _t('Uploaded on %(date)s by %(user)s', { date: formatDate(new Date(this.props.mxEvent.getTs())), user: sender, @@ -173,11 +173,13 @@ export default class ImageView extends React.Component {
); } - let eventRedact; + let redactButton; if (mayRedact) { - eventRedact = (
- { _t('Remove') } -
); + redactButton = ( + + { + + ); } const rotationDegrees = this.state.rotationDegrees; @@ -192,40 +194,34 @@ export default class ImageView extends React.Component { }} className="mx_ImageView" > -
-
- -
+
- - { - - - { - - - { - -
-
{ this.getName() }
- { eventMeta } - -
- { _t('Download this file') }
- { sizeRes } -
+ { metadata } + { sizeRes } +
+
+ + { + + + { + + + { - { eventRedact } -
-
+ { redactButton } + + { +
-
-
+
+ +
); From 2040815f661be9c832a629022f01764ae99c8c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 19 Dec 2020 10:20:15 +0100 Subject: [PATCH 0044/2197] Implement zooming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.js | 45 +++++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index a66d2e2e6f..0c235d87be 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -46,7 +46,10 @@ export default class ImageView extends React.Component { constructor(props) { super(props); - this.state = { rotationDegrees: 0 }; + this.state = { + rotationDegrees: 0, + zoom: 100, + }; } onKeyDown = (ev) => { @@ -57,6 +60,16 @@ export default class ImageView extends React.Component { } }; + onWheel = (ev) => { + if (ev.ctrlKey) { + ev.stopPropagation(); + ev.preventDefault(); + this.setState({ + zoom: this.state.zoom - ev.deltaY, + }); + } + } + onRedactClick = () => { const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog"); Modal.createTrackedDialog('Confirm Redact Dialog', 'Image View', ConfirmRedactDialog, { @@ -98,6 +111,18 @@ export default class ImageView extends React.Component { this.setState({ rotationDegrees }); }; + zoomIn = () => { + this.setState({ + zoom: this.state.zoom + 10, + }); + }; + + zoomOut = () => { + this.setState({ + zoom: this.state.zoom - 10, + }); + } + render() { /* // In theory max-width: 80%, max-height: 80% on the CSS should work @@ -130,12 +155,13 @@ export default class ImageView extends React.Component { let style = {}; let res; + style = { + width: this.state.zoom + "%", + height: this.state.zoom + "%", + }; + if (this.props.width && this.props.height) { - style = { - width: this.props.width, - height: this.props.height, - }; - res = style.width + "x" + style.height + "px"; + res = this.props.width + "x" + this.props.height + "px"; } let size; @@ -190,6 +216,7 @@ export default class ImageView extends React.Component { returnFocus={true} lockProps={{ onKeyDown: this.onKeyDown, + onWheel: this.onWheel, role: "dialog", }} className="mx_ImageView" @@ -204,6 +231,12 @@ export default class ImageView extends React.Component { { sizeRes }
+ + { + + + { + { From 3c306bc54bb81aea6fa18d3f7204c083a053a0ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 19 Dec 2020 12:54:55 +0100 Subject: [PATCH 0045/2197] Added icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/img/download-white.svg | 95 +++++++++++++++++++++ res/img/feather-customised/trash.custom.svg | 87 +++++++++++++++++-- res/img/minus-white.svg | 64 ++++++++++++++ res/img/plus-white.svg | 73 ++++++++++++++++ res/img/trash-red.svg | 89 +++++++++++++++++++ src/components/views/elements/ImageView.js | 8 +- 6 files changed, 406 insertions(+), 10 deletions(-) create mode 100644 res/img/download-white.svg create mode 100644 res/img/minus-white.svg create mode 100644 res/img/plus-white.svg create mode 100644 res/img/trash-red.svg diff --git a/res/img/download-white.svg b/res/img/download-white.svg new file mode 100644 index 0000000000..5c800b350e --- /dev/null +++ b/res/img/download-white.svg @@ -0,0 +1,95 @@ + + + + + + image/svg+xml + + Fill 75 + + + + + + Fill 75 + Created with Sketch. + + + + + + + + + + + + + diff --git a/res/img/feather-customised/trash.custom.svg b/res/img/feather-customised/trash.custom.svg index dc1985ddb2..589bb0a4e5 100644 --- a/res/img/feather-customised/trash.custom.svg +++ b/res/img/feather-customised/trash.custom.svg @@ -1,7 +1,82 @@ - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/res/img/minus-white.svg b/res/img/minus-white.svg new file mode 100644 index 0000000000..2921f34980 --- /dev/null +++ b/res/img/minus-white.svg @@ -0,0 +1,64 @@ + + + + + + image/svg+xml + + Fill 75 + + + + + + Fill 75 + Created with Sketch. + + + diff --git a/res/img/plus-white.svg b/res/img/plus-white.svg new file mode 100644 index 0000000000..7759ace50a --- /dev/null +++ b/res/img/plus-white.svg @@ -0,0 +1,73 @@ + + + + + + image/svg+xml + + Fill 75 + + + + + + Fill 75 + Created with Sketch. + + + + diff --git a/res/img/trash-red.svg b/res/img/trash-red.svg new file mode 100644 index 0000000000..0b1d201d2e --- /dev/null +++ b/res/img/trash-red.svg @@ -0,0 +1,89 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index 0c235d87be..719a17d3cb 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -203,7 +203,7 @@ export default class ImageView extends React.Component { if (mayRedact) { redactButton = ( - { + { ); } @@ -232,10 +232,10 @@ export default class ImageView extends React.Component {
- { + { - { + { { @@ -244,7 +244,7 @@ export default class ImageView extends React.Component { { - { + { { redactButton } From b9f480a825f8f8279d807aa2d10973a58fef78b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 19 Dec 2020 13:13:34 +0100 Subject: [PATCH 0046/2197] Remove flex property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_ImageView.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index 77fa597d66..5f002c7d7a 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -35,7 +35,6 @@ limitations under the License. .mx_ImageView_imageBox { overflow: auto; margin: 0 50px 50px 50px; - flex: 1; display: flex; } From 2c5f3f31b1bd339c6d40337ddc3bd059fe095edf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 19 Dec 2020 13:30:56 +0100 Subject: [PATCH 0047/2197] Fixed Chromium issues - made listner non-passive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index 719a17d3cb..6dfd1f78ad 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -52,6 +52,14 @@ export default class ImageView extends React.Component { }; } + componentDidMount() { + this.focusLock.addEventListener('wheel', this.onWheel, { passive: false }); + } + + componentWillUnmount() { + this.focusLock.removeEventListener('wheel', this.onWheel); + } + onKeyDown = (ev) => { if (ev.key === Key.ESCAPE) { ev.stopPropagation(); @@ -216,10 +224,10 @@ export default class ImageView extends React.Component { returnFocus={true} lockProps={{ onKeyDown: this.onKeyDown, - onWheel: this.onWheel, role: "dialog", }} className="mx_ImageView" + ref={ref => this.focusLock = ref} >
From 633221f012961024d2e706f7b7ee0c1fb3cb88f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 19 Dec 2020 13:50:21 +0100 Subject: [PATCH 0048/2197] Center image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_ImageView.scss | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index 5f002c7d7a..e6ed3684bb 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -40,13 +40,9 @@ limitations under the License. .mx_ImageView_content img { flex: 1; - //max-width: 100%; - /* XXX: max-height interacts badly with flex on Chrome and doesn't relayout properly until you refresh */ - //max-height: 100%; - /* object-fit hack needed for Chrome due to Chrome not re-laying-out until you refresh */ object-fit: contain; - /* background-image: url('$(res)/img/trans.png'); */ pointer-events: all; + margin: auto; } .mx_ImageView_panel { From ae25ff82169048be25dcb37c8ec680b22dfb805d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 19 Dec 2020 15:54:26 +0100 Subject: [PATCH 0049/2197] Switched to scale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_ImageView.scss | 5 +++-- src/components/views/elements/ImageView.js | 9 ++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index e6ed3684bb..c87cfd1ece 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -36,13 +36,14 @@ limitations under the License. overflow: auto; margin: 0 50px 50px 50px; display: flex; + height: 100%; } .mx_ImageView_content img { - flex: 1; object-fit: contain; pointer-events: all; - margin: auto; + //margin: auto; + //margin: 0 auto 0 auto; } .mx_ImageView_panel { diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index 6dfd1f78ad..a42d957daa 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -160,14 +160,8 @@ export default class ImageView extends React.Component { height: displayHeight }; */ - let style = {}; let res; - style = { - width: this.state.zoom + "%", - height: this.state.zoom + "%", - }; - if (this.props.width && this.props.height) { res = this.props.width + "x" + this.props.height + "px"; } @@ -217,7 +211,8 @@ export default class ImageView extends React.Component { } const rotationDegrees = this.state.rotationDegrees; - const effectiveStyle = {transform: `rotate(${rotationDegrees}deg)`, ...style}; + const zoom = this.state.zoom/100; + const effectiveStyle = {transform: `rotate(${rotationDegrees}deg) scale(${zoom})`}; return ( Date: Sun, 20 Dec 2020 09:51:57 +0100 Subject: [PATCH 0050/2197] Added a comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index a42d957daa..bc1d8b4d53 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -53,6 +53,8 @@ export default class ImageView extends React.Component { } componentDidMount() { + // We have to use addEventListener() because the listener + // needs to be passive in order to work with Chromium this.focusLock.addEventListener('wheel', this.onWheel, { passive: false }); } From be9b68a4dd3d453548b5e1562019998bd28170de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Dec 2020 10:07:03 +0100 Subject: [PATCH 0051/2197] Use height and width properties for scaling again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_ImageView.scss | 11 ++++++----- src/components/views/elements/ImageView.js | 7 +++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index c87cfd1ece..1fcf1bf543 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -33,17 +33,18 @@ limitations under the License. } .mx_ImageView_imageBox { - overflow: auto; - margin: 0 50px 50px 50px; + overflow: scroll; display: flex; - height: 100%; + flex-grow: 1; +} + +.mainImage { + margin: auto; } .mx_ImageView_content img { object-fit: contain; pointer-events: all; - //margin: auto; - //margin: 0 auto 0 auto; } .mx_ImageView_panel { diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index bc1d8b4d53..2feff97fef 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -163,6 +163,10 @@ export default class ImageView extends React.Component { }; */ let res; + const style = { + height: this.state.zoom + "%", + width: this.state.zoom + "%", + }; if (this.props.width && this.props.height) { res = this.props.width + "x" + this.props.height + "px"; @@ -213,8 +217,7 @@ export default class ImageView extends React.Component { } const rotationDegrees = this.state.rotationDegrees; - const zoom = this.state.zoom/100; - const effectiveStyle = {transform: `rotate(${rotationDegrees}deg) scale(${zoom})`}; + const effectiveStyle = {transform: `rotate(${rotationDegrees}deg)`, ...style}; return ( Date: Sun, 20 Dec 2020 12:16:05 +0100 Subject: [PATCH 0052/2197] Added some padding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_ImageView.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index 1fcf1bf543..ec651fcc6a 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -35,6 +35,7 @@ limitations under the License. .mx_ImageView_imageBox { overflow: scroll; display: flex; + padding: 0 50px 50px 50px; flex-grow: 1; } From f9884b1cc75cd7b2cbdb1d15cfa9b9ea364f424c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Dec 2020 17:40:16 +0100 Subject: [PATCH 0053/2197] Implement translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_ImageView.scss | 6 +- src/components/views/elements/ImageView.js | 144 +++++++++++++-------- 2 files changed, 97 insertions(+), 53 deletions(-) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index ec651fcc6a..10553a1c54 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -33,7 +33,7 @@ limitations under the License. } .mx_ImageView_imageBox { - overflow: scroll; + overflow: hidden; display: flex; padding: 0 50px 50px 50px; flex-grow: 1; @@ -41,6 +41,10 @@ limitations under the License. .mainImage { margin: auto; + + &:hover { + cursor: grab; + } } .mx_ImageView_content img { diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index 2feff97fef..8cc55e40bf 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -47,11 +47,19 @@ export default class ImageView extends React.Component { constructor(props) { super(props); this.state = { - rotationDegrees: 0, + rotation: 0, zoom: 100, + translationX: 0, + translationY: 0, + moving: false, }; } + initX = 0; + initY = 0; + lastX = 0; + lastY = 0; + componentDidMount() { // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium @@ -74,8 +82,18 @@ export default class ImageView extends React.Component { if (ev.ctrlKey) { ev.stopPropagation(); ev.preventDefault(); + const newZoom =this.state.zoom - ev.deltaY; + + if (newZoom <= 100) { + this.setState({ + zoom: 100, + translationX: 0, + translationY: 0, + }); + return; + } this.setState({ - zoom: this.state.zoom - ev.deltaY, + zoom: newZoom, }); } } @@ -109,65 +127,72 @@ export default class ImageView extends React.Component { return name; } - rotateCounterClockwise = () => { - const cur = this.state.rotationDegrees; + onRotateCounterClockwiseClick = () => { + const cur = this.state.rotation; const rotationDegrees = (cur - 90) % 360; - this.setState({ rotationDegrees }); + this.setState({ rotation: rotationDegrees }); }; - rotateClockwise = () => { - const cur = this.state.rotationDegrees; + onRotateClockwiseClick = () => { + const cur = this.state.rotation; const rotationDegrees = (cur + 90) % 360; - this.setState({ rotationDegrees }); + this.setState({ rotation: rotationDegrees }); }; - zoomIn = () => { + onZoomInClick = () => { this.setState({ zoom: this.state.zoom + 10, }); }; - zoomOut = () => { + onZoomOutClick = () => { + if (this.state.zoom <= 100) { + this.setState({ + zoom: 100, + translationX: 0, + translationY: 0, + }); + return; + } this.setState({ zoom: this.state.zoom - 10, }); } + onStartMoving = ev => { + ev.stopPropagation(); + ev.preventDefault(); + + if (this.state.zoom <= 100) return false; + + this.setState({moving: true}); + this.initX = ev.pageX - this.lastX; + this.initY = ev.pageY - this.lastY; + } + + onMoving = ev => { + ev.stopPropagation(); + ev.preventDefault(); + + if (!this.state.moving) return false; + + this.lastX = ev.pageX - this.initX; + this.lastY = ev.pageY - this.initY; + this.setState({ + translationX: this.lastX, + translationY: this.lastY, + }); + } + + onEndMoving = ev => { + this.setState({moving: false}); + } + render() { -/* - // In theory max-width: 80%, max-height: 80% on the CSS should work - // but in practice, it doesn't, so do it manually: + let mayRedact = false; + const showEventMeta = !!this.props.mxEvent; - var width = this.props.width || 500; - var height = this.props.height || 500; - - var maxWidth = document.documentElement.clientWidth * 0.8; - var maxHeight = document.documentElement.clientHeight * 0.8; - - var widthFrac = width / maxWidth; - var heightFrac = height / maxHeight; - - var displayWidth; - var displayHeight; - if (widthFrac > heightFrac) { - displayWidth = Math.min(width, maxWidth); - displayHeight = (displayWidth / width) * height; - } else { - displayHeight = Math.min(height, maxHeight); - displayWidth = (displayHeight / height) * width; - } - - var style = { - width: displayWidth, - height: displayHeight - }; -*/ let res; - const style = { - height: this.state.zoom + "%", - width: this.state.zoom + "%", - }; - if (this.props.width && this.props.height) { res = this.props.width + "x" + this.props.height + "px"; } @@ -184,9 +209,6 @@ export default class ImageView extends React.Component { sizeRes = size || res; } - let mayRedact = false; - const showEventMeta = !!this.props.mxEvent; - let metadata; if (showEventMeta) { // Figure out the sender, defaulting to mxid @@ -216,8 +238,16 @@ export default class ImageView extends React.Component { ); } - const rotationDegrees = this.state.rotationDegrees; - const effectiveStyle = {transform: `rotate(${rotationDegrees}deg)`, ...style}; + const rotationDegrees = this.state.rotation + "deg"; + const zoomPercentage = this.state.zoom/100; + const translatePixelsX = this.state.translationX + "px"; + const translatePixelsY = this.state.translationY + "px"; + const style = { + transform: `rotate(${rotationDegrees}) + scale(${zoomPercentage}) + translateX(${translatePixelsX}) + translateY(${translatePixelsY})`, + }; return ( { sizeRes }
- +
From 7dd7aeffedc612827712a0b1ff91a3561f56f016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Dec 2020 18:19:11 +0100 Subject: [PATCH 0054/2197] Remove imageBox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_ImageView.scss | 10 +++------ src/components/views/elements/ImageView.js | 24 ++++++++++------------ 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index 10553a1c54..a2ca9c7927 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -26,21 +26,15 @@ limitations under the License. .mx_ImageView_content { width: 100%; - height: 100%; display: flex; flex-direction: column; -} -.mx_ImageView_imageBox { overflow: hidden; - display: flex; - padding: 0 50px 50px 50px; - flex-grow: 1; } .mainImage { - margin: auto; + //margin: auto; &:hover { cursor: grab; @@ -56,6 +50,8 @@ limitations under the License. display: flex; justify-content: space-between; padding: 50px; + position: absolute; + width: 100%; } .mx_ImageView_toolbar { diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index 8cc55e40bf..9d747463f9 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -290,19 +290,17 @@ export default class ImageView extends React.Component {
-
- -
+
); From 6758734593ad30e9a766e63ccb23921b43c00c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Dec 2020 19:48:24 +0100 Subject: [PATCH 0055/2197] Remove panel element MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_ImageView.scss | 39 +++++++++------- src/components/views/elements/ImageView.js | 54 +++++++++++----------- 2 files changed, 48 insertions(+), 45 deletions(-) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index a2ca9c7927..0153d372fc 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -28,50 +28,55 @@ limitations under the License. width: 100%; display: flex; - flex-direction: column; + justify-content: center; + align-items: center; overflow: hidden; } .mainImage { - //margin: auto; + object-fit: contain; + pointer-events: all; + + max-width: 100vw; + max-height: 90vh; + min-width: 100px; + min-height: 100px; &:hover { cursor: grab; } } -.mx_ImageView_content img { - object-fit: contain; +.mx_ImageView_panel { + position: absolute; + z-index: 1000; + align-self: flex-start; pointer-events: all; } -.mx_ImageView_panel { - display: flex; - justify-content: space-between; - padding: 50px; - position: absolute; - width: 100%; -} - .mx_ImageView_toolbar { + right: 0; + padding: 50px 50px 0 0; display: flex; -} - -.mx_ImageView_button { - padding: 5px; + } .mx_ImageView_label { + left: 0; + padding: 50px 0 0 50px; text-align: left; display: flex; justify-content: center; flex-direction: column; - min-height: 100%; max-width: 240px; color: $lightbox-fg-color; } +.mx_ImageView_button { + padding: 5px; +} + .mx_ImageView_name { font-size: $font-18px; margin-bottom: 6px; diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index 9d747463f9..86c6cb8ad5 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -260,35 +260,33 @@ export default class ImageView extends React.Component { ref={ref => this.focusLock = ref} >
-
-
-
- { this.getName() } -
- { metadata } - { sizeRes } -
-
- - { - - - { - - - { - - - { - - - { - - { redactButton } - - { - +
+
+ { this.getName() }
+ { metadata } + { sizeRes } +
+
+ + { + + + { + + + { + + + { + + + { + + { redactButton } + + { +
Date: Sun, 20 Dec 2020 20:00:11 +0100 Subject: [PATCH 0056/2197] Fixed translation issue while the image is rotated MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index 86c6cb8ad5..0db907003c 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -242,11 +242,15 @@ export default class ImageView extends React.Component { const zoomPercentage = this.state.zoom/100; const translatePixelsX = this.state.translationX + "px"; const translatePixelsY = this.state.translationY + "px"; + /* The order of the values is important! + * First, we translate and only then we rotate, otherwise + * we would apply the translation to an already rotated + * image causing it translate in the wrong direction. */ const style = { - transform: `rotate(${rotationDegrees}) + transform: `translateX(${translatePixelsX}) + translateY(${translatePixelsY}) scale(${zoomPercentage}) - translateX(${translatePixelsX}) - translateY(${translatePixelsY})`, + rotate(${rotationDegrees})`, }; return ( From 776b0e8198e1179ee5f1b7ee9edb6e07d6c14ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Dec 2020 20:05:27 +0100 Subject: [PATCH 0057/2197] Change comment styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index 0db907003c..b6fa9ef35b 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -61,8 +61,8 @@ export default class ImageView extends React.Component { lastY = 0; componentDidMount() { - // We have to use addEventListener() because the listener - // needs to be passive in order to work with Chromium + /* We have to use addEventListener() because the listener + * needs to be passive in order to work with Chromium */ this.focusLock.addEventListener('wheel', this.onWheel, { passive: false }); } From f0abd52130b6f70df4bef144d26afdda05819bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Dec 2020 20:09:01 +0100 Subject: [PATCH 0058/2197] Remove the need to press ctrl while zooming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.js | 24 ++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index b6fa9ef35b..d40708af4c 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -79,23 +79,21 @@ export default class ImageView extends React.Component { }; onWheel = (ev) => { - if (ev.ctrlKey) { - ev.stopPropagation(); - ev.preventDefault(); - const newZoom =this.state.zoom - ev.deltaY; + ev.stopPropagation(); + ev.preventDefault(); + const newZoom =this.state.zoom - ev.deltaY; - if (newZoom <= 100) { - this.setState({ - zoom: 100, - translationX: 0, - translationY: 0, - }); - return; - } + if (newZoom <= 100) { this.setState({ - zoom: newZoom, + zoom: 100, + translationX: 0, + translationY: 0, }); + return; } + this.setState({ + zoom: newZoom, + }); } onRedactClick = () => { From cbfa6c5f94bc839ce158ee994a2efda81d024508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Dec 2020 20:15:25 +0100 Subject: [PATCH 0059/2197] Fix some sizing issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_ImageView.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index 0153d372fc..2c3d881394 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -39,7 +39,7 @@ limitations under the License. pointer-events: all; max-width: 100vw; - max-height: 90vh; + max-height: 80vh; min-width: 100px; min-height: 100px; From 61c5e7e8f17356c849fda70793cd6e5c86f4342c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Dec 2020 20:15:39 +0100 Subject: [PATCH 0060/2197] Reorder label items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index d40708af4c..b343ec37c2 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -266,8 +266,8 @@ export default class ImageView extends React.Component {
{ this.getName() }
- { metadata } { sizeRes } + { metadata }
From 0dff150bb271985133b551c0b99639fc68461cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Dec 2020 20:18:21 +0100 Subject: [PATCH 0061/2197] Fix some more sizing issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_ImageView.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index 2c3d881394..1b38021267 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -39,7 +39,7 @@ limitations under the License. pointer-events: all; max-width: 100vw; - max-height: 80vh; + max-height: 70vh; min-width: 100px; min-height: 100px; From 096fb33397e29e65cf0ec5dea77eb99b8b8c690e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Dec 2020 20:19:32 +0100 Subject: [PATCH 0062/2197] Always allow moving MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index b343ec37c2..12112db48a 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -161,8 +161,6 @@ export default class ImageView extends React.Component { ev.stopPropagation(); ev.preventDefault(); - if (this.state.zoom <= 100) return false; - this.setState({moving: true}); this.initX = ev.pageX - this.lastX; this.initY = ev.pageY - this.lastY; From 3d62138cbd67fa7beefa9770abf954a35c8286f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Dec 2020 20:28:19 +0100 Subject: [PATCH 0063/2197] Set max zoom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index 12112db48a..82f3386e8d 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -91,6 +91,11 @@ export default class ImageView extends React.Component { }); return; } + if (newZoom >= 300) { + this.setState({zoom: 300}); + return; + } + this.setState({ zoom: newZoom, }); @@ -138,6 +143,11 @@ export default class ImageView extends React.Component { }; onZoomInClick = () => { + if (this.state.zoom >= 300) { + this.setState({zoom: 300}); + return; + } + this.setState({ zoom: this.state.zoom + 10, }); From f771b7ac98764135691f64fbddefded6cdb06621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Dec 2020 20:37:31 +0100 Subject: [PATCH 0064/2197] Added zoom button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/img/zoom-white.svg | 59 ++++++++++++++++++++++ src/components/views/elements/ImageView.js | 33 +++++++++--- 2 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 res/img/zoom-white.svg diff --git a/res/img/zoom-white.svg b/res/img/zoom-white.svg new file mode 100644 index 0000000000..19379cb881 --- /dev/null +++ b/res/img/zoom-white.svg @@ -0,0 +1,59 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index 82f3386e8d..8552b2e381 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -59,6 +59,8 @@ export default class ImageView extends React.Component { initY = 0; lastX = 0; lastY = 0; + minZoom = 100; + maxZoom = 300; componentDidMount() { /* We have to use addEventListener() because the listener @@ -83,16 +85,16 @@ export default class ImageView extends React.Component { ev.preventDefault(); const newZoom =this.state.zoom - ev.deltaY; - if (newZoom <= 100) { + if (newZoom <= this.minZoom) { this.setState({ - zoom: 100, + zoom: this.minZoom, translationX: 0, translationY: 0, }); return; } - if (newZoom >= 300) { - this.setState({zoom: 300}); + if (newZoom >= this.maxZoom) { + this.setState({zoom: this.maxZoom}); return; } @@ -143,8 +145,8 @@ export default class ImageView extends React.Component { }; onZoomInClick = () => { - if (this.state.zoom >= 300) { - this.setState({zoom: 300}); + if (this.state.zoom >= this.maxZoom) { + this.setState({zoom: this.maxZoom}); return; } @@ -154,9 +156,9 @@ export default class ImageView extends React.Component { }; onZoomOutClick = () => { - if (this.state.zoom <= 100) { + if (this.state.zoom <= this.minZoom) { this.setState({ - zoom: 100, + zoom: this.minZoom, translationX: 0, translationY: 0, }); @@ -167,6 +169,18 @@ export default class ImageView extends React.Component { }); } + onZoomClick = () => { + if (this.state.zoom <= this.minZoom) { + this.setState({zoom: this.maxZoom}); + } else { + this.setState({ + zoom: this.minZoom, + translationX: 0, + translationY: 0, + }); + } + } + onStartMoving = ev => { ev.stopPropagation(); ev.preventDefault(); @@ -278,6 +292,9 @@ export default class ImageView extends React.Component { { metadata }
+ + { + { From 6315c8ecefae446a11eb9b031e35b524a1bfa9d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Dec 2020 20:45:33 +0100 Subject: [PATCH 0065/2197] Fix formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/elements/_ImageView.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index 1b38021267..43333a25e6 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -59,7 +59,6 @@ limitations under the License. right: 0; padding: 50px 50px 0 0; display: flex; - } .mx_ImageView_label { From 00bc97b63fde4d1605b8820422d98fa73680e889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 20 Dec 2020 20:45:47 +0100 Subject: [PATCH 0066/2197] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f8ef44763d..cd4285753f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1833,11 +1833,14 @@ "expand": "expand", "You cannot delete this image. (%(code)s)": "You cannot delete this image. (%(code)s)", "Uploaded on %(date)s by %(user)s": "Uploaded on %(date)s by %(user)s", + "Zoom": "Zoom", + "Zoom in": "Zoom in", + "Zoom out": "Zoom out", "Rotate Left": "Rotate Left", "Rotate counter-clockwise": "Rotate counter-clockwise", "Rotate Right": "Rotate Right", "Rotate clockwise": "Rotate clockwise", - "Download this file": "Download this file", + "Download": "Download", "Information": "Information", "Language Dropdown": "Language Dropdown", "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", @@ -2593,7 +2596,6 @@ "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.": "Your recovery key is a safety net - you can use it to restore access to your encrypted messages if you forget your recovery passphrase.", "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Keep a copy of it somewhere secure, like a password manager or even a safe.", "Your recovery key": "Your recovery key", - "Download": "Download", "Your recovery key has been copied to your clipboard, paste it to:": "Your recovery key has been copied to your clipboard, paste it to:", "Your recovery key is in your Downloads folder.": "Your recovery key is in your Downloads folder.", "Print it and store it somewhere safe": "Print it and store it somewhere safe", From 78b3f50bfd4bbe3de053d7b7d994ad779e3002a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20M=C3=A4der?= Date: Sun, 20 Dec 2020 23:14:56 +0100 Subject: [PATCH 0067/2197] Use LaTeX delimiters by default, add /tex command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since parsing for $'s as maths delimiters is tricky, switch the default to \(...\) for inline and \[...\] for display maths as it is used in LaTeX. Add /tex command to explicitly parse in TeX mode, which uses $...$ for inline and $$...$$ for display maths. Signed-off-by: Sven Mäder --- src/SlashCommands.tsx | 18 +++++++ src/editor/deserialize.ts | 8 +-- src/editor/serialize.ts | 98 ++++++++++++++++++++++++++++--------- src/i18n/strings/en_EN.json | 1 + 4 files changed, 99 insertions(+), 26 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 79c21c4af5..e9bde933ec 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -48,6 +48,7 @@ import SettingsStore from "./settings/SettingsStore"; import {UIFeature} from "./settings/UIFeature"; import {CHAT_EFFECTS} from "./effects" import CallHandler from "./CallHandler"; +import {markdownSerializeIfNeeded} from './editor/serialize'; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -223,6 +224,23 @@ export const Commands = [ }, category: CommandCategories.messages, }), + new Command({ + command: 'tex', + args: '', + description: _td('Sends a message in TeX mode, using $ and $$ delimiters for maths'), + runFn: function(roomId, args) { + if (SettingsStore.getValue("feature_latex_maths")) { + if (args) { + let html = markdownSerializeIfNeeded(args, {forceHTML: false}, {forceTEX: true}); + return success(MatrixClientPeg.get().sendHtmlMessage(roomId, args, html)); + } + return reject(this.getUsage()); + } else { + return reject("Render LaTeX maths in messages needs to be enabled in Labs"); + } + }, + category: CommandCategories.messages, + }), new Command({ command: 'ddg', args: '', diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts index 6336b4c46b..a1ee079af5 100644 --- a/src/editor/deserialize.ts +++ b/src/editor/deserialize.ts @@ -136,11 +136,11 @@ function parseElement(n: HTMLElement, partCreator: PartCreator, lastNode: HTMLEl // math nodes are translated back into delimited latex strings if (n.hasAttribute("data-mx-maths")) { const delimLeft = (n.nodeName == "SPAN") ? - (SdkConfig.get()['latex_maths_delims'] || {})['inline_left'] || "$" : - (SdkConfig.get()['latex_maths_delims'] || {})['display_left'] || "$$"; + (SdkConfig.get()['latex_maths_delims'] || {})['inline_left'] || "\\(" : + (SdkConfig.get()['latex_maths_delims'] || {})['display_left'] || "\\["; const delimRight = (n.nodeName == "SPAN") ? - (SdkConfig.get()['latex_maths_delims'] || {})['inline_right'] || "$" : - (SdkConfig.get()['latex_maths_delims'] || {})['display_right'] || "$$"; + (SdkConfig.get()['latex_maths_delims'] || {})['inline_right'] || "\\)" : + (SdkConfig.get()['latex_maths_delims'] || {})['display_right'] || "\\]"; const tex = n.getAttribute("data-mx-maths"); return partCreator.plain(delimLeft + tex + delimRight); } else if (!checkDescendInto(n)) { diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index c1f4da306b..ca798a324e 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -41,24 +41,57 @@ export function mdSerialize(model: EditorModel) { }, ""); } -export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { - let md = mdSerialize(model); +export function markdownSerializeIfNeeded(md: string, {forceHTML = false} = {}, {forceTEX = false} = {}) { + // copy of raw input to remove unwanted math later + const orig = md; if (SettingsStore.getValue("feature_latex_maths")) { - const displayPattern = (SdkConfig.get()['latex_maths_delims'] || {})['display_pattern'] || - "\\$\\$(([^$]|\\\\\\$)*)\\$\\$"; - const inlinePattern = (SdkConfig.get()['latex_maths_delims'] || {})['inline_pattern'] || - "\\$(([^$]|\\\\\\$)*)\\$"; + if (forceTEX) { + // detect math with tex delimiters, inline: $...$, display $$...$$ + // preferably use negative lookbehinds, not supported in all major browsers: + // const displayPattern = "^(?\n\n
\n\n`; - }); + // conditions for display math detection ($$...$$): + // - left delimiter ($$) is not escaped by a backslash + // - pattern starts at the beginning of a line + // - left delimiter is not followed by a space or tab character + // - pattern ends at the end of a line + const displayPattern = "^(?!\\\\)\\$\\$(?![ \\t])(([^$]|\\\\\\$)+?)\\$\\$$"; - md = md.replace(RegExp(inlinePattern, "gm"), function(m, p1) { - const p1e = AllHtmlEntities.encode(p1); - return ``; - }); + // conditions for inline math detection ($...$): + // - left and right delimiters ($) are not escaped by backslashes + // - pattern starts at the beginning of a line or follows a whitespace character + // - left delimiter is not followed by a whitespace character + // - right delimiter is not preseeded by a whitespace character + const inlinePattern = "(^|\\s)(?!\\\\)\\$(?!\\s)(([^$]|\\\\\\$)*[^\\\\\\s\\$](?:\\\\\\$)?)\\$"; + + md = md.replace(RegExp(displayPattern, "gm"), function(m, p1) { + const p1e = AllHtmlEntities.encode(p1); + return `
\n\n
\n\n`; + }); + + md = md.replace(RegExp(inlinePattern, "gm"), function(m, p1, p2) { + const p2e = AllHtmlEntities.encode(p2); + return `${p1}`; + }); + } else { + // detect math with latex delimiters, inline: \(...\), display \[...\] + const displayPattern = (SdkConfig.get()['latex_maths_delims'] || {})['display_pattern'] || + "^\\\\\\[(.*?)\\\\\\]$"; + const inlinePattern = (SdkConfig.get()['latex_maths_delims'] || {})['inline_pattern'] || + "(^|\\s)\\\\\\((.*?)\\\\\\)"; + + md = md.replace(RegExp(displayPattern, "gms"), function(m, p1) { + const p1e = AllHtmlEntities.encode(p1); + return `
\n\n
\n\n`; + }); + + md = md.replace(RegExp(inlinePattern, "gms"), function(m, p1, p2) { + const p2e = AllHtmlEntities.encode(p2); + return `${p1}`; + }); + } // make sure div tags always start on a new line, otherwise it will confuse // the markdown parser @@ -69,15 +102,30 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = if (!parser.isPlainText() || forceHTML) { // feed Markdown output to HTML parser const phtml = cheerio.load(parser.toHTML(), - { _useHtmlParser2: true, decodeEntities: false }) + { _useHtmlParser2: true, decodeEntities: false }); - // add fallback output for latex math, which should not be interpreted as markdown - phtml('div, span').each(function(i, e) { - const tex = phtml(e).attr('data-mx-maths') - if (tex) { - phtml(e).html(`${tex}`) - } - }); + if (SettingsStore.getValue("feature_latex_maths")) { + // original Markdown without LaTeX replacements + const parserOrig = new Markdown(orig); + const phtmlOrig = cheerio.load(parserOrig.toHTML(), + { _useHtmlParser2: true, decodeEntities: false }); + + // since maths delimiters are handled before Markdown, + // code blocks could contain mangled content. + // replace code blocks with original content + phtml('code').contents('div, span').each(function(i) { + const origData = phtmlOrig('code').contents('div, span')[i].data; + phtml('code').contents('div, span')[i].data = origData; + }); + + // add fallback output for latex math, which should not be interpreted as markdown + phtml('div, span').each(function(i, e) { + const tex = phtml(e).attr('data-mx-maths') + if (tex) { + phtml(e).html(`${tex}`) + } + }); + } return phtml.html(); } // ensure removal of escape backslashes in non-Markdown messages @@ -86,6 +134,12 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = } } +export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { + let md = mdSerialize(model); + + return markdownSerializeIfNeeded(md, {forceHTML: forceHTML}); +} + export function textSerialize(model: EditorModel) { return model.parts.reduce((text, part) => { switch (part.type) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 2fb70ecdb1..bf6c61414a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -416,6 +416,7 @@ "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message", "Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown", "Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown", + "Sends a message in TeX mode, using $ and $$ delimiters for maths": "Sends a message in TeX mode, using $ and $$ delimiters for maths", "Searches DuckDuckGo for results": "Searches DuckDuckGo for results", "/ddg is not a command": "/ddg is not a command", "To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.", From 997e08f21e32d61f164cfefb4c397fc48d19de71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 21 Dec 2020 10:07:46 +0100 Subject: [PATCH 0068/2197] Reorganize buttons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js index 8552b2e381..4e38b4b4eb 100644 --- a/src/components/views/elements/ImageView.js +++ b/src/components/views/elements/ImageView.js @@ -292,15 +292,16 @@ export default class ImageView extends React.Component { { metadata }
+ { redactButton } { - - { - { + + { + { @@ -310,7 +311,6 @@ export default class ImageView extends React.Component { { - { redactButton } { From ad5844fcc0b9e585e0f0f5766a33a9e7f716d43e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 21 Dec 2020 20:33:03 +0100 Subject: [PATCH 0069/2197] Fix i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 32c3a61f8a..a3345caac5 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1839,8 +1839,8 @@ "You cannot delete this image. (%(code)s)": "You cannot delete this image. (%(code)s)", "Uploaded on %(date)s by %(user)s": "Uploaded on %(date)s by %(user)s", "Zoom": "Zoom", - "Zoom in": "Zoom in", "Zoom out": "Zoom out", + "Zoom in": "Zoom in", "Rotate Left": "Rotate Left", "Rotate counter-clockwise": "Rotate counter-clockwise", "Rotate Right": "Rotate Right", From fb57123e25966912277ac65927304b088b9e7db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20M=C3=A4der?= Date: Tue, 22 Dec 2020 12:18:38 +0100 Subject: [PATCH 0070/2197] Improve inline latex regex matching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sven Mäder --- src/SlashCommands.tsx | 2 +- src/editor/serialize.ts | 29 +++++++++++++++++++---------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index e9bde933ec..bf190fc450 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -231,7 +231,7 @@ export const Commands = [ runFn: function(roomId, args) { if (SettingsStore.getValue("feature_latex_maths")) { if (args) { - let html = markdownSerializeIfNeeded(args, {forceHTML: false}, {forceTEX: true}); + const html = markdownSerializeIfNeeded(args, {forceHTML: false}, {forceTEX: true}); return success(MatrixClientPeg.get().sendHtmlMessage(roomId, args, html)); } return reject(this.getUsage()); diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index ca798a324e..4ef722e334 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -52,18 +52,18 @@ export function markdownSerializeIfNeeded(md: string, {forceHTML = false} = {}, // const displayPattern = "^(? Date: Tue, 22 Dec 2020 13:31:58 +0100 Subject: [PATCH 0071/2197] Fix linting error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sven Mäder --- src/editor/serialize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 4ef722e334..3aafa70fe8 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -144,7 +144,7 @@ export function markdownSerializeIfNeeded(md: string, {forceHTML = false} = {}, } export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { - let md = mdSerialize(model); + const md = mdSerialize(model); return markdownSerializeIfNeeded(md, {forceHTML: forceHTML}); } From d589c6100069c5ddae0c7372760c63d40ecd84ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 9 Jan 2021 09:09:14 +0100 Subject: [PATCH 0072/2197] Added send message button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/rooms/_MessageComposer.scss | 5 +++++ src/components/views/rooms/MessageComposer.js | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index 71c0db947e..897167f745 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -239,6 +239,7 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/call/video-call.svg'); } + .mx_MessageComposer_emoji::before { mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg'); } @@ -247,6 +248,10 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/composer/sticker.svg'); } +.mx_MessageComposer_sendMessage::before { + mask-image: url('$(res)/img/element-icons/call/video-call.svg'); +} + .mx_MessageComposer_formatting { cursor: pointer; margin: 0 11px; diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 4ddff8f4b0..86ad3ddbdd 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -393,6 +393,10 @@ export default class MessageComposer extends React.Component { }); } + sendMessage = () => { + this.messageComposerInput._sendMessage(); + } + render() { const controls = [ this.state.me ? : null, @@ -450,6 +454,16 @@ export default class MessageComposer extends React.Component { ); } } + + if (true) { + controls.push(( + + )); + } } else if (this.state.tombstone) { const replacementRoomId = this.state.tombstone.getContent()['replacement_room']; From c64b2a585f3c2d1e75392657995d6b1813250f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 9 Jan 2021 09:17:40 +0100 Subject: [PATCH 0073/2197] Added option to disable send button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/MessageComposer.js | 2 +- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 1 + src/settings/Settings.ts | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 86ad3ddbdd..315b1b78c7 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -455,7 +455,7 @@ export default class MessageComposer extends React.Component { } } - if (true) { + if (SettingsStore.getValue("MessageComposerInput.sendButton")) { controls.push(( Date: Sat, 9 Jan 2021 09:18:10 +0100 Subject: [PATCH 0074/2197] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ac18ccb23e..6baccf95de 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -799,6 +799,7 @@ "Show typing notifications": "Show typing notifications", "Use Command + Enter to send a message": "Use Command + Enter to send a message", "Use Ctrl + Enter to send a message": "Use Ctrl + Enter to send a message", + "Show send message button": "Show send message button", "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", "Mirror local video feed": "Mirror local video feed", "Enable Community Filter Panel": "Enable Community Filter Panel", @@ -1412,6 +1413,7 @@ "Send a reply…": "Send a reply…", "Send an encrypted message…": "Send an encrypted message…", "Send a message…": "Send a message…", + "Send message": "Send message", "The conversation continues here.": "The conversation continues here.", "This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.", "You do not have permission to post to this room": "You do not have permission to post to this room", From 4538274e74938294f840abcae070ac75d052d311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 9 Jan 2021 09:25:29 +0100 Subject: [PATCH 0075/2197] Added send message icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/rooms/_MessageComposer.scss | 2 +- res/img/element-icons/send-message.svg | 54 +++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 res/img/element-icons/send-message.svg diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index 897167f745..8c2a997490 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -249,7 +249,7 @@ limitations under the License. } .mx_MessageComposer_sendMessage::before { - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); + mask-image: url('$(res)/img/element-icons/send-message.svg'); } .mx_MessageComposer_formatting { diff --git a/res/img/element-icons/send-message.svg b/res/img/element-icons/send-message.svg new file mode 100644 index 0000000000..2e74745e21 --- /dev/null +++ b/res/img/element-icons/send-message.svg @@ -0,0 +1,54 @@ + + + + + + image/svg+xml + + + + + + + + + From 263f2136502c657c4dcbb07674e965576ad562ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 9 Jan 2021 09:27:02 +0100 Subject: [PATCH 0076/2197] Remove an empty line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/rooms/_MessageComposer.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index 8c2a997490..8b34318f1d 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -239,7 +239,6 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/call/video-call.svg'); } - .mx_MessageComposer_emoji::before { mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg'); } From 7d120f7183eef575f16d78b1e9b4fdfabf7d2848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 9 Jan 2021 09:33:11 +0100 Subject: [PATCH 0077/2197] Simplifie svg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/img/element-icons/send-message.svg | 55 +------------------------- 1 file changed, 2 insertions(+), 53 deletions(-) diff --git a/res/img/element-icons/send-message.svg b/res/img/element-icons/send-message.svg index 2e74745e21..ce35bf8bc8 100644 --- a/res/img/element-icons/send-message.svg +++ b/res/img/element-icons/send-message.svg @@ -1,54 +1,3 @@ - - - - - - image/svg+xml - - - - - - - - + + From 9f1113b3bd47295d1e6f0d2e897bcb93773a06cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 16 Jan 2021 16:35:50 +0100 Subject: [PATCH 0078/2197] Watch setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/MessageComposer.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 315b1b78c7..a18dded04f 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -263,6 +263,7 @@ export default class MessageComposer extends React.Component { tombstone: this._getRoomTombstone(), canSendMessages: this.props.room.maySendMessage(), showCallButtons: SettingsStore.getValue("showCallButtonsInComposer"), + showSendButton: SettingsStore.getValue("MessageComposerInput.sendButton"), hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room), joinedConference: WidgetStore.instance.isJoinedToConferenceIn(this.props.room), }; @@ -280,6 +281,12 @@ export default class MessageComposer extends React.Component { } }; + onSendButtonChanged = () => { + this.setState({ + showSendButton: SettingsStore.getValue("MessageComposerInput.sendButton"), + }); + } + _onWidgetUpdate = () => { this.setState({hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room)}); }; @@ -292,6 +299,8 @@ export default class MessageComposer extends React.Component { this.dispatcherRef = dis.register(this.onAction); MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents); this._waitForOwnMember(); + this.showSendButtonRef = SettingsStore.watchSetting( + "MessageComposerInput.sendButton", null, this.onSendButtonChanged); } _waitForOwnMember() { @@ -317,6 +326,7 @@ export default class MessageComposer extends React.Component { WidgetStore.instance.removeListener(UPDATE_EVENT, this._onWidgetUpdate); ActiveWidgetStore.removeListener('update', this._onActiveWidgetUpdate); dis.unregister(this.dispatcherRef); + SettingsStore.unwatchSetting(this.showSendButtonRef); } _onRoomStateEvents(ev, state) { @@ -455,7 +465,7 @@ export default class MessageComposer extends React.Component { } } - if (SettingsStore.getValue("MessageComposerInput.sendButton")) { + if (this.state.showSendButton) { controls.push(( Date: Sat, 16 Jan 2021 16:37:50 +0100 Subject: [PATCH 0079/2197] Rename setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/MessageComposer.js | 6 +++--- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 2 +- src/settings/Settings.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index a18dded04f..1e43aa6652 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -263,7 +263,7 @@ export default class MessageComposer extends React.Component { tombstone: this._getRoomTombstone(), canSendMessages: this.props.room.maySendMessage(), showCallButtons: SettingsStore.getValue("showCallButtonsInComposer"), - showSendButton: SettingsStore.getValue("MessageComposerInput.sendButton"), + showSendButton: SettingsStore.getValue("MessageComposerInput.showSendButton"), hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room), joinedConference: WidgetStore.instance.isJoinedToConferenceIn(this.props.room), }; @@ -283,7 +283,7 @@ export default class MessageComposer extends React.Component { onSendButtonChanged = () => { this.setState({ - showSendButton: SettingsStore.getValue("MessageComposerInput.sendButton"), + showSendButton: SettingsStore.getValue("MessageComposerInput.showSendButton"), }); } @@ -300,7 +300,7 @@ export default class MessageComposer extends React.Component { MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents); this._waitForOwnMember(); this.showSendButtonRef = SettingsStore.watchSetting( - "MessageComposerInput.sendButton", null, this.onSendButtonChanged); + "MessageComposerInput.showSendButton", null, this.onSendButtonChanged); } _waitForOwnMember() { diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 31971b7167..eff0824d59 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -34,7 +34,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'MessageComposerInput.suggestEmoji', 'sendTypingNotifications', 'MessageComposerInput.ctrlEnterToSend', - `MessageComposerInput.sendButton`, + `MessageComposerInput.showSendButton`, ]; static TIMELINE_SETTINGS = [ diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 50d0f919e6..2d8385240c 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -336,7 +336,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: isMac ? _td("Use Command + Enter to send a message") : _td("Use Ctrl + Enter to send a message"), default: false, }, - "MessageComposerInput.sendButton": { + "MessageComposerInput.showSendButton": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td("Show send message button"), default: false, From c9f5c90047e0140da31050d97d0f831bf50858c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 16 Jan 2021 16:43:47 +0100 Subject: [PATCH 0080/2197] Rename method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/MessageComposer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 1e43aa6652..9683c4c79e 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -281,7 +281,7 @@ export default class MessageComposer extends React.Component { } }; - onSendButtonChanged = () => { + onShowSendButtonChanged = () => { this.setState({ showSendButton: SettingsStore.getValue("MessageComposerInput.showSendButton"), }); @@ -300,7 +300,7 @@ export default class MessageComposer extends React.Component { MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents); this._waitForOwnMember(); this.showSendButtonRef = SettingsStore.watchSetting( - "MessageComposerInput.showSendButton", null, this.onSendButtonChanged); + "MessageComposerInput.showSendButton", null, this.onShowSendButtonChanged); } _waitForOwnMember() { From 0c09158f2a1be05b6a80d8aa393af351b4f4f785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 22 Jan 2021 08:41:39 +0100 Subject: [PATCH 0081/2197] Comment out unused code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../settings/tabs/user/AppearanceUserSettingsTab.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index 209f245b11..c2f010695b 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -28,13 +28,13 @@ import { FontWatcher } from "../../../../../settings/watchers/FontWatcher"; import { RecheckThemePayload } from '../../../../../dispatcher/payloads/RecheckThemePayload'; import { Action } from '../../../../../dispatcher/actions'; import { IValidationResult, IFieldState } from '../../../elements/Validation'; -import StyledRadioButton from '../../../elements/StyledRadioButton'; +//import StyledRadioButton from '../../../elements/StyledRadioButton'; import StyledCheckbox from '../../../elements/StyledCheckbox'; import SettingsFlag from '../../../elements/SettingsFlag'; import Field from '../../../elements/Field'; import EventTilePreview from '../../../elements/EventTilePreview'; import StyledRadioGroup from "../../../elements/StyledRadioGroup"; -import classNames from 'classnames'; +//import classNames from 'classnames'; import { SettingLevel } from "../../../../../settings/SettingLevel"; import {UIFeature} from "../../../../../settings/UIFeature"; @@ -213,7 +213,7 @@ export default class AppearanceUserSettingsTab extends React.Component): void => { + /*private onLayoutChange = (e: React.ChangeEvent): void => { const val = e.target.value === "true"; this.setState({ @@ -221,7 +221,7 @@ export default class AppearanceUserSettingsTab extends React.Component; } - private renderLayoutSection = () => { + /*private renderLayoutSection = () => { return
{_t("Message layout")} @@ -384,7 +384,7 @@ export default class AppearanceUserSettingsTab extends React.Component
; - }; + };*/ private renderAdvancedSection() { if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null; From c69cc550ea00c9bf16a8d773f91c6733dec80a5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 22 Jan 2021 13:41:21 +0100 Subject: [PATCH 0082/2197] Added layout enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/settings/Layout.ts | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/settings/Layout.ts diff --git a/src/settings/Layout.ts b/src/settings/Layout.ts new file mode 100644 index 0000000000..bfb86170e8 --- /dev/null +++ b/src/settings/Layout.ts @@ -0,0 +1,5 @@ +/* TODO: This should be later reworked into something more generic */ +export enum Layout { + IRC = "irc", + Group = "group" +} From 972c9470494ee571f9d2077bb2d1a54be1d1211f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 22 Jan 2021 13:44:45 +0100 Subject: [PATCH 0083/2197] More generic layout setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/MessagePanel.js | 10 +++--- src/components/structures/RoomView.tsx | 15 +++++---- src/components/structures/TimelinePanel.js | 7 ++-- .../views/elements/EventTilePreview.tsx | 9 +++--- src/components/views/elements/ReplyThread.js | 9 +++--- src/components/views/rooms/EventTile.js | 18 ++++++----- .../tabs/user/AppearanceUserSettingsTab.tsx | 32 +++++++++++++------ src/contexts/RoomContext.ts | 3 +- src/settings/Settings.ts | 8 ++--- 9 files changed, 65 insertions(+), 46 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 375545f819..7b40980714 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -26,6 +26,7 @@ import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import SettingsStore from '../../settings/SettingsStore'; +import {Layout} from "../../settings/Layout"; import {_t} from "../../languageHandler"; import {haveTileForEvent} from "../views/rooms/EventTile"; import {textForEvent} from "../../TextForEvent"; @@ -135,14 +136,13 @@ export default class MessagePanel extends React.Component { // whether to show reactions for an event showReactions: PropTypes.bool, - // whether to use the irc layout - useIRCLayout: PropTypes.bool, + // which layout to use + layout: Layout, // whether or not to show flair at all enableFlair: PropTypes.bool, }; - // Force props to be loaded for useIRCLayout constructor(props) { super(props); @@ -612,7 +612,7 @@ export default class MessagePanel extends React.Component { isSelectedEvent={highlight} getRelationsForEvent={this.props.getRelationsForEvent} showReactions={this.props.showReactions} - useIRCLayout={this.props.useIRCLayout} + layout={this.props.layout} enableFlair={this.props.enableFlair} /> @@ -810,7 +810,7 @@ export default class MessagePanel extends React.Component { } let ircResizer = null; - if (this.props.useIRCLayout) { + if (this.props.layout == Layout.IRC) { ircResizer = { statusBarVisible: false, canReact: false, canReply: false, - useIRCLayout: SettingsStore.getValue("useIRCLayout"), + layout: SettingsStore.getValue("layout"), matrixClientIsReady: this.context && this.context.isInitialSyncComplete(), }; @@ -263,7 +264,7 @@ export default class RoomView extends React.Component { this.showReadReceiptsWatchRef = SettingsStore.watchSetting("showReadReceipts", null, this.onReadReceiptsChange); - this.layoutWatcherRef = SettingsStore.watchSetting("useIRCLayout", null, this.onLayoutChange); + this.layoutWatcherRef = SettingsStore.watchSetting("layout", null, this.onLayoutChange); } // TODO: [REACT-WARNING] Move into constructor @@ -624,7 +625,7 @@ export default class RoomView extends React.Component { private onLayoutChange = () => { this.setState({ - useIRCLayout: SettingsStore.getValue("useIRCLayout"), + layout: SettingsStore.getValue("layout"), }); }; @@ -1915,8 +1916,8 @@ export default class RoomView extends React.Component { const messagePanelClassNames = classNames( "mx_RoomView_messagePanel", { - "mx_IRCLayout": this.state.useIRCLayout, - "mx_GroupLayout": !this.state.useIRCLayout, + "mx_IRCLayout": this.state.layout == Layout.IRC, + "mx_GroupLayout": this.state.layout == Layout.Group, }); // console.info("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview); @@ -1939,7 +1940,7 @@ export default class RoomView extends React.Component { permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)} resizeNotifier={this.props.resizeNotifier} showReactions={true} - useIRCLayout={this.state.useIRCLayout} + layout={this.state.layout} />); let topUnreadMessagesBar = null; diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 27a384ddb2..2d322b378d 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -18,6 +18,7 @@ limitations under the License. */ import SettingsStore from "../../settings/SettingsStore"; +import {Layout} from "../../settings/Layout"; import React, {createRef} from 'react'; import ReactDOM from "react-dom"; import PropTypes from 'prop-types'; @@ -111,8 +112,8 @@ class TimelinePanel extends React.Component { // whether to show reactions for an event showReactions: PropTypes.bool, - // whether to use the irc layout - useIRCLayout: PropTypes.bool, + // which layout to use + layout: Layout, } // a map from room id to read marker event timestamp @@ -1442,7 +1443,7 @@ class TimelinePanel extends React.Component { getRelationsForEvent={this.getRelationsForEvent} editState={this.state.editState} showReactions={this.props.showReactions} - useIRCLayout={this.props.useIRCLayout} + layout={this.props.layout} enableFlair={SettingsStore.getValue(UIFeature.Flair)} /> ); diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx index 20cca35d62..49c97831bc 100644 --- a/src/components/views/elements/EventTilePreview.tsx +++ b/src/components/views/elements/EventTilePreview.tsx @@ -22,6 +22,7 @@ import * as Avatar from '../../../Avatar'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import EventTile from '../rooms/EventTile'; import SettingsStore from "../../../settings/SettingsStore"; +import {Layout} from "../../../settings/Layout"; import {UIFeature} from "../../../settings/UIFeature"; interface IProps { @@ -33,7 +34,7 @@ interface IProps { /** * Whether to use the irc layout or not */ - useIRCLayout: boolean; + layout: Layout; /** * classnames to apply to the wrapper of the preview @@ -121,14 +122,14 @@ export default class EventTilePreview extends React.Component { const event = this.fakeEvent(this.state); const className = classnames(this.props.className, { - "mx_IRCLayout": this.props.useIRCLayout, - "mx_GroupLayout": !this.props.useIRCLayout, + "mx_IRCLayout": this.props.layout == Layout.IRC, + "mx_GroupLayout": this.props.layout == Layout.Group, }); return
; diff --git a/src/components/views/elements/ReplyThread.js b/src/components/views/elements/ReplyThread.js index 24b49f2b13..74aa715642 100644 --- a/src/components/views/elements/ReplyThread.js +++ b/src/components/views/elements/ReplyThread.js @@ -24,6 +24,7 @@ import {wantsDateSeparator} from '../../../DateUtils'; import {MatrixEvent} from 'matrix-js-sdk'; import {makeUserPermalink, RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; import SettingsStore from "../../../settings/SettingsStore"; +import {Layout} from "../../../settings/Layout"; import escapeHtml from "escape-html"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {Action} from "../../../dispatcher/actions"; @@ -42,7 +43,7 @@ export default class ReplyThread extends React.Component { onHeightChanged: PropTypes.func.isRequired, permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired, // Specifies which layout to use. - useIRCLayout: PropTypes.bool, + layout: Layout, }; static contextType = MatrixClientContext; @@ -209,7 +210,7 @@ export default class ReplyThread extends React.Component { }; } - static makeThread(parentEv, onHeightChanged, permalinkCreator, ref, useIRCLayout) { + static makeThread(parentEv, onHeightChanged, permalinkCreator, ref, layout) { if (!ReplyThread.getParentEventId(parentEv)) { return
; } @@ -218,7 +219,7 @@ export default class ReplyThread extends React.Component { onHeightChanged={onHeightChanged} ref={ref} permalinkCreator={permalinkCreator} - useIRCLayout={useIRCLayout} + layout={layout} />; } @@ -386,7 +387,7 @@ export default class ReplyThread extends React.Component { permalinkCreator={this.props.permalinkCreator} isRedacted={ev.isRedacted()} isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")} - useIRCLayout={this.props.useIRCLayout} + layout={this.props.layout} enableFlair={SettingsStore.getValue(UIFeature.Flair)} replacingEventId={ev.replacingEventId()} /> diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 11277daa57..17ba61732a 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -27,6 +27,7 @@ import * as TextForEvent from "../../../TextForEvent"; import * as sdk from "../../../index"; import dis from '../../../dispatcher/dispatcher'; import SettingsStore from "../../../settings/SettingsStore"; +import {Layout} from "../../../settings/Layout"; import {EventStatus} from 'matrix-js-sdk'; import {formatTime} from "../../../DateUtils"; import {MatrixClientPeg} from '../../../MatrixClientPeg'; @@ -225,8 +226,8 @@ export default class EventTile extends React.Component { // whether to show reactions for this event showReactions: PropTypes.bool, - // whether to use the irc layout - useIRCLayout: PropTypes.bool, + // which layout to use + layout: Layout, // whether or not to show flair at all enableFlair: PropTypes.bool, @@ -732,7 +733,7 @@ export default class EventTile extends React.Component { // joins/parts/etc avatarSize = 14; needsSenderProfile = false; - } else if (this.props.useIRCLayout) { + } else if (this.props.layout == Layout.IRC) { avatarSize = 14; needsSenderProfile = true; } else if (this.props.continuation && this.props.tileShape !== "file_grid") { @@ -843,10 +844,11 @@ export default class EventTile extends React.Component { { timestamp } ; - const groupTimestamp = !this.props.useIRCLayout ? linkedTimestamp : null; - const ircTimestamp = this.props.useIRCLayout ? linkedTimestamp : null; - const groupPadlock = !this.props.useIRCLayout && !isBubbleMessage && this._renderE2EPadlock(); - const ircPadlock = this.props.useIRCLayout && !isBubbleMessage && this._renderE2EPadlock(); + const useIRCLayout = this.props.layout == Layout.IRC; + const groupTimestamp = !useIRCLayout ? linkedTimestamp : null; + const ircTimestamp = useIRCLayout ? linkedTimestamp : null; + const groupPadlock = !useIRCLayout && !isBubbleMessage && this._renderE2EPadlock(); + const ircPadlock = useIRCLayout && !isBubbleMessage && this._renderE2EPadlock(); switch (this.props.tileShape) { case 'notif': { @@ -941,7 +943,7 @@ export default class EventTile extends React.Component { this.props.onHeightChanged, this.props.permalinkCreator, this._replyThread, - this.props.useIRCLayout, + this.props.layout, ); // tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index c2f010695b..5d3596911f 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -37,6 +37,7 @@ import StyledRadioGroup from "../../../elements/StyledRadioGroup"; //import classNames from 'classnames'; import { SettingLevel } from "../../../../../settings/SettingLevel"; import {UIFeature} from "../../../../../settings/UIFeature"; +import {Layout} from "../../../../../settings/Layout"; interface IProps { } @@ -62,7 +63,7 @@ interface IState extends IThemeState { useSystemFont: boolean; systemFont: string; showAdvanced: boolean; - useIRCLayout: boolean; + layout: Layout; } @@ -83,7 +84,7 @@ export default class AppearanceUserSettingsTab extends React.Component { + if (enabled) { + this.setState({layout: Layout.IRC}); + SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC); + } else { + this.setState({layout: Layout.Group}); + SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.Group); + } + } + private renderThemeSection() { const themeWatcher = new ThemeWatcher(); let systemThemeSection: JSX.Element; @@ -306,7 +317,7 @@ export default class AppearanceUserSettingsTab extends React.Component
Aa
@@ -409,14 +420,15 @@ export default class AppearanceUserSettingsTab extends React.Component - this.setState({useIRCLayout: checked})} + disabled={this.state.layout == Layout.IRC} /> + this.onIRCLayoutChange(ev.target.checked)} + > + {_t("Enable experimental, compact IRC style layout")} + + ({ roomLoading: true, @@ -40,7 +41,7 @@ const RoomContext = createContext({ statusBarVisible: false, canReact: false, canReply: false, - useIRCLayout: false, + layout: Layout.Group, matrixClientIsReady: false, }); RoomContext.displayName = "RoomContext"; diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 6ca009df61..3651e43d7d 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -36,6 +36,7 @@ import { isMac } from '../Keyboard'; import UIFeatureController from "./controllers/UIFeatureController"; import { UIFeature } from "./UIFeature"; import { OrderedMultiController } from "./controllers/OrderedMultiController"; +import {Layout} from "./Layout"; // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times const LEVELS_ROOM_SETTINGS = [ @@ -623,10 +624,9 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("IRC display name width"), default: 80, }, - "useIRCLayout": { - supportedLevels: LEVELS_ACCOUNT_SETTINGS, - displayName: _td("Enable experimental, compact IRC style layout"), - default: false, + "layout": { + supportedLevels: LEVELS_ROOM_SETTINGS_WITH_ROOM, + default: Layout.Group, }, "showChatEffects": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, From 90ad3360b628136f4e5dfc69db2c4fd81e07f0aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 24 Jan 2021 09:15:11 +0100 Subject: [PATCH 0084/2197] Fixed read receipts? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/rooms/_EventTile.scss | 8 +------- src/components/views/rooms/EventTile.js | 6 +++--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 429ac7ed4b..e2cff70841 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -258,17 +258,11 @@ $left-gutter: 64px; display: inline-block; width: 14px; height: 14px; - top: 29px; + top: -20px; user-select: none; z-index: 1; } -.mx_EventTile_continuation .mx_EventTile_readAvatars, -.mx_EventTile_info .mx_EventTile_readAvatars, -.mx_EventTile_emote .mx_EventTile_readAvatars { - top: 7px; -} - .mx_EventTile_readAvatars .mx_BaseAvatar { position: absolute; display: inline-block; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 4df74f77ce..7a2047af70 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -950,9 +950,6 @@ export default class EventTile extends React.Component { return (
{ ircTimestamp } -
- { readAvatars } -
{ sender } { ircPadlock }
@@ -971,6 +968,9 @@ export default class EventTile extends React.Component { { reactionsRow } { actionBar }
+
+ { readAvatars } +
{ // The avatar goes after the event tile as it's absolutely positioned to be over the // event tile line, so needs to be later in the DOM so it appears on top (this avoids From 7efbd50e316f1d61b6aec98c6b451982e9d509b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 26 Jan 2021 13:30:34 +0100 Subject: [PATCH 0085/2197] Handle migration from useIRCLayout to layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../handlers/DeviceSettingsHandler.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/settings/handlers/DeviceSettingsHandler.ts b/src/settings/handlers/DeviceSettingsHandler.ts index 2096203598..208a265e04 100644 --- a/src/settings/handlers/DeviceSettingsHandler.ts +++ b/src/settings/handlers/DeviceSettingsHandler.ts @@ -20,6 +20,7 @@ import SettingsHandler from "./SettingsHandler"; import {MatrixClientPeg} from "../../MatrixClientPeg"; import {SettingLevel} from "../SettingLevel"; import { CallbackFn, WatchManager } from "../WatchManager"; +import { Layout } from "../Layout"; /** * Gets and sets settings at the "device" level for the current device. @@ -67,6 +68,13 @@ export default class DeviceSettingsHandler extends SettingsHandler { return val['value']; } + // Special case for old useIRCLayout setting + if (settingName === "layout") { + const settings = this.getSettings() || {}; + if (settings["useIRCLayout"]) return Layout.IRC; + return settings[settingName]; + } + const settings = this.getSettings() || {}; return settings[settingName]; } @@ -106,6 +114,18 @@ export default class DeviceSettingsHandler extends SettingsHandler { return Promise.resolve(); } + // Special case for old useIRCLayout setting + if (settingName === "layout") { + const settings = this.getSettings() || {}; + + delete settings["useIRCLayout"]; + settings["layout"] = newValue; + localStorage.setItem("mx_local_settings", JSON.stringify(settings)); + + this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue); + return Promise.resolve(); + } + const settings = this.getSettings() || {}; settings[settingName] = newValue; localStorage.setItem("mx_local_settings", JSON.stringify(settings)); From 554c18cabdfbf79ce36108392e48cb6e7469a0b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 26 Jan 2021 13:36:47 +0100 Subject: [PATCH 0086/2197] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 274bd247e2..6e34bbdf1e 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -828,7 +828,6 @@ "How fast should messages be downloaded.": "How fast should messages be downloaded.", "Manually verify all remote sessions": "Manually verify all remote sessions", "IRC display name width": "IRC display name width", - "Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout", "Show chat effects": "Show chat effects", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", @@ -1148,12 +1147,10 @@ "Custom theme URL": "Custom theme URL", "Add theme": "Add theme", "Theme": "Theme", - "Message layout": "Message layout", - "Compact": "Compact", - "Modern": "Modern", "Hide advanced": "Hide advanced", "Show advanced": "Show advanced", "Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Set the name of a font installed on your system & %(brand)s will attempt to use it.", + "Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout", "Customise your appearance": "Customise your appearance", "Appearance Settings only affect this %(brand)s session.": "Appearance Settings only affect this %(brand)s session.", "Flair": "Flair", From 5de92b68d954ba3f997f2d5713d954ee05303b2e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 27 Jan 2021 11:39:57 +0000 Subject: [PATCH 0087/2197] Show a specific error for hs_disabled --- src/components/structures/LoggedInView.tsx | 2 +- src/components/structures/RoomStatusBar.js | 4 ++++ src/components/structures/auth/Login.tsx | 3 +++ src/components/structures/auth/Registration.tsx | 1 + src/toasts/ServerLimitToast.tsx | 1 + src/utils/ErrorUtils.js | 1 + 6 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 70ec2b7033..508b7f05e7 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -94,7 +94,7 @@ interface IProps { interface IUsageLimit { // eslint-disable-next-line camelcase - limit_type: "monthly_active_user" | string; + limit_type: "monthly_active_user" | "hs_disabled" | string; // eslint-disable-next-line camelcase admin_contact?: string; } diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index c1c4ad6292..aa4bceba74 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -195,6 +195,10 @@ export default class RoomStatusBar extends React.Component { "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. " + "Please contact your service administrator to continue using the service.", ), + 'hs_disabled': _td( + "Your message wasn't sent because this homeserver has been blocked by it's administrator. " + + "Please contact your service administrator to continue using the service.", + ), '': _td( "Your message wasn't sent because this homeserver has exceeded a resource limit. " + "Please contact your service administrator to continue using the service.", diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index 606aeb44ab..a9fd363763 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -218,6 +218,9 @@ export default class LoginComponent extends React.PureComponent 'monthly_active_user': _td( "This homeserver has hit its Monthly Active User limit.", ), + 'hs_blocked': _td( + "This homeserver has been blocked by it's administrator.", + ), '': _td( "This homeserver has exceeded one of its resource limits.", ), diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index 095f3d3433..f9d338902c 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -276,6 +276,7 @@ export default class Registration extends React.Component { response.data.admin_contact, { 'monthly_active_user': _td("This homeserver has hit its Monthly Active User limit."), + 'hs_blocked': _td("This homeserver has been blocked by it's administrator."), '': _td("This homeserver has exceeded one of its resource limits."), }, ); diff --git a/src/toasts/ServerLimitToast.tsx b/src/toasts/ServerLimitToast.tsx index d35140be3d..9dbe8c05f1 100644 --- a/src/toasts/ServerLimitToast.tsx +++ b/src/toasts/ServerLimitToast.tsx @@ -26,6 +26,7 @@ const TOAST_KEY = "serverlimit"; export const showToast = (limitType: string, adminContact?: string, syncError?: boolean) => { const errorText = messageForResourceLimitError(limitType, adminContact, { 'monthly_active_user': _td("Your homeserver has exceeded its user limit."), + 'hs_blocked': _td("This homeserver has been blocked by it's administrator."), '': _td("Your homeserver has exceeded one of its resource limits."), }); const contactText = messageForResourceLimitError(limitType, adminContact, { diff --git a/src/utils/ErrorUtils.js b/src/utils/ErrorUtils.js index f0a4d7c49e..2c6acd5503 100644 --- a/src/utils/ErrorUtils.js +++ b/src/utils/ErrorUtils.js @@ -62,6 +62,7 @@ export function messageForSyncError(err) { err.data.admin_contact, { 'monthly_active_user': _td("This homeserver has hit its Monthly Active User limit."), + 'hs_blocked': _td("This homeserver has been blocked by its administrator."), '': _td("This homeserver has exceeded one of its resource limits."), }, ); From 27724a93d28d7945e49f94b5fa1158095bd84d8d Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 27 Jan 2021 11:42:36 +0000 Subject: [PATCH 0088/2197] new strings --- src/i18n/strings/en_EN.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8d047ea3f1..e55ab581ca 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -650,6 +650,7 @@ "Unexpected error resolving identity server configuration": "Unexpected error resolving identity server configuration", "The message you are trying to send is too large.": "The message you are trying to send is too large.", "This homeserver has hit its Monthly Active User limit.": "This homeserver has hit its Monthly Active User limit.", + "This homeserver has been blocked by its administrator.": "This homeserver has been blocked by its administrator.", "This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.", "Please contact your service administrator to continue using the service.": "Please contact your service administrator to continue using the service.", "Unable to connect to Homeserver. Retrying...": "Unable to connect to Homeserver. Retrying...", @@ -727,6 +728,7 @@ "Enable desktop notifications": "Enable desktop notifications", "Enable": "Enable", "Your homeserver has exceeded its user limit.": "Your homeserver has exceeded its user limit.", + "This homeserver has been blocked by it's administrator.": "This homeserver has been blocked by it's administrator.", "Your homeserver has exceeded one of its resource limits.": "Your homeserver has exceeded one of its resource limits.", "Contact your server admin.": "Contact your server admin.", "Warning": "Warning", @@ -2471,6 +2473,7 @@ "Filter rooms and people": "Filter rooms and people", "You can't send any messages until you review and agree to our terms and conditions.": "You can't send any messages until you review and agree to our terms and conditions.", "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please contact your service administrator to continue using the service.", + "Your message wasn't sent because this homeserver has been blocked by it's administrator. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has been blocked by it's administrator. Please contact your service administrator to continue using the service.", "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.": "Your message wasn't sent because this homeserver has exceeded a resource limit. Please contact your service administrator to continue using the service.", "%(count)s of your messages have not been sent.|other": "Some of your messages have not been sent.", "%(count)s of your messages have not been sent.|one": "Your message was not sent.", From c4f0987487c58ed88dacc62ff155dfa140729fc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20M=C3=A4der?= Date: Fri, 29 Jan 2021 00:11:06 +0100 Subject: [PATCH 0089/2197] Use TeX and LaTeX delimiters by default for serialize --- src/SlashCommands.tsx | 2 +- src/editor/serialize.ts | 71 +++++++++++++++++++++---------------- src/i18n/strings/en_EN.json | 2 +- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index bf190fc450..387b9991db 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -227,7 +227,7 @@ export const Commands = [ new Command({ command: 'tex', args: '', - description: _td('Sends a message in TeX mode, using $ and $$ delimiters for maths'), + description: _td('Sends a message in TeX mode, without restrictions'), runFn: function(roomId, args) { if (SettingsStore.getValue("feature_latex_maths")) { if (args) { diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 3aafa70fe8..61d7878de5 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -47,28 +47,12 @@ export function markdownSerializeIfNeeded(md: string, {forceHTML = false} = {}, if (SettingsStore.getValue("feature_latex_maths")) { if (forceTEX) { - // detect math with tex delimiters, inline: $...$, display $$...$$ - // preferably use negative lookbehinds, not supported in all major browsers: - // const displayPattern = "^(?\n\n
\n\n`; + md = md.replace(RegExp(displayPattern, "gm"), function(m, p1, p2) { + const p2e = AllHtmlEntities.encode(p2); + return `${p1}
\n\n
\n\n`; }); md = md.replace(RegExp(inlinePattern, "gm"), function(m, p1, p2) { @@ -76,24 +60,51 @@ export function markdownSerializeIfNeeded(md: string, {forceHTML = false} = {}, return `${p1}`; }); } else { + // detect math with tex delimiters, inline: $...$, display $$...$$ + // preferably use negative lookbehinds, not supported in all major browsers: + // const displayPattern = "^(?\n\n
\n\n`; + }); + + md = md.replace(RegExp(inlinePatternDollar, "gm"), function(m, p1, p2) { + const p2e = AllHtmlEntities.encode(p2); + return `${p1}`; + }); + // detect math with latex delimiters, inline: \(...\), display \[...\] // conditions for display math detection \[...\]: - // - pattern starts at beginning of line - // - pattern ends at end of line + // - pattern starts at beginning of line or is not prefixed with backslash + // - pattern is not empty const displayPattern = (SdkConfig.get()['latex_maths_delims'] || {})['display_pattern'] || - "^\\\\\\[(.*?)\\\\\\]$"; + "(^|[^\\\\])\\\\\\[(?!\\\\\\])(.*?)\\\\\\]"; // conditions for inline math detection \(...\): // - pattern starts at beginning of line or is not prefixed with backslash - // (this allows escaping and requires that multiple consecutive - // patterns are separated by at least one character) + // - pattern is not empty const inlinePattern = (SdkConfig.get()['latex_maths_delims'] || {})['inline_pattern'] || - "(^|[^\\\\])\\\\\\((.*?)\\\\\\)"; + "(^|[^\\\\])\\\\\\((?!\\\\\\))(.*?)\\\\\\)"; - md = md.replace(RegExp(displayPattern, "gms"), function(m, p1) { - const p1e = AllHtmlEntities.encode(p1); - return `
\n\n
\n\n`; + md = md.replace(RegExp(displayPattern, "gms"), function(m, p1, p2) { + const p2e = AllHtmlEntities.encode(p2); + return `${p1}
\n\n
\n\n`; }); md = md.replace(RegExp(inlinePattern, "gms"), function(m, p1, p2) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index bf6c61414a..bcae343550 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -416,7 +416,7 @@ "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message", "Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown", "Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown", - "Sends a message in TeX mode, using $ and $$ delimiters for maths": "Sends a message in TeX mode, using $ and $$ delimiters for maths", + "Sends a message in TeX mode, without restrictions": "Sends a message in TeX mode, without restrictions", "Searches DuckDuckGo for results": "Searches DuckDuckGo for results", "/ddg is not a command": "/ddg is not a command", "To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.", From bcc069771061b7559313ded1f453f91eaf0f283f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20M=C3=A4der?= Date: Fri, 29 Jan 2021 13:05:49 +0100 Subject: [PATCH 0090/2197] Remove /tex command --- src/SlashCommands.tsx | 18 ------ src/editor/serialize.ts | 108 +++++++++++++++--------------------- src/i18n/strings/en_EN.json | 1 - 3 files changed, 44 insertions(+), 83 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 387b9991db..79c21c4af5 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -48,7 +48,6 @@ import SettingsStore from "./settings/SettingsStore"; import {UIFeature} from "./settings/UIFeature"; import {CHAT_EFFECTS} from "./effects" import CallHandler from "./CallHandler"; -import {markdownSerializeIfNeeded} from './editor/serialize'; // XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816 interface HTMLInputEvent extends Event { @@ -224,23 +223,6 @@ export const Commands = [ }, category: CommandCategories.messages, }), - new Command({ - command: 'tex', - args: '', - description: _td('Sends a message in TeX mode, without restrictions'), - runFn: function(roomId, args) { - if (SettingsStore.getValue("feature_latex_maths")) { - if (args) { - const html = markdownSerializeIfNeeded(args, {forceHTML: false}, {forceTEX: true}); - return success(MatrixClientPeg.get().sendHtmlMessage(roomId, args, html)); - } - return reject(this.getUsage()); - } else { - return reject("Render LaTeX maths in messages needs to be enabled in Labs"); - } - }, - category: CommandCategories.messages, - }), new Command({ command: 'ddg', args: '', diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 61d7878de5..6b1d97a0ef 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -41,77 +41,63 @@ export function mdSerialize(model: EditorModel) { }, ""); } -export function markdownSerializeIfNeeded(md: string, {forceHTML = false} = {}, {forceTEX = false} = {}) { +export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { + let md = mdSerialize(model); // copy of raw input to remove unwanted math later const orig = md; if (SettingsStore.getValue("feature_latex_maths")) { - if (forceTEX) { - const displayPattern = "(^|[^\\\\$])\\$\\$(([^$]|\\\\\\$)+?)\\$\\$"; - const inlinePattern = "(^|[^\\\\$])\\$(([^$]|\\\\\\$)*([^\\\\\\$]|\\\\\\$))\\$"; + // detect math with tex delimiters, inline: $...$, display $$...$$ + // preferably use negative lookbehinds, not supported in all major browsers: + // const displayPattern = "^(?\n\n
\n\n`; - }); + // conditions for display math detection $$...$$: + // - pattern starts at beginning of line or is not prefixed with backslash or dollar + // - left delimiter ($$) is not escaped by backslash + const displayPatternDollar = "(^|[^\\\\$])\\$\\$(([^$]|\\\\\\$)+?)\\$\\$"; - md = md.replace(RegExp(inlinePattern, "gm"), function(m, p1, p2) { - const p2e = AllHtmlEntities.encode(p2); - return `${p1}`; - }); - } else { - // detect math with tex delimiters, inline: $...$, display $$...$$ - // preferably use negative lookbehinds, not supported in all major browsers: - // const displayPattern = "^(?\n\n
\n\n`; + }); - // conditions for inline math detection $...$: - // - pattern starts at beginning of line, follows whitespace character or punctuation - // - pattern is on a single line - // - left and right delimiters ($) are not escaped by backslashes - // - left delimiter is not followed by whitespace character - // - right delimiter is not prefixed with whitespace character - const inlinePatternDollar = "(^|\\s|[.,!?:;])(?!\\\\)\\$(?!\\s)(([^$\\n]|\\\\\\$)*([^\\\\\\s\\$]|\\\\\\$)(?:\\\\\\$)?)\\$"; + md = md.replace(RegExp(inlinePatternDollar, "gm"), function(m, p1, p2) { + const p2e = AllHtmlEntities.encode(p2); + return `${p1}`; + }); - md = md.replace(RegExp(displayPatternDollar, "gm"), function(m, p1, p2) { - const p2e = AllHtmlEntities.encode(p2); - return `${p1}
\n\n
\n\n`; - }); + // detect math with latex delimiters, inline: \(...\), display \[...\] - md = md.replace(RegExp(inlinePatternDollar, "gm"), function(m, p1, p2) { - const p2e = AllHtmlEntities.encode(p2); - return `${p1}`; - }); + // conditions for display math detection \[...\]: + // - pattern starts at beginning of line or is not prefixed with backslash + // - pattern is not empty + const displayPattern = (SdkConfig.get()['latex_maths_delims'] || {})['display_pattern'] || + "(^|[^\\\\])\\\\\\[(?!\\\\\\])(.*?)\\\\\\]"; - // detect math with latex delimiters, inline: \(...\), display \[...\] + // conditions for inline math detection \(...\): + // - pattern starts at beginning of line or is not prefixed with backslash + // - pattern is not empty + const inlinePattern = (SdkConfig.get()['latex_maths_delims'] || {})['inline_pattern'] || + "(^|[^\\\\])\\\\\\((?!\\\\\\))(.*?)\\\\\\)"; - // conditions for display math detection \[...\]: - // - pattern starts at beginning of line or is not prefixed with backslash - // - pattern is not empty - const displayPattern = (SdkConfig.get()['latex_maths_delims'] || {})['display_pattern'] || - "(^|[^\\\\])\\\\\\[(?!\\\\\\])(.*?)\\\\\\]"; + md = md.replace(RegExp(displayPattern, "gms"), function(m, p1, p2) { + const p2e = AllHtmlEntities.encode(p2); + return `${p1}
\n\n
\n\n`; + }); - // conditions for inline math detection \(...\): - // - pattern starts at beginning of line or is not prefixed with backslash - // - pattern is not empty - const inlinePattern = (SdkConfig.get()['latex_maths_delims'] || {})['inline_pattern'] || - "(^|[^\\\\])\\\\\\((?!\\\\\\))(.*?)\\\\\\)"; - - md = md.replace(RegExp(displayPattern, "gms"), function(m, p1, p2) { - const p2e = AllHtmlEntities.encode(p2); - return `${p1}
\n\n
\n\n`; - }); - - md = md.replace(RegExp(inlinePattern, "gms"), function(m, p1, p2) { - const p2e = AllHtmlEntities.encode(p2); - return `${p1}`; - }); - } + md = md.replace(RegExp(inlinePattern, "gms"), function(m, p1, p2) { + const p2e = AllHtmlEntities.encode(p2); + return `${p1}`; + }); // make sure div tags always start on a new line, otherwise it will confuse // the markdown parser @@ -154,12 +140,6 @@ export function markdownSerializeIfNeeded(md: string, {forceHTML = false} = {}, } } -export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { - const md = mdSerialize(model); - - return markdownSerializeIfNeeded(md, {forceHTML: forceHTML}); -} - export function textSerialize(model: EditorModel) { return model.parts.reduce((text, part) => { switch (part.type) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index bcae343550..2fb70ecdb1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -416,7 +416,6 @@ "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message", "Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown", "Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown", - "Sends a message in TeX mode, without restrictions": "Sends a message in TeX mode, without restrictions", "Searches DuckDuckGo for results": "Searches DuckDuckGo for results", "/ddg is not a command": "/ddg is not a command", "To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.", From ac1f9b4247f7eefacc969bfcbdbf30984c8e15e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20M=C3=A4der?= Date: Fri, 29 Jan 2021 15:49:20 +0100 Subject: [PATCH 0091/2197] Add config keys for alternative patterns --- src/editor/serialize.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index 6b1d97a0ef..6655c64347 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -55,7 +55,9 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = // conditions for display math detection $$...$$: // - pattern starts at beginning of line or is not prefixed with backslash or dollar // - left delimiter ($$) is not escaped by backslash - const displayPatternDollar = "(^|[^\\\\$])\\$\\$(([^$]|\\\\\\$)+?)\\$\\$"; + const displayPatternAlternative = (SdkConfig.get()['latex_maths_delims'] || + {})['display_pattern_alternative'] || + "(^|[^\\\\$])\\$\\$(([^$]|\\\\\\$)+?)\\$\\$"; // conditions for inline math detection $...$: // - pattern starts at beginning of line, follows whitespace character or punctuation @@ -63,14 +65,16 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = // - left and right delimiters ($) are not escaped by backslashes // - left delimiter is not followed by whitespace character // - right delimiter is not prefixed with whitespace character - const inlinePatternDollar = "(^|\\s|[.,!?:;])(?!\\\\)\\$(?!\\s)(([^$\\n]|\\\\\\$)*([^\\\\\\s\\$]|\\\\\\$)(?:\\\\\\$)?)\\$"; + const inlinePatternAlternative = (SdkConfig.get()['latex_maths_delims'] || + {})['inline_pattern_alternative'] || + "(^|\\s|[.,!?:;])(?!\\\\)\\$(?!\\s)(([^$\\n]|\\\\\\$)*([^\\\\\\s\\$]|\\\\\\$)(?:\\\\\\$)?)\\$"; - md = md.replace(RegExp(displayPatternDollar, "gm"), function(m, p1, p2) { + md = md.replace(RegExp(displayPatternAlternative, "gm"), function(m, p1, p2) { const p2e = AllHtmlEntities.encode(p2); return `${p1}
\n\n
\n\n`; }); - md = md.replace(RegExp(inlinePatternDollar, "gm"), function(m, p1, p2) { + md = md.replace(RegExp(inlinePatternAlternative, "gm"), function(m, p1, p2) { const p2e = AllHtmlEntities.encode(p2); return `${p1}`; }); From dea1faecfe6b0718268692c4826550d82864229c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 4 Feb 2021 18:15:15 +0100 Subject: [PATCH 0092/2197] Added license MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/settings/Layout.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/settings/Layout.ts b/src/settings/Layout.ts index bfb86170e8..e8d6e67797 100644 --- a/src/settings/Layout.ts +++ b/src/settings/Layout.ts @@ -1,3 +1,19 @@ +/* +Copyright 2021 Šimon Brandner + +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. +*/ + /* TODO: This should be later reworked into something more generic */ export enum Layout { IRC = "irc", From 20193ad7fed0f8ccb54b56424820ea431a9fc1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 5 Feb 2021 08:16:06 +0100 Subject: [PATCH 0093/2197] Added LayoutPropType MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/settings/Layout.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/settings/Layout.ts b/src/settings/Layout.ts index e8d6e67797..3a42b2b510 100644 --- a/src/settings/Layout.ts +++ b/src/settings/Layout.ts @@ -14,8 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ +import PropTypes from 'prop-types'; + /* TODO: This should be later reworked into something more generic */ export enum Layout { IRC = "irc", Group = "group" } + +/* We need this because multiple components are still using JavaScript */ +export const LayoutPropType = PropTypes.oneOf(Object.values(Layout)); From f2d236d429a6272fed848d30b1ae05dc10d89903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 5 Feb 2021 08:17:30 +0100 Subject: [PATCH 0094/2197] Use LayoutPropType MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/MessagePanel.js | 4 ++-- src/components/structures/TimelinePanel.js | 4 ++-- src/components/views/elements/ReplyThread.js | 4 ++-- src/components/views/rooms/EventTile.js | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 7b40980714..5e6b94b9f8 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -26,7 +26,7 @@ import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import SettingsStore from '../../settings/SettingsStore'; -import {Layout} from "../../settings/Layout"; +import {Layout, LayoutPropType} from "../../settings/Layout"; import {_t} from "../../languageHandler"; import {haveTileForEvent} from "../views/rooms/EventTile"; import {textForEvent} from "../../TextForEvent"; @@ -137,7 +137,7 @@ export default class MessagePanel extends React.Component { showReactions: PropTypes.bool, // which layout to use - layout: Layout, + layout: LayoutPropType, // whether or not to show flair at all enableFlair: PropTypes.bool, diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 2d322b378d..e8da5c42d0 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -18,7 +18,7 @@ limitations under the License. */ import SettingsStore from "../../settings/SettingsStore"; -import {Layout} from "../../settings/Layout"; +import {LayoutPropType} from "../../settings/Layout"; import React, {createRef} from 'react'; import ReactDOM from "react-dom"; import PropTypes from 'prop-types'; @@ -113,7 +113,7 @@ class TimelinePanel extends React.Component { showReactions: PropTypes.bool, // which layout to use - layout: Layout, + layout: LayoutPropType, } // a map from room id to read marker event timestamp diff --git a/src/components/views/elements/ReplyThread.js b/src/components/views/elements/ReplyThread.js index 74aa715642..27d773b099 100644 --- a/src/components/views/elements/ReplyThread.js +++ b/src/components/views/elements/ReplyThread.js @@ -24,7 +24,7 @@ import {wantsDateSeparator} from '../../../DateUtils'; import {MatrixEvent} from 'matrix-js-sdk'; import {makeUserPermalink, RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; import SettingsStore from "../../../settings/SettingsStore"; -import {Layout} from "../../../settings/Layout"; +import {LayoutPropType} from "../../../settings/Layout"; import escapeHtml from "escape-html"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {Action} from "../../../dispatcher/actions"; @@ -43,7 +43,7 @@ export default class ReplyThread extends React.Component { onHeightChanged: PropTypes.func.isRequired, permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired, // Specifies which layout to use. - layout: Layout, + layout: LayoutPropType, }; static contextType = MatrixClientContext; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 17ba61732a..bb10825b6e 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -27,7 +27,7 @@ import * as TextForEvent from "../../../TextForEvent"; import * as sdk from "../../../index"; import dis from '../../../dispatcher/dispatcher'; import SettingsStore from "../../../settings/SettingsStore"; -import {Layout} from "../../../settings/Layout"; +import {Layout, LayoutPropType} from "../../../settings/Layout"; import {EventStatus} from 'matrix-js-sdk'; import {formatTime} from "../../../DateUtils"; import {MatrixClientPeg} from '../../../MatrixClientPeg'; @@ -227,7 +227,7 @@ export default class EventTile extends React.Component { showReactions: PropTypes.bool, // which layout to use - layout: Layout, + layout: LayoutPropType, // whether or not to show flair at all enableFlair: PropTypes.bool, From cc38bcf333bc9fdd7d8ebc7d0b4d06330ba7e359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 6 Feb 2021 15:09:21 +0100 Subject: [PATCH 0095/2197] Display room name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/Pill.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/Pill.js b/src/components/views/elements/Pill.js index daa4cb70e2..f1527c48b1 100644 --- a/src/components/views/elements/Pill.js +++ b/src/components/views/elements/Pill.js @@ -226,7 +226,7 @@ class Pill extends React.Component { case Pill.TYPE_ROOM_MENTION: { const room = this.state.room; if (room) { - linkText = resource; + linkText = room.name; if (this.props.shouldShowPillAvatar) { avatar =
); } From e6da146b21c3e4df169cf90a189cb700ab8ac052 Mon Sep 17 00:00:00 2001 From: William Kray Date: Sun, 29 Nov 2020 16:53:50 -0800 Subject: [PATCH 0103/2197] pad url preview close button a bit --- res/css/views/rooms/_LinkPreviewWidget.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/rooms/_LinkPreviewWidget.scss b/res/css/views/rooms/_LinkPreviewWidget.scss index d06cd65502..27453cf352 100644 --- a/res/css/views/rooms/_LinkPreviewWidget.scss +++ b/res/css/views/rooms/_LinkPreviewWidget.scss @@ -57,6 +57,7 @@ limitations under the License. cursor: pointer; width: 18px; height: 18px; + padding: 0px 5px 5px 5px; img { flex: 0 0 40px; From fec230945129dea9074547b24e24112a4b658eb2 Mon Sep 17 00:00:00 2001 From: William Kray Date: Fri, 29 Jan 2021 01:30:08 -0800 Subject: [PATCH 0104/2197] move cancel button to the right --- res/css/views/rooms/_LinkPreviewWidget.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/css/views/rooms/_LinkPreviewWidget.scss b/res/css/views/rooms/_LinkPreviewWidget.scss index 27453cf352..5310bd3bbb 100644 --- a/res/css/views/rooms/_LinkPreviewWidget.scss +++ b/res/css/views/rooms/_LinkPreviewWidget.scss @@ -58,6 +58,8 @@ limitations under the License. width: 18px; height: 18px; padding: 0px 5px 5px 5px; + margin-left: auto; + margin-right: 0px; img { flex: 0 0 40px; From 392b47e200c91773144e68d86aaaf0e5e6dc33d2 Mon Sep 17 00:00:00 2001 From: PunitLodha Date: Tue, 9 Feb 2021 11:39:36 +0530 Subject: [PATCH 0105/2197] Add email only if the verification is complete --- .../views/settings/account/EmailAddresses.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/views/settings/account/EmailAddresses.js b/src/components/views/settings/account/EmailAddresses.js index ca66d5df65..07d2133e95 100644 --- a/src/components/views/settings/account/EmailAddresses.js +++ b/src/components/views/settings/account/EmailAddresses.js @@ -178,19 +178,21 @@ export default class EmailAddresses extends React.Component { e.preventDefault(); this.setState({continueDisabled: true}); - this.state.addTask.checkEmailLinkClicked().then(() => { - const email = this.state.newEmailAddress; + this.state.addTask.checkEmailLinkClicked().then(([finished]) => { + if (finished) { + const email = this.state.newEmailAddress; + const emails = [ + ...this.props.emails, + { address: email, medium: "email" }, + ]; + this.props.onEmailsChange(emails); + } this.setState({ addTask: null, continueDisabled: false, verifying: false, newEmailAddress: "", }); - const emails = [ - ...this.props.emails, - { address: email, medium: "email" }, - ]; - this.props.onEmailsChange(emails); }).catch((err) => { this.setState({continueDisabled: false}); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); From 673717d6c0675c6756beba822f679af9b8375214 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 10 Feb 2021 15:57:57 +0000 Subject: [PATCH 0106/2197] Upgrade matrix-js-sdk to 9.7.0-rc.1 --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 2263c7de32..65201a1083 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "9.7.0-rc.1", "matrix-widget-api": "^0.1.0-beta.13", "minimist": "^1.2.5", "pako": "^2.0.3", diff --git a/yarn.lock b/yarn.lock index 1a350f697a..e3d7a5ac28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5577,9 +5577,10 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "9.6.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/6ad3fb16b3813c717943f6bc8bca5a55fe325477" +matrix-js-sdk@9.7.0-rc.1: + version "9.7.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-9.7.0-rc.1.tgz#30cabda3f2b416d09c95afc4fe138bba44534c76" + integrity sha512-y6D2bXYKQqGrtf1PLHjjHf6wW67DIhdrwQ77nKOhEd/rfaEqcMf99wqSvF/nzFexbG1Y7PG4IdM4YTdOQ9UD+g== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From 1dfbc73d4adcced75a258ecc55b9adc09770c303 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 10 Feb 2021 16:51:52 +0000 Subject: [PATCH 0107/2197] Prepare changelog for v3.14.0-rc.1 --- CHANGELOG.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43a1494497..50880b1d53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,79 @@ +Changes in [3.14.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.14.0-rc.1) (2021-02-10) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.13.1...v3.14.0-rc.1) + + * Upgrade to JS SDK 9.7.0-rc.1 + * Translations update from Weblate + [\#5636](https://github.com/matrix-org/matrix-react-sdk/pull/5636) + * Add host signup modal with iframe + [\#5450](https://github.com/matrix-org/matrix-react-sdk/pull/5450) + * Fix duplication of codeblock elements + [\#5633](https://github.com/matrix-org/matrix-react-sdk/pull/5633) + * Handle undefined call stats + [\#5632](https://github.com/matrix-org/matrix-react-sdk/pull/5632) + * Avoid delayed displaying of sources in source picker + [\#5631](https://github.com/matrix-org/matrix-react-sdk/pull/5631) + * Give breadcrumbs toolbar an accessibility label. + [\#5628](https://github.com/matrix-org/matrix-react-sdk/pull/5628) + * Fix the %s in logs + [\#5627](https://github.com/matrix-org/matrix-react-sdk/pull/5627) + * Fix jumpy notifications settings UI + [\#5625](https://github.com/matrix-org/matrix-react-sdk/pull/5625) + * Improve displaying of code blocks + [\#5559](https://github.com/matrix-org/matrix-react-sdk/pull/5559) + * Fix desktop Matrix screen sharing and add a screen/window picker + [\#5525](https://github.com/matrix-org/matrix-react-sdk/pull/5525) + * Call "MatrixClientPeg.get()" only once in method "findOverrideMuteRule" + [\#5498](https://github.com/matrix-org/matrix-react-sdk/pull/5498) + * Close current modal when session is logged out + [\#5616](https://github.com/matrix-org/matrix-react-sdk/pull/5616) + * Switch room explorer list to CSS grid + [\#5551](https://github.com/matrix-org/matrix-react-sdk/pull/5551) + * Improve SSO login start screen and 3pid invite handling somewhat + [\#5622](https://github.com/matrix-org/matrix-react-sdk/pull/5622) + * Don't jump to bottom on reaction + [\#5621](https://github.com/matrix-org/matrix-react-sdk/pull/5621) + * Fix several profile settings oddities + [\#5620](https://github.com/matrix-org/matrix-react-sdk/pull/5620) + * Add option to hide the stickers button in the composer + [\#5530](https://github.com/matrix-org/matrix-react-sdk/pull/5530) + * Fix confusing right panel button behaviour + [\#5598](https://github.com/matrix-org/matrix-react-sdk/pull/5598) + * Fix jumping timestamp if hovering a message with e2e indicator bar + [\#5601](https://github.com/matrix-org/matrix-react-sdk/pull/5601) + * Fix avatar and trash alignment + [\#5614](https://github.com/matrix-org/matrix-react-sdk/pull/5614) + * Fix z-index of stickerpicker + [\#5617](https://github.com/matrix-org/matrix-react-sdk/pull/5617) + * Fix permalink via parsing for rooms + [\#5615](https://github.com/matrix-org/matrix-react-sdk/pull/5615) + * Fix "Terms and Conditions" checkbox alignment + [\#5613](https://github.com/matrix-org/matrix-react-sdk/pull/5613) + * Fix flair height after accent changes + [\#5611](https://github.com/matrix-org/matrix-react-sdk/pull/5611) + * Iterate Social Logins work around edge cases and branding + [\#5609](https://github.com/matrix-org/matrix-react-sdk/pull/5609) + * Lock widget room ID when added + [\#5607](https://github.com/matrix-org/matrix-react-sdk/pull/5607) + * Better errors for SSO failures + [\#5605](https://github.com/matrix-org/matrix-react-sdk/pull/5605) + * Increase language search bar width + [\#5549](https://github.com/matrix-org/matrix-react-sdk/pull/5549) + * Scroll to bottom on message_sent + [\#5565](https://github.com/matrix-org/matrix-react-sdk/pull/5565) + * Fix new rooms being titled 'Empty Room' + [\#5587](https://github.com/matrix-org/matrix-react-sdk/pull/5587) + * Fix saving the collapsed state of the left panel + [\#5593](https://github.com/matrix-org/matrix-react-sdk/pull/5593) + * Fix app-url hint in the e2e-test run script output + [\#5600](https://github.com/matrix-org/matrix-react-sdk/pull/5600) + * Fix RoomView re-mounting breaking peeking + [\#5602](https://github.com/matrix-org/matrix-react-sdk/pull/5602) + * Tweak a few room ID checks + [\#5592](https://github.com/matrix-org/matrix-react-sdk/pull/5592) + * Remove pills from event permalinks with text + [\#5575](https://github.com/matrix-org/matrix-react-sdk/pull/5575) + Changes in [3.13.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.13.1) (2021-02-04) ===================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.13.0...v3.13.1) From d1524b2e9d243806e2ab55ebc414f2c17609629d Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 10 Feb 2021 16:51:52 +0000 Subject: [PATCH 0108/2197] v3.14.0-rc.1 --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 65201a1083..5beb383791 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.13.1", + "version": "3.14.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -27,7 +27,7 @@ "matrix-gen-i18n": "scripts/gen-i18n.js", "matrix-prune-i18n": "scripts/prune-i18n.js" }, - "main": "./src/index.js", + "main": "./lib/index.js", "matrix_src_main": "./src/index.js", "matrix_lib_main": "./lib/index.js", "matrix_lib_typings": "./lib/index.d.ts", @@ -190,5 +190,6 @@ "transformIgnorePatterns": [ "/node_modules/(?!matrix-js-sdk).+$" ] - } + }, + "typings": "./lib/index.d.ts" } From a61462bc8559815a4ea08d599268b773b681483c Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Thu, 11 Feb 2021 16:34:15 -0500 Subject: [PATCH 0109/2197] use the default SSSS key if the default is set implements MSC2874 --- src/SecurityManager.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts index 220320470a..11d228e7ab 100644 --- a/src/SecurityManager.ts +++ b/src/SecurityManager.ts @@ -98,11 +98,24 @@ async function getSecretStorageKey( { keys: keyInfos }: { keys: Record }, ssssItemName, ): Promise<[string, Uint8Array]> { - const keyInfoEntries = Object.entries(keyInfos); - if (keyInfoEntries.length > 1) { - throw new Error("Multiple storage key requests not implemented"); + const cli = MatrixClientPeg.get(); + let keyId = await cli.getDefaultSecretStorageKeyId(); + let keyInfo; + if (keyId) { + // use the default SSSS key if set + keyInfo = keyInfos[keyId]; + if (!keyInfo) { + throw new Error("Unable to use default SSSS key"); + } + } else { + // if no default SSSS key is set, fall back to a heuristic of using the + // only available key, if only one key is set + const keyInfoEntries = Object.entries(keyInfos); + if (keyInfoEntries.length > 1) { + throw new Error("Multiple storage key requests not implemented"); + } + [keyId, keyInfo] = keyInfoEntries[0]; } - const [keyId, keyInfo] = keyInfoEntries[0]; // Check the in-memory cache if (isCachingAllowed() && secretStorageKeys[keyId]) { From 33979b335446bf16bd02815ae88de7241540ed74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 08:18:27 +0100 Subject: [PATCH 0110/2197] Added a tooltip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/Pill.js | 39 +++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/Pill.js b/src/components/views/elements/Pill.js index cbbe562754..5b3189f389 100644 --- a/src/components/views/elements/Pill.js +++ b/src/components/views/elements/Pill.js @@ -26,6 +26,7 @@ import FlairStore from "../../../stores/FlairStore"; import {getPrimaryPermalinkEntity, parseAppLocalLink} from "../../../utils/permalinks/Permalinks"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {Action} from "../../../dispatcher/actions"; +import Tooltip from './Tooltip'; class Pill extends React.Component { static roomNotifPos(text) { @@ -68,6 +69,8 @@ class Pill extends React.Component { group: null, // The room related to the room pill room: null, + // Is the user hovering the pill + hover: false, }; // TODO: [REACT-WARNING] Replace with appropriate lifecycle event @@ -154,6 +157,18 @@ class Pill extends React.Component { this._unmounted = true; } + onMouseOver = () => { + this.setState({ + hover: true, + }); + }; + + onMouseLeave = () => { + this.setState({ + hover: false, + }); + }; + doProfileLookup(userId, member) { MatrixClientPeg.get().getProfileInfo(userId).then((resp) => { if (this._unmounted) { @@ -256,16 +271,36 @@ class Pill extends React.Component { }); if (this.state.pillType) { + const {yOffset} = this.props; + + let tip; + if (this.state.hover) { + tip = ; + } + return { this.props.inMessage ? - + { avatar } { linkText } : - + { avatar } { linkText } } + {tip} ; } else { // Deliberately render nothing if the URL isn't recognised From c7f9defd12491a962d2012eb07b5e37bc60f17a6 Mon Sep 17 00:00:00 2001 From: Clemens Zeidler Date: Thu, 11 Feb 2021 22:18:10 +1300 Subject: [PATCH 0111/2197] Add simple implementation of a KeyBindingsManager + match tests --- src/KeyBindingsManager.ts | 102 ++++++++++++++++++++++++ test/KeyBindingsManager-test.ts | 137 ++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 src/KeyBindingsManager.ts create mode 100644 test/KeyBindingsManager-test.ts diff --git a/src/KeyBindingsManager.ts b/src/KeyBindingsManager.ts new file mode 100644 index 0000000000..b9cf9749ca --- /dev/null +++ b/src/KeyBindingsManager.ts @@ -0,0 +1,102 @@ +import { isMac } from "./Keyboard"; + +export enum KeyBindingContext { + +} + +export enum KeyAction { + None = 'None', +} + +/** + * Represent a key combination. + * + * The combo is evaluated strictly, i.e. the KeyboardEvent must match the exactly what is specified in the KeyCombo. + */ +export type KeyCombo = { + /** Currently only one `normal` key is supported */ + keys: string[]; + + /** On PC: ctrl is pressed; on Mac: meta is pressed */ + ctrlOrCmd?: boolean; + + altKey?: boolean; + ctrlKey?: boolean; + metaKey?: boolean; + shiftKey?: boolean; +} + +export type KeyBinding = { + keyCombo: KeyCombo; + action: KeyAction; +} + +/** + * Helper method to check if a KeyboardEvent matches a KeyCombo + * + * Note, this method is only exported for testing. + */ +export function isKeyComboMatch(ev: KeyboardEvent, combo: KeyCombo, onMac: boolean): boolean { + if (combo.keys.length > 0 && ev.key !== combo.keys[0]) { + return false; + } + + const comboCtrl = combo.ctrlKey ?? false; + const comboAlt = combo.altKey ?? false; + const comboShift = combo.shiftKey ?? false; + const comboMeta = combo.metaKey ?? false; + // When ctrlOrCmd is set, the keys need do evaluated differently on PC and Mac + if (combo.ctrlOrCmd) { + if (onMac) { + if (!ev.metaKey + || ev.ctrlKey !== comboCtrl + || ev.altKey !== comboAlt + || ev.shiftKey !== comboShift) { + return false; + } + } else { + if (!ev.ctrlKey + || ev.metaKey !== comboMeta + || ev.altKey !== comboAlt + || ev.shiftKey !== comboShift) { + return false; + } + } + return true; + } + + if (ev.metaKey !== comboMeta + || ev.ctrlKey !== comboCtrl + || ev.altKey !== comboAlt + || ev.shiftKey !== comboShift) { + return false; + } + + return true; +} + +export class KeyBindingsManager { + contextBindings: Record = {}; + + /** + * Finds a matching KeyAction for a given KeyboardEvent + */ + getAction(context: KeyBindingContext, ev: KeyboardEvent): KeyAction { + const bindings = this.contextBindings[context]; + if (!bindings) { + return KeyAction.None; + } + const binding = bindings.find(it => isKeyComboMatch(ev, it.keyCombo, isMac)); + if (binding) { + return binding.action; + } + + return KeyAction.None; + } +} + +const manager = new KeyBindingsManager(); + +export function getKeyBindingsManager(): KeyBindingsManager { + return manager; +} diff --git a/test/KeyBindingsManager-test.ts b/test/KeyBindingsManager-test.ts new file mode 100644 index 0000000000..f272878658 --- /dev/null +++ b/test/KeyBindingsManager-test.ts @@ -0,0 +1,137 @@ +import { isKeyComboMatch, KeyCombo } from '../src/KeyBindingsManager'; +const assert = require('assert'); + +function mockKeyEvent(key: string, modifiers?: { + ctrlKey?: boolean, + altKey?: boolean, + shiftKey?: boolean, + metaKey?: boolean +}): KeyboardEvent { + return { + key, + ctrlKey: modifiers?.ctrlKey ?? false, + altKey: modifiers?.altKey ?? false, + shiftKey: modifiers?.shiftKey ?? false, + metaKey: modifiers?.metaKey ?? false + } as KeyboardEvent; +} + +describe('KeyBindingsManager', () => { + it('should match basic key combo', () => { + const combo1: KeyCombo = { + keys: ['k'], + }; + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo1, false), true); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('n'), combo1, false), false); + + }); + + it('should match key + modifier key combo', () => { + const combo: KeyCombo = { + keys: ['k'], + ctrlKey: true, + }; + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false), true); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo, false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true }), combo, false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true, metaKey: true }), combo, false), false); + + const combo2: KeyCombo = { + keys: ['k'], + metaKey: true, + }; + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo2, false), true); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { metaKey: true }), combo2, false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo2, false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { altKey: true, metaKey: true }), combo2, false), false); + + const combo3: KeyCombo = { + keys: ['k'], + altKey: true, + }; + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { altKey: true }), combo3, false), true); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { altKey: true }), combo3, false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo3, false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo3, false), false); + + const combo4: KeyCombo = { + keys: ['k'], + shiftKey: true, + }; + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true }), combo4, false), true); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { shiftKey: true }), combo4, false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo4, false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true, ctrlKey: true }), combo4, false), false); + }); + + it('should match key + multiple modifiers key combo', () => { + const combo: KeyCombo = { + keys: ['k'], + ctrlKey: true, + altKey: true, + }; + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, false), true); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true, altKey: true }), combo, false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo, false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true, shiftKey: true }), combo, + false), false); + + const combo2: KeyCombo = { + keys: ['k'], + ctrlKey: true, + shiftKey: true, + altKey: true, + }; + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, shiftKey: true, altKey: true }), combo2, + false), true); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true, shiftKey: true, altKey: true }), combo2, + false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo2, false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', + { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo2, false), false); + + const combo3: KeyCombo = { + keys: ['k'], + ctrlKey: true, + shiftKey: true, + altKey: true, + metaKey: true, + }; + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', + { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo3, false), true); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', + { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo3, false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', + { ctrlKey: true, shiftKey: true, altKey: true }), combo3, false), false); + }); + + it('should match ctrlOrMeta key combo', () => { + const combo: KeyCombo = { + keys: ['k'], + ctrlOrCmd: true, + }; + // PC: + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false), true); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo, false), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, false), false); + // MAC: + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo, true), true); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, true), false); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, true), false); + }); + + it('should match advanced ctrlOrMeta key combo', () => { + const combo: KeyCombo = { + keys: ['k'], + ctrlOrCmd: true, + altKey: true, + }; + // PC: + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, false), true); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true, altKey: true }), combo, false), false); + // MAC: + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true, altKey: true }), combo, true), true); + assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, true), false); + }); +}); From a975de22efca2302aa5302b5d0142aafd235a701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 11:27:14 +0100 Subject: [PATCH 0112/2197] Fixed buggy tooltip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/Pill.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/elements/Pill.js b/src/components/views/elements/Pill.js index 5b3189f389..68a87d40b9 100644 --- a/src/components/views/elements/Pill.js +++ b/src/components/views/elements/Pill.js @@ -290,6 +290,7 @@ class Pill extends React.Component { > { avatar } { linkText } + { tip } : { avatar } { linkText } + { tip } } - {tip} ; } else { // Deliberately render nothing if the URL isn't recognised From a075568e895c0e2b3e9e83c710cbc9c510b488bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 11:34:09 +0100 Subject: [PATCH 0113/2197] Fixed tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the previous commits I have removed the native title/tooltip so it needs to be removed from the tests as well Signed-off-by: Šimon Brandner --- test/components/views/messages/TextualBody-test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/components/views/messages/TextualBody-test.js b/test/components/views/messages/TextualBody-test.js index d5b80b6756..a596825c09 100644 --- a/test/components/views/messages/TextualBody-test.js +++ b/test/components/views/messages/TextualBody-test.js @@ -208,7 +208,7 @@ describe("", () => { const content = wrapper.find(".mx_EventTile_body"); expect(content.html()).toBe('' + 'Hey ' + - '' + + '' + 'Member' + ''); @@ -267,8 +267,8 @@ describe("", () => { expect(content.html()).toBe( '' + 'A ' + '!ZxbRYPQXDXKGmDnJNg:example.com with vias', From cb5237a18be0bf17eede4768f8978dfc44e7c609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 14:21:07 +0100 Subject: [PATCH 0114/2197] Display room name instead of alias MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/editor/parts.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/editor/parts.ts b/src/editor/parts.ts index 8a7ccfcb7b..67f6a2c0c5 100644 --- a/src/editor/parts.ts +++ b/src/editor/parts.ts @@ -329,8 +329,8 @@ class NewlinePart extends BasePart implements IBasePart { } class RoomPillPart extends PillPart { - constructor(displayAlias, private room: Room) { - super(displayAlias, displayAlias); + constructor(resourceId: string, label: string, private room: Room) { + super(resourceId, label); } setAvatar(node: HTMLElement) { @@ -357,6 +357,10 @@ class RoomPillPart extends PillPart { } class AtRoomPillPart extends RoomPillPart { + constructor(text: string, room: Room) { + super(text, text, room); + } + get type(): IPillPart["type"] { return Type.AtRoomPill; } @@ -521,7 +525,7 @@ export class PartCreator { r.getAltAliases().includes(alias); }); } - return new RoomPillPart(alias, room); + return new RoomPillPart(alias, room ? room.name : alias, room); } atRoomPill(text: string) { From d8a9b84af94fac4321d700a9c9bfee29763e7fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 14:28:39 +0100 Subject: [PATCH 0115/2197] Don't show tooltip if there is nothing to display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We do this because resource is undefined for @room Signed-off-by: Šimon Brandner --- src/components/views/elements/Pill.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/Pill.js b/src/components/views/elements/Pill.js index 68a87d40b9..c6806c289e 100644 --- a/src/components/views/elements/Pill.js +++ b/src/components/views/elements/Pill.js @@ -274,7 +274,7 @@ class Pill extends React.Component { const {yOffset} = this.props; let tip; - if (this.state.hover) { + if (this.state.hover && resource) { tip = ; } From 17f09d3b7a4eb800d0d2076cc906500aeb914cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 15:16:07 +0100 Subject: [PATCH 0116/2197] Added onIsEmptyChanged prop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/SendMessageComposer.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 553fb44c04..99761ec8ba 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -117,6 +117,7 @@ export default class SendMessageComposer extends React.Component { placeholder: PropTypes.string, permalinkCreator: PropTypes.object.isRequired, replyToEvent: PropTypes.object, + onIsEmptyChanged: PropTypes.func, }; static contextType = MatrixClientContext; @@ -534,10 +535,15 @@ export default class SendMessageComposer extends React.Component { } } + onChange = () => { + this.props.onIsEmptyChanged(this.model.isEmpty); + } + render() { return (
Date: Fri, 12 Feb 2021 15:35:04 +0100 Subject: [PATCH 0117/2197] Extract send button into a function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/MessageComposer.js | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 9683c4c79e..0ea5d80c92 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -50,6 +50,22 @@ ComposerAvatar.propTypes = { me: PropTypes.object.isRequired, }; +function SendButton(props) { + return ( +
+ +
+ ); +} + +SendButton.propTypes = { + onClick: PropTypes.func.isRequired, +}; + function CallButton(props) { const onVoiceCallClick = (ev) => { dis.dispatch({ @@ -466,13 +482,9 @@ export default class MessageComposer extends React.Component { } if (this.state.showSendButton) { - controls.push(( - - )); + controls.push( + , + ); } } else if (this.state.tombstone) { const replacementRoomId = this.state.tombstone.getContent()['replacement_room']; From 42a48ee27d8e902e7f4835d8e221ae530af76570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 15:39:54 +0100 Subject: [PATCH 0118/2197] Added composerEmpty property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/MessageComposer.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 0ea5d80c92..8c13fa4dc8 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -282,6 +282,7 @@ export default class MessageComposer extends React.Component { showSendButton: SettingsStore.getValue("MessageComposerInput.showSendButton"), hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room), joinedConference: WidgetStore.instance.isJoinedToConferenceIn(this.props.room), + composerEmpty: true, }; } @@ -423,6 +424,12 @@ export default class MessageComposer extends React.Component { this.messageComposerInput._sendMessage(); } + onIsEmptyChanged = (isEmpty) => { + this.setState({ + composerEmpty: isEmpty, + }); + } + render() { const controls = [ this.state.me ? : null, @@ -448,6 +455,7 @@ export default class MessageComposer extends React.Component { resizeNotifier={this.props.resizeNotifier} permalinkCreator={this.props.permalinkCreator} replyToEvent={this.props.replyToEvent} + onIsEmptyChanged={this.onIsEmptyChanged} />, , , From 50b0a78132298fb906976b4f1e2824f1195c1fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 15:41:43 +0100 Subject: [PATCH 0119/2197] Renamed composerEmpty to isComposerEmpty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/MessageComposer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 8c13fa4dc8..7918243631 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -282,7 +282,7 @@ export default class MessageComposer extends React.Component { showSendButton: SettingsStore.getValue("MessageComposerInput.showSendButton"), hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room), joinedConference: WidgetStore.instance.isJoinedToConferenceIn(this.props.room), - composerEmpty: true, + isComposerEmpty: true, }; } @@ -426,7 +426,7 @@ export default class MessageComposer extends React.Component { onIsEmptyChanged = (isEmpty) => { this.setState({ - composerEmpty: isEmpty, + isComposerEmpty: isEmpty, }); } From 35c0cb99d04690f2465f7b4eb69aa5b006ccd7e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 15:42:30 +0100 Subject: [PATCH 0120/2197] Use isComposerEmpty for send button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/MessageComposer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 7918243631..61d61b8f42 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -489,7 +489,7 @@ export default class MessageComposer extends React.Component { } } - if (this.state.showSendButton) { + if (!this.state.isComposerEmpty) { controls.push( , ); From ba2c68819f9d2108efb6d44ff1afe73397b4feae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 15:42:59 +0100 Subject: [PATCH 0121/2197] Removed showSendButton setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/MessageComposer.js | 10 ---------- .../settings/tabs/user/PreferencesUserSettingsTab.js | 1 - src/settings/Settings.ts | 5 ----- 3 files changed, 16 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 61d61b8f42..f6fc8af55d 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -279,7 +279,6 @@ export default class MessageComposer extends React.Component { tombstone: this._getRoomTombstone(), canSendMessages: this.props.room.maySendMessage(), showCallButtons: SettingsStore.getValue("showCallButtonsInComposer"), - showSendButton: SettingsStore.getValue("MessageComposerInput.showSendButton"), hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room), joinedConference: WidgetStore.instance.isJoinedToConferenceIn(this.props.room), isComposerEmpty: true, @@ -298,12 +297,6 @@ export default class MessageComposer extends React.Component { } }; - onShowSendButtonChanged = () => { - this.setState({ - showSendButton: SettingsStore.getValue("MessageComposerInput.showSendButton"), - }); - } - _onWidgetUpdate = () => { this.setState({hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room)}); }; @@ -316,8 +309,6 @@ export default class MessageComposer extends React.Component { this.dispatcherRef = dis.register(this.onAction); MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents); this._waitForOwnMember(); - this.showSendButtonRef = SettingsStore.watchSetting( - "MessageComposerInput.showSendButton", null, this.onShowSendButtonChanged); } _waitForOwnMember() { @@ -343,7 +334,6 @@ export default class MessageComposer extends React.Component { WidgetStore.instance.removeListener(UPDATE_EVENT, this._onWidgetUpdate); ActiveWidgetStore.removeListener('update', this._onActiveWidgetUpdate); dis.unregister(this.dispatcherRef); - SettingsStore.unwatchSetting(this.showSendButtonRef); } _onRoomStateEvents(ev, state) { diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index eff0824d59..4d8493401e 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -34,7 +34,6 @@ export default class PreferencesUserSettingsTab extends React.Component { 'MessageComposerInput.suggestEmoji', 'sendTypingNotifications', 'MessageComposerInput.ctrlEnterToSend', - `MessageComposerInput.showSendButton`, ]; static TIMELINE_SETTINGS = [ diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 2d8385240c..b239b809fe 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -336,11 +336,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: isMac ? _td("Use Command + Enter to send a message") : _td("Use Ctrl + Enter to send a message"), default: false, }, - "MessageComposerInput.showSendButton": { - supportedLevels: LEVELS_ACCOUNT_SETTINGS, - displayName: _td("Show send message button"), - default: false, - }, "MessageComposerInput.autoReplaceEmoji": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Automatically replace plain text Emoji'), From daff94ecbcdfb1592c483112289412296c8720b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 15:47:33 +0100 Subject: [PATCH 0122/2197] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6baccf95de..27ec207dda 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -799,7 +799,6 @@ "Show typing notifications": "Show typing notifications", "Use Command + Enter to send a message": "Use Command + Enter to send a message", "Use Ctrl + Enter to send a message": "Use Ctrl + Enter to send a message", - "Show send message button": "Show send message button", "Automatically replace plain text Emoji": "Automatically replace plain text Emoji", "Mirror local video feed": "Mirror local video feed", "Enable Community Filter Panel": "Enable Community Filter Panel", @@ -1404,6 +1403,7 @@ "Invited": "Invited", "Filter room members": "Filter room members", "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (power %(powerLevelNumber)s)", + "Send message": "Send message", "Voice call": "Voice call", "Video call": "Video call", "Hangup": "Hangup", @@ -1413,7 +1413,6 @@ "Send a reply…": "Send a reply…", "Send an encrypted message…": "Send an encrypted message…", "Send a message…": "Send a message…", - "Send message": "Send message", "The conversation continues here.": "The conversation continues here.", "This room has been replaced and is no longer active.": "This room has been replaced and is no longer active.", "You do not have permission to post to this room": "You do not have permission to post to this room", From b26951714938a40a73f23c77db44d4a2b98769bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 15:52:42 +0100 Subject: [PATCH 0123/2197] Removed wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/MessageComposer.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index f6fc8af55d..a78cf323c6 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -52,13 +52,11 @@ ComposerAvatar.propTypes = { function SendButton(props) { return ( -
- -
+ ); } From 97f5b6920c8d25588390aa6ff2c6f9dd54b379e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 16:48:46 +0100 Subject: [PATCH 0124/2197] Check if the method is defined before calling it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/SendMessageComposer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index ca27141c02..9a14e33d05 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -538,7 +538,9 @@ export default class SendMessageComposer extends React.Component { } onChange = () => { - this.props.onIsEmptyChanged(this.model.isEmpty); + if (this.props.onIsEmptyChanged) { + this.props.onIsEmptyChanged(this.model.isEmpty); + } } render() { From 130e4f7bfddffb48d35a4a5a5adaf090d889905b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 17:06:02 +0100 Subject: [PATCH 0125/2197] Added some styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/rooms/_MessageComposer.scss | 26 +++++++++++++++++-- src/components/views/rooms/MessageComposer.js | 2 +- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index 8b34318f1d..c24e4912d4 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -247,8 +247,30 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/composer/sticker.svg'); } -.mx_MessageComposer_sendMessage::before { - mask-image: url('$(res)/img/element-icons/send-message.svg'); +.mx_MessageComposer_sendMessage { + cursor: pointer; + position: relative; + margin-right: 6px; + width: 32px; + height: 32px; + border-radius: 100%; + background-color: $button-bg-color; + + &:before { + position: absolute; + height: 16px; + width: 16px; + top: 8px; + left: 9px; + + mask-image: url('$(res)/img/element-icons/send-message.svg'); + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + + background-color: $button-fg-color; + content: ''; + } } .mx_MessageComposer_formatting { diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index d023d334c3..d70f273be2 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -53,7 +53,7 @@ ComposerAvatar.propTypes = { function SendButton(props) { return ( From 3983c15302772ebfd3bc04670bf4d3dd7b7b1762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 12 Feb 2021 17:11:24 +0100 Subject: [PATCH 0126/2197] Delint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/rooms/_MessageComposer.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index c24e4912d4..2789ffdfb7 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -256,7 +256,7 @@ limitations under the License. border-radius: 100%; background-color: $button-bg-color; - &:before { + &::before { position: absolute; height: 16px; width: 16px; From 196507a730a6e64665fbac54f2d1c682c311783d Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 12 Feb 2021 20:55:54 +0000 Subject: [PATCH 0127/2197] VoIP virtual rooms, mk II Does a thirdparty protocol lookup to the homeserver to get the corresponding native/virtual user for a matrix ID. Stores the mappings in room account data. Involves some slightly nasty workarounds for that fact that room account data has no local echo. --- src/@types/global.d.ts | 2 + src/CallHandler.tsx | 88 +++++++++--- src/SlashCommands.tsx | 4 +- src/VoipUserMapper.ts | 127 +++++++++++------- src/components/views/voip/DialPadModal.tsx | 5 +- src/dispatcher/actions.ts | 7 + src/stores/room-list/RoomListStore.ts | 37 +++++ .../room-list/filters/VisibilityProvider.ts | 8 +- test/VoipUserMapper-test.ts | 31 ----- 9 files changed, 208 insertions(+), 101 deletions(-) delete mode 100644 test/VoipUserMapper-test.ts diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 2a28c8e43f..28f22780a2 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -37,6 +37,7 @@ import CountlyAnalytics from "../CountlyAnalytics"; import UserActivity from "../UserActivity"; import {ModalWidgetStore} from "../stores/ModalWidgetStore"; import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore"; +import VoipUserMapper from "../VoipUserMapper"; declare global { interface Window { @@ -66,6 +67,7 @@ declare global { mxCountlyAnalytics: typeof CountlyAnalytics; mxUserActivity: UserActivity; mxModalWidgetStore: ModalWidgetStore; + mxVoipUserMapper: VoipUserMapper; } interface Document { diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index a6d3534fa1..ca9f96c7c2 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -84,10 +84,17 @@ import { CallError } from "matrix-js-sdk/src/webrtc/call"; import { logger } from 'matrix-js-sdk/src/logger'; import DesktopCapturerSourcePicker from "./components/views/elements/DesktopCapturerSourcePicker" import { Action } from './dispatcher/actions'; -import { roomForVirtualRoom, getOrCreateVirtualRoomForRoom } from './VoipUserMapper'; +import VoipUserMapper from './VoipUserMapper'; import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid'; -const CHECK_PSTN_SUPPORT_ATTEMPTS = 3; +export const PROTOCOL_PSTN = 'm.protocol.pstn'; +export const PROTOCOL_PSTN_PREFIXED = 'im.vector.protocol.pstn'; +export const PROTOCOL_SIP_NATIVE = 'im.vector.protocol.sip_native'; +export const PROTOCOL_SIP_VIRTUAL = 'im.vector.protocol.sip_virtual'; + +const CHECK_PROTOCOLS_ATTEMPTS = 3; +// Event type for room account data used to mark rooms as virtual rooms (and store the ID of their native room) +export const VIRTUAL_ROOM_EVENT_TYPE = 'im.vector.is_virtual_room'; enum AudioID { Ring = 'ringAudio', @@ -96,6 +103,12 @@ enum AudioID { Busy = 'busyAudio', } +interface ThirpartyLookupResponse { + userid: string, + protocol: string, + fields: {[key: string]: any}, +} + // Unlike 'CallType' in js-sdk, this one includes screen sharing // (because a screen sharing call is only a screen sharing call to the caller, // to the callee it's just a video call, at least as far as the current impl @@ -126,7 +139,12 @@ export default class CallHandler { private audioPromises = new Map>(); private dispatcherRef: string = null; private supportsPstnProtocol = null; + private pstnSupportPrefixed = null; // True if the server only support the prefixed pstn protocol + private supportsSipNativeVirtual = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native private pstnSupportCheckTimer: NodeJS.Timeout; // number actually because we're in the browser + // For rooms we've been invited to, true if they're from virtual user, false if we've checked and they aren't. + private invitedRoomsAreVirtual = new Map(); + private invitedRoomCheckInProgress = false; static sharedInstance() { if (!window.mxCallHandler) { @@ -140,9 +158,9 @@ export default class CallHandler { * Gets the user-facing room associated with a call (call.roomId may be the call "virtual room" * if a voip_mxid_translate_pattern is set in the config) */ - public static roomIdForCall(call: MatrixCall) { + public static roomIdForCall(call: MatrixCall): string { if (!call) return null; - return roomForVirtualRoom(call.roomId) || call.roomId; + return VoipUserMapper.sharedInstance().nativeRoomForVirtualRoom(call.roomId) || call.roomId; } start() { @@ -163,7 +181,7 @@ export default class CallHandler { MatrixClientPeg.get().on('Call.incoming', this.onCallIncoming); } - this.checkForPstnSupport(CHECK_PSTN_SUPPORT_ATTEMPTS); + this.checkProtocols(CHECK_PROTOCOLS_ATTEMPTS); } stop() { @@ -177,33 +195,73 @@ export default class CallHandler { } } - private async checkForPstnSupport(maxTries) { + private async checkProtocols(maxTries) { try { const protocols = await MatrixClientPeg.get().getThirdpartyProtocols(); - if (protocols['im.vector.protocol.pstn'] !== undefined) { - this.supportsPstnProtocol = protocols['im.vector.protocol.pstn']; - } else if (protocols['m.protocol.pstn'] !== undefined) { - this.supportsPstnProtocol = protocols['m.protocol.pstn']; + + if (protocols[PROTOCOL_PSTN] !== undefined) { + this.supportsPstnProtocol = Boolean(protocols[PROTOCOL_PSTN]); + if (this.supportsPstnProtocol) this.pstnSupportPrefixed = false; + } else if (protocols[PROTOCOL_PSTN_PREFIXED] !== undefined) { + this.supportsPstnProtocol = Boolean(protocols[PROTOCOL_PSTN_PREFIXED]); + if (this.supportsPstnProtocol) this.pstnSupportPrefixed = true; } else { this.supportsPstnProtocol = null; } + dis.dispatch({action: Action.PstnSupportUpdated}); + + if (protocols[PROTOCOL_SIP_NATIVE] !== undefined && protocols[PROTOCOL_SIP_VIRTUAL] !== undefined) { + this.supportsSipNativeVirtual = Boolean( + protocols[PROTOCOL_SIP_NATIVE] && protocols[PROTOCOL_SIP_VIRTUAL], + ); + } + + dis.dispatch({action: Action.VirtualRoomSupportUpdated}); } catch (e) { if (maxTries === 1) { - console.log("Failed to check for pstn protocol support and no retries remain: assuming no support", e); + console.log("Failed to check for protocol support and no retries remain: assuming no support", e); } else { - console.log("Failed to check for pstn protocol support: will retry", e); + console.log("Failed to check for protocol support: will retry", e); this.pstnSupportCheckTimer = setTimeout(() => { - this.checkForPstnSupport(maxTries - 1); + this.checkProtocols(maxTries - 1); }, 10000); } } } - getSupportsPstnProtocol() { + public getSupportsPstnProtocol() { return this.supportsPstnProtocol; } + public getSupportsVirtualRooms() { + return this.supportsPstnProtocol; + } + + public pstnLookup(phoneNumber: string): Promise { + return MatrixClientPeg.get().getThirdpartyUser( + this.pstnSupportPrefixed ? PROTOCOL_PSTN_PREFIXED : PROTOCOL_PSTN, { + 'm.id.phone': phoneNumber, + }, + ); + } + + public sipVirtualLookup(nativeMxid: string): Promise { + return MatrixClientPeg.get().getThirdpartyUser( + PROTOCOL_SIP_VIRTUAL, { + 'native_mxid': nativeMxid, + }, + ); + } + + public sipNativeLookup(virtualMxid: string): Promise { + return MatrixClientPeg.get().getThirdpartyUser( + PROTOCOL_SIP_NATIVE, { + 'virtual_mxid': virtualMxid, + }, + ); + } + private onCallIncoming = (call) => { // we dispatch this synchronously to make sure that the event // handlers on the call are set up immediately (so that if @@ -543,7 +601,7 @@ export default class CallHandler { Analytics.trackEvent('voip', 'placeCall', 'type', type); CountlyAnalytics.instance.trackStartCall(roomId, type === PlaceCallType.Video, false); - const mappedRoomId = (await getOrCreateVirtualRoomForRoom(roomId)) || roomId; + const mappedRoomId = (await VoipUserMapper.sharedInstance().getOrCreateVirtualRoomForRoom(roomId)) || roomId; logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId); const call = createNewMatrixCall(MatrixClientPeg.get(), mappedRoomId); diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index a39ad33b04..8f20754027 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -1040,9 +1040,7 @@ export const Commands = [ return success((async () => { if (isPhoneNumber) { - const results = await MatrixClientPeg.get().getThirdpartyUser('im.vector.protocol.pstn', { - 'm.id.phone': userId, - }); + const results = await CallHandler.sharedInstance().pstnLookup(this.state.value); if (!results || results.length === 0 || !results[0].userid) { throw new Error("Unable to find Matrix ID for phone number"); } diff --git a/src/VoipUserMapper.ts b/src/VoipUserMapper.ts index a4f5822065..c317402327 100644 --- a/src/VoipUserMapper.ts +++ b/src/VoipUserMapper.ts @@ -17,63 +17,96 @@ limitations under the License. import { ensureDMExists, findDMForUser } from './createRoom'; import { MatrixClientPeg } from "./MatrixClientPeg"; import DMRoomMap from "./utils/DMRoomMap"; -import SdkConfig from "./SdkConfig"; +import CallHandler, { VIRTUAL_ROOM_EVENT_TYPE } from './CallHandler'; +import RoomListStore from './stores/room-list/RoomListStore'; +import { Room } from 'matrix-js-sdk/src/models/room'; -// Functions for mapping users & rooms for the voip_mxid_translate_pattern -// config option +// Functions for mapping virtual users & rooms. Currently the only lookup +// is sip virtual: there could be others in the future. -export function voipUserMapperEnabled(): boolean { - return SdkConfig.get()['voip_mxid_translate_pattern'] !== undefined; -} +export default class VoipUserMapper { + private virtualRoomIdCache = new Set(); -// only exported for tests -export function userToVirtualUser(userId: string, templateString?: string): string { - if (templateString === undefined) templateString = SdkConfig.get()['voip_mxid_translate_pattern']; - if (!templateString) return null; - return templateString.replace('${mxid}', encodeURIComponent(userId).replace(/%/g, '=').toLowerCase()); -} + public static sharedInstance(): VoipUserMapper { + if (window.mxVoipUserMapper === undefined) window.mxVoipUserMapper = new VoipUserMapper(); + return window.mxVoipUserMapper; + } -// only exported for tests -export function virtualUserToUser(userId: string, templateString?: string): string { - if (templateString === undefined) templateString = SdkConfig.get()['voip_mxid_translate_pattern']; - if (!templateString) return null; + private async userToVirtualUser(userId: string): Promise { + const results = await CallHandler.sharedInstance().sipVirtualLookup(userId); + if (results.length === 0) return null; + return results[0].userid; + } - const regexString = templateString.replace('${mxid}', '(.+)'); + public async getOrCreateVirtualRoomForRoom(roomId: string):Promise { + const userId = DMRoomMap.shared().getUserIdForRoomId(roomId); + if (!userId) return null; - const match = userId.match('^' + regexString + '$'); - if (!match) return null; + const virtualUser = await this.userToVirtualUser(userId); + if (!virtualUser) return null; - return decodeURIComponent(match[1].replace(/=/g, '%')); -} + // There's quite a bit of acromatics here to prevent the virtual room being shown + // while it's being created: forstly, we have to stop the RoomListStore from showing + // new rooms for a bit, because we can't set the room account data to say it's a virtual + // room until we have the room ID. Secondly, once we have the new room ID, we have to + // temporarily cache the fact it's a virtual room because there's no local echo on + // room account data so it won't show up in the room model until it comes down the + // sync stream again. Ick. + RoomListStore.instance.startHoldingNewRooms(); + try { + const virtualRoomId = await ensureDMExists(MatrixClientPeg.get(), virtualUser); + MatrixClientPeg.get().setRoomAccountData(virtualRoomId, VIRTUAL_ROOM_EVENT_TYPE, { + native_room: roomId, + }); + this.virtualRoomIdCache.add(virtualRoomId); -async function getOrCreateVirtualRoomForUser(userId: string):Promise { - const virtualUser = userToVirtualUser(userId); - if (!virtualUser) return null; + return virtualRoomId; + } finally { + RoomListStore.instance.stopHoldingNewRooms(); + } + } - return await ensureDMExists(MatrixClientPeg.get(), virtualUser); -} + public nativeRoomForVirtualRoom(roomId: string):string { + const virtualRoom = MatrixClientPeg.get().getRoom(roomId); + if (!virtualRoom) return null; + const virtualRoomEvent = virtualRoom.getAccountData(VIRTUAL_ROOM_EVENT_TYPE); + if (!virtualRoomEvent || !virtualRoomEvent.getContent()) return null; + return virtualRoomEvent.getContent()['native_room'] || null; + } -export async function getOrCreateVirtualRoomForRoom(roomId: string):Promise { - const user = DMRoomMap.shared().getUserIdForRoomId(roomId); - if (!user) return null; - return getOrCreateVirtualRoomForUser(user); -} + public isVirtualRoom(roomId: string):boolean { + if (this.nativeRoomForVirtualRoom(roomId)) return true; -export function roomForVirtualRoom(roomId: string):string { - const virtualUser = DMRoomMap.shared().getUserIdForRoomId(roomId); - if (!virtualUser) return null; - const realUser = virtualUserToUser(virtualUser); - const room = findDMForUser(MatrixClientPeg.get(), realUser); - if (room) { - return room.roomId; - } else { - return null; + return this.virtualRoomIdCache.has(roomId); + } + + public async onNewInvitedRoom(invitedRoom: Room) { + const inviterId = invitedRoom.getDMInviter(); + console.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`); + const result = await CallHandler.sharedInstance().sipNativeLookup(inviterId); + if (result.length === 0) { + return true; + } + + if (result[0].fields.is_virtual) { + const nativeUser = result[0].userid; + const nativeRoom = findDMForUser(MatrixClientPeg.get(), nativeUser); + if (nativeRoom) { + // It's a virtual room with a matching native room, so set the room account data. This + // will make sure we know where how to map calls and also allow us know not to display + // it in the future. + MatrixClientPeg.get().setRoomAccountData(invitedRoom.roomId, VIRTUAL_ROOM_EVENT_TYPE, { + native_room: nativeRoom.roomId, + }); + // also auto-join the virtual room if we have a matching native room + // (possibly we should only join if we've also joined the native room, then we'd also have + // to make sure we joined virtual rooms on joining a native one) + MatrixClientPeg.get().joinRoom(invitedRoom.roomId); + } + + // also put this room in the virtual room ID cache so isVirtualRoom return the right answer + // in however long it takes for the echo of setAccountData to come down the sync + this.virtualRoomIdCache.add(invitedRoom.roomId); + } } } - -export function isVirtualRoom(roomId: string):boolean { - const virtualUser = DMRoomMap.shared().getUserIdForRoomId(roomId); - if (!virtualUser) return null; - const realUser = virtualUserToUser(virtualUser); - return Boolean(realUser); -} diff --git a/src/components/views/voip/DialPadModal.tsx b/src/components/views/voip/DialPadModal.tsx index 74b39e0721..9f031a48a3 100644 --- a/src/components/views/voip/DialPadModal.tsx +++ b/src/components/views/voip/DialPadModal.tsx @@ -24,6 +24,7 @@ import DialPad from './DialPad'; import dis from '../../../dispatcher/dispatcher'; import Modal from "../../../Modal"; import ErrorDialog from "../../views/dialogs/ErrorDialog"; +import CallHandler from "../../../CallHandler"; interface IProps { onFinished: (boolean) => void; @@ -64,9 +65,7 @@ export default class DialpadModal extends React.PureComponent { } onDialPress = async () => { - const results = await MatrixClientPeg.get().getThirdpartyUser('im.vector.protocol.pstn', { - 'm.id.phone': this.state.value, - }); + const results = await CallHandler.sharedInstance().pstnLookup(this.state.value); if (!results || results.length === 0 || !results[0].userid) { Modal.createTrackedDialog('', '', ErrorDialog, { title: _t("Unable to look up phone number"), diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index ce27f9b289..12bf4c57a3 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -106,4 +106,11 @@ export enum Action { * XXX: Is an action the right thing for this? */ PstnSupportUpdated = "pstn_support_updated", + + /** + * Similar to PstnSupportUpdated, fired when CallHandler has checked for virtual room support + * payload: none + * XXX: Ditto + */ + VirtualRoomSupportUpdated = "virtual_room_support_updated", } diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index 24a75c53e7..15e385f7e4 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -35,6 +35,7 @@ import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; import { NameFilterCondition } from "./filters/NameFilterCondition"; import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore"; import { VisibilityProvider } from "./filters/VisibilityProvider"; +import VoipUserMapper from "../../VoipUserMapper"; interface IState { tagsEnabled?: boolean; @@ -63,6 +64,9 @@ export class RoomListStoreClass extends AsyncStoreWithClient { } this.emit(LISTS_UPDATE_EVENT); }); + // When new rooms arrive, we may hold them here until we have enough info to know whether we should before display them. + private roomHoldingPen: Room[] = []; + private holdNewRooms = false; private readonly watchedSettings = [ 'feature_custom_tags', @@ -126,6 +130,24 @@ export class RoomListStoreClass extends AsyncStoreWithClient { this.updateFn.trigger(); } + // After calling this, any new rooms that appear are not displayed until stopHoldingNewRooms() + // is called. Be sure to always call this in a try/finally block to ensure stopHoldingNewRooms + // is called afterwards. + public startHoldingNewRooms() { + console.log("hold-new-rooms mode enabled."); + this.holdNewRooms = true; + } + + public stopHoldingNewRooms() { + console.log("hold-new-rooms mode disabled: processing " + this.roomHoldingPen.length + " held rooms"); + this.holdNewRooms = false; + for (const heldRoom of this.roomHoldingPen) { + console.log("Processing held room: " + heldRoom.roomId); + this.handleRoomUpdate(heldRoom, RoomUpdateCause.NewRoom); + } + this.roomHoldingPen = []; + } + private checkLoggingEnabled() { if (SettingsStore.getValue("advancedRoomListLogging")) { console.warn("Advanced room list logging is enabled"); @@ -398,6 +420,21 @@ export class RoomListStoreClass extends AsyncStoreWithClient { } private async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise { + if (cause === RoomUpdateCause.NewRoom) { + if (this.holdNewRooms) { + console.log("Room updates are held: putting room " + room.roomId + " into the holding pen"); + this.roomHoldingPen.push(room); + return; + } else { + // we call straight out to VoipUserMapper here which is a bit of a hack: probably this + // should be calling the visibility provider which in turn farms out to various visibility + // providers? Anyway, the point of this is that we delay doing anything about this room + // until the VoipUserMapper had had a chance to do the things it needs to do to decide + // if we should show this room or not. + await VoipUserMapper.sharedInstance().onNewInvitedRoom(room); + } + } + if (!VisibilityProvider.instance.isRoomVisible(room)) { return; // don't do anything on rooms that aren't visible } diff --git a/src/stores/room-list/filters/VisibilityProvider.ts b/src/stores/room-list/filters/VisibilityProvider.ts index 6bc790bb1e..45233357c1 100644 --- a/src/stores/room-list/filters/VisibilityProvider.ts +++ b/src/stores/room-list/filters/VisibilityProvider.ts @@ -15,8 +15,9 @@ */ import {Room} from "matrix-js-sdk/src/models/room"; +import CallHandler from "../../../CallHandler"; import { RoomListCustomisations } from "../../../customisations/RoomList"; -import { isVirtualRoom, voipUserMapperEnabled } from "../../../VoipUserMapper"; +import VoipUserMapper from "../../../VoipUserMapper"; export class VisibilityProvider { private static internalInstance: VisibilityProvider; @@ -35,7 +36,10 @@ export class VisibilityProvider { let isVisible = true; // Returned at the end of this function let forced = false; // When true, this function won't bother calling the customisation points - if (voipUserMapperEnabled() && isVirtualRoom(room.roomId)) { + if ( + CallHandler.sharedInstance().getSupportsVirtualRooms() && + VoipUserMapper.sharedInstance().isVirtualRoom(room.roomId) + ) { isVisible = false; forced = true; } diff --git a/test/VoipUserMapper-test.ts b/test/VoipUserMapper-test.ts deleted file mode 100644 index ee45379e4c..0000000000 --- a/test/VoipUserMapper-test.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright 2021 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 { userToVirtualUser, virtualUserToUser } from '../src/VoipUserMapper'; - -const templateString = '@_greatappservice_${mxid}:frooble.example'; -const realUser = '@alice:boop.example'; -const virtualUser = "@_greatappservice_=40alice=3aboop.example:frooble.example"; - -describe('VoipUserMapper', function() { - it('translates users to virtual users', function() { - expect(userToVirtualUser(realUser, templateString)).toEqual(virtualUser); - }); - - it('translates users to virtual users', function() { - expect(virtualUserToUser(virtualUser, templateString)).toEqual(realUser); - }); -}); From 8afb74d0e16883f24a24fa2466efafdde74c772f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 13 Feb 2021 08:53:25 +0100 Subject: [PATCH 0128/2197] Fix border radius when the panel is collapsed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/rooms/_RoomSublist.scss | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/res/css/views/rooms/_RoomSublist.scss b/res/css/views/rooms/_RoomSublist.scss index 27c7c7d0f7..92a475694e 100644 --- a/res/css/views/rooms/_RoomSublist.scss +++ b/res/css/views/rooms/_RoomSublist.scss @@ -197,6 +197,9 @@ limitations under the License. .mx_RoomSublist_resizerHandles { flex: 0 0 4px; + display: flex; + justify-content: center; + width: 100%; } // Class name comes from the ResizableBox component @@ -207,17 +210,12 @@ limitations under the License. border-radius: 3px; // Override styles from library - width: unset !important; + max-width: 64px; height: 4px !important; // Update RESIZE_HANDLE_HEIGHT if this changes // This is positioned directly below the 'show more' button. - position: absolute; + position: relative !important; bottom: 0 !important; // override from library - - // Together, these make the bar 64px wide - // These are also overridden from the library - left: calc(50% - 32px) !important; - right: calc(50% - 32px) !important; } &:hover, &.mx_RoomSublist_hasMenuOpen { From 82474074f22bedd27c69e60d53fde373ac19c0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 13 Feb 2021 13:03:26 +0100 Subject: [PATCH 0129/2197] Fix scrollbar color for non-Firefox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/themes/dark/css/_dark.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 612b8e03bd..a878aa3cdd 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -259,6 +259,11 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28); .mx_EventTile_content .markdown-body pre:hover { border-color: #808080 !important; // inverted due to rules below scrollbar-color: rgba(0, 0, 0, 0.2) transparent; // copied from light theme due to inversion below + // the code above works only in Firefox, this is for other browsers + // see https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-color + &::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); // copied from light theme due to inversion below + } } .mx_EventTile_content .markdown-body { pre, code { From 7dc6029f19502b9a450d998db5364d39b8872d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 13 Feb 2021 15:29:38 +0100 Subject: [PATCH 0130/2197] Move icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This tweaks the icon positions to look a bit better. Espacially with a scrollbar on the side Signed-off-by: Šimon Brandner --- res/css/views/rooms/_EventTile.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 42df3211de..60b7bb08d8 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -531,14 +531,14 @@ $left-gutter: 64px; display: inline-block; visibility: hidden; cursor: pointer; - top: 6px; - right: 12px; + top: 8px; + right: 8px; width: 19px; height: 19px; background-color: $message-action-bar-fg-color; } .mx_EventTile_buttonBottom { - top: 31px; + top: 33px; } .mx_EventTile_copyButton { mask-image: url($copy-button-url); From b4c5dec4e5f7e904b6eb54f767c0c3b698e0a40e Mon Sep 17 00:00:00 2001 From: Clemens Zeidler Date: Sun, 14 Feb 2021 15:56:55 +1300 Subject: [PATCH 0131/2197] Use the KeyBindingsManager for the SendMessageComposer --- src/KeyBindingsManager.ts | 73 +++++++++++++-- .../views/rooms/SendMessageComposer.js | 91 ++++++++----------- 2 files changed, 107 insertions(+), 57 deletions(-) diff --git a/src/KeyBindingsManager.ts b/src/KeyBindingsManager.ts index b9cf9749ca..c32610670d 100644 --- a/src/KeyBindingsManager.ts +++ b/src/KeyBindingsManager.ts @@ -1,17 +1,23 @@ -import { isMac } from "./Keyboard"; +import { isMac, Key } from './Keyboard'; +import SettingsStore from './settings/SettingsStore'; export enum KeyBindingContext { - + SendMessageComposer = 'SendMessageComposer', } export enum KeyAction { None = 'None', + // SendMessageComposer actions: + Send = 'Send', + SelectPrevSendHistory = 'SelectPrevSendHistory', + SelectNextSendHistory = 'SelectNextSendHistory', + EditLastMessage = 'EditLastMessage', } /** * Represent a key combination. * - * The combo is evaluated strictly, i.e. the KeyboardEvent must match the exactly what is specified in the KeyCombo. + * The combo is evaluated strictly, i.e. the KeyboardEvent must match exactly what is specified in the KeyCombo. */ export type KeyCombo = { /** Currently only one `normal` key is supported */ @@ -27,8 +33,53 @@ export type KeyCombo = { } export type KeyBinding = { - keyCombo: KeyCombo; action: KeyAction; + keyCombo: KeyCombo; +} + +const messageComposerBindings = (): KeyBinding[] => { + const bindings: KeyBinding[] = [ + { + action: KeyAction.SelectPrevSendHistory, + keyCombo: { + keys: [Key.ARROW_UP], + altKey: true, + ctrlKey: true, + }, + }, + { + action: KeyAction.SelectNextSendHistory, + keyCombo: { + keys: [Key.ARROW_DOWN], + altKey: true, + ctrlKey: true, + }, + }, + { + action: KeyAction.EditLastMessage, + keyCombo: { + keys: [Key.ARROW_UP], + } + }, + ]; + if (SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend')) { + bindings.push({ + action: KeyAction.Send, + keyCombo: { + keys: [Key.ENTER], + ctrlOrCmd: true, + }, + }); + } else { + bindings.push({ + action: KeyAction.Send, + keyCombo: { + keys: [Key.ENTER], + }, + }); + } + + return bindings; } /** @@ -75,14 +126,24 @@ export function isKeyComboMatch(ev: KeyboardEvent, combo: KeyCombo, onMac: boole return true; } +export type KeyBindingsGetter = () => KeyBinding[]; + export class KeyBindingsManager { - contextBindings: Record = {}; + /** + * Map of KeyBindingContext to a KeyBinding getter arrow function. + * + * Returning a getter function allowed to have dynamic bindings, e.g. when settings change the bindings can be + * recalculated. + */ + contextBindings: Record = { + [KeyBindingContext.SendMessageComposer]: messageComposerBindings, + }; /** * Finds a matching KeyAction for a given KeyboardEvent */ getAction(context: KeyBindingContext, ev: KeyboardEvent): KeyAction { - const bindings = this.contextBindings[context]; + const bindings = this.contextBindings[context]?.(); if (!bindings) { return KeyAction.None; } diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 62c474e417..0559c71e9e 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -48,6 +48,7 @@ import SettingsStore from "../../../settings/SettingsStore"; import CountlyAnalytics from "../../../CountlyAnalytics"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import EMOJI_REGEX from 'emojibase-regex'; +import { getKeyBindingsManager, KeyAction, KeyBindingContext } from '../../../KeyBindingsManager'; function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) { const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent); @@ -144,60 +145,48 @@ export default class SendMessageComposer extends React.Component { if (this._editorRef.isComposing(event)) { return; } - const hasModifier = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; - const ctrlEnterToSend = !!SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend'); - const send = ctrlEnterToSend - ? event.key === Key.ENTER && isOnlyCtrlOrCmdKeyEvent(event) - : event.key === Key.ENTER && !hasModifier; - if (send) { - this._sendMessage(); - event.preventDefault(); - } else if (event.key === Key.ARROW_UP) { - this.onVerticalArrow(event, true); - } else if (event.key === Key.ARROW_DOWN) { - this.onVerticalArrow(event, false); - } else if (event.key === Key.ESCAPE) { - dis.dispatch({ - action: 'reply_to_event', - event: null, - }); - } else if (this._prepareToEncrypt) { - // This needs to be last! - this._prepareToEncrypt(); + const action = getKeyBindingsManager().getAction(KeyBindingContext.MessageComposer, event); + switch (action) { + case KeyAction.Send: + this._sendMessage(); + event.preventDefault(); + break; + case KeyAction.SelectPrevSendHistory: + case KeyAction.SelectNextSendHistory: + // Try select composer history + const selected = this.selectSendHistory(action === KeyAction.SelectPrevSendHistory); + if (selected) { + // We're selecting history, so prevent the key event from doing anything else + event.preventDefault(); + } + break; + case KeyAction.EditLastMessage: + // selection must be collapsed and caret at start + if (this._editorRef.isSelectionCollapsed() && this._editorRef.isCaretAtStart()) { + const editEvent = findEditableEvent(this.props.room, false); + if (editEvent) { + // We're selecting history, so prevent the key event from doing anything else + event.preventDefault(); + dis.dispatch({ + action: 'edit_event', + event: editEvent, + }); + } + } + break; + default: + if (event.key === Key.ESCAPE) { + dis.dispatch({ + action: 'reply_to_event', + event: null, + }); + } else if (this._prepareToEncrypt) { + // This needs to be last! + this._prepareToEncrypt(); + } } }; - onVerticalArrow(e, up) { - // arrows from an initial-caret composer navigates recent messages to edit - // ctrl-alt-arrows navigate send history - if (e.shiftKey || e.metaKey) return; - - const shouldSelectHistory = e.altKey && e.ctrlKey; - const shouldEditLastMessage = !e.altKey && !e.ctrlKey && up && !this.props.replyToEvent; - - if (shouldSelectHistory) { - // Try select composer history - const selected = this.selectSendHistory(up); - if (selected) { - // We're selecting history, so prevent the key event from doing anything else - e.preventDefault(); - } - } else if (shouldEditLastMessage) { - // selection must be collapsed and caret at start - if (this._editorRef.isSelectionCollapsed() && this._editorRef.isCaretAtStart()) { - 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, - }); - } - } - } - } - // we keep sent messages/commands in a separate history (separate from undo history) // so you can alt+up/down in them selectSendHistory(up) { From 74a6c1e8d887f793cdd078d8e5634e948f5cae8c Mon Sep 17 00:00:00 2001 From: PunitLodha Date: Sun, 14 Feb 2021 18:21:12 +0530 Subject: [PATCH 0132/2197] Dont clear email if verification cancelled by user --- src/components/views/settings/account/EmailAddresses.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/settings/account/EmailAddresses.js b/src/components/views/settings/account/EmailAddresses.js index 07d2133e95..a8de7693a9 100644 --- a/src/components/views/settings/account/EmailAddresses.js +++ b/src/components/views/settings/account/EmailAddresses.js @@ -179,6 +179,7 @@ export default class EmailAddresses extends React.Component { this.setState({continueDisabled: true}); this.state.addTask.checkEmailLinkClicked().then(([finished]) => { + let newEmailAddress = this.state.newEmailAddress; if (finished) { const email = this.state.newEmailAddress; const emails = [ @@ -186,12 +187,13 @@ export default class EmailAddresses extends React.Component { { address: email, medium: "email" }, ]; this.props.onEmailsChange(emails); + newEmailAddress = ""; } this.setState({ addTask: null, continueDisabled: false, verifying: false, - newEmailAddress: "", + newEmailAddress, }); }).catch((err) => { this.setState({continueDisabled: false}); From 0902936d3973581ae0464c4299d7b692ed04878f Mon Sep 17 00:00:00 2001 From: PunitLodha Date: Sun, 14 Feb 2021 18:21:39 +0530 Subject: [PATCH 0133/2197] Add phone number only if verification is complete --- .../views/settings/account/PhoneNumbers.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/components/views/settings/account/PhoneNumbers.js b/src/components/views/settings/account/PhoneNumbers.js index 02e995ac45..df54b5ca1f 100644 --- a/src/components/views/settings/account/PhoneNumbers.js +++ b/src/components/views/settings/account/PhoneNumbers.js @@ -177,21 +177,25 @@ export default class PhoneNumbers extends React.Component { this.setState({continueDisabled: true}); const token = this.state.newPhoneNumberCode; const address = this.state.verifyMsisdn; - this.state.addTask.haveMsisdnToken(token).then(() => { + this.state.addTask.haveMsisdnToken(token).then(([finished]) => { + let newPhoneNumber = this.state.newPhoneNumber; + if (finished) { + const msisdns = [ + ...this.props.msisdns, + { address, medium: "msisdn" }, + ]; + this.props.onMsisdnsChange(msisdns); + newPhoneNumber = ""; + } this.setState({ addTask: null, continueDisabled: false, verifying: false, verifyMsisdn: "", verifyError: null, - newPhoneNumber: "", + newPhoneNumber, newPhoneNumberCode: "", }); - const msisdns = [ - ...this.props.msisdns, - { address, medium: "msisdn" }, - ]; - this.props.onMsisdnsChange(msisdns); }).catch((err) => { this.setState({continueDisabled: false}); if (err.errcode !== 'M_THREEPID_AUTH_FAILED') { From f343aaa05a16d7cb185eff9b4ba996fc841f2e6f Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Sun, 14 Feb 2021 18:37:06 +0100 Subject: [PATCH 0134/2197] fix context menu padding calculation Signed-off-by: Michael Weimann --- src/components/structures/ContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index aab7701f26..473b90ad77 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -299,7 +299,7 @@ export class ContextMenu extends React.PureComponent { // such that it does not leave the (padded) window. if (contextMenuRect) { const padding = 10; - adjusted = Math.min(position.top, document.body.clientHeight - contextMenuRect.height + padding); + adjusted = Math.min(position.top, document.body.clientHeight - contextMenuRect.height - padding); } position.top = adjusted; From 4a138f3b84f4346ebde24c37a7cf2a22e3490b8e Mon Sep 17 00:00:00 2001 From: Clemens Zeidler Date: Mon, 15 Feb 2021 19:21:08 +1300 Subject: [PATCH 0135/2197] Only support a single key in the KeyCombo Keep it simple... --- src/KeyBindingsManager.ts | 15 +++++++-------- test/KeyBindingsManager-test.ts | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/KeyBindingsManager.ts b/src/KeyBindingsManager.ts index c32610670d..030cd94e99 100644 --- a/src/KeyBindingsManager.ts +++ b/src/KeyBindingsManager.ts @@ -20,8 +20,7 @@ export enum KeyAction { * The combo is evaluated strictly, i.e. the KeyboardEvent must match exactly what is specified in the KeyCombo. */ export type KeyCombo = { - /** Currently only one `normal` key is supported */ - keys: string[]; + key?: string; /** On PC: ctrl is pressed; on Mac: meta is pressed */ ctrlOrCmd?: boolean; @@ -42,7 +41,7 @@ const messageComposerBindings = (): KeyBinding[] => { { action: KeyAction.SelectPrevSendHistory, keyCombo: { - keys: [Key.ARROW_UP], + key: Key.ARROW_UP, altKey: true, ctrlKey: true, }, @@ -50,7 +49,7 @@ const messageComposerBindings = (): KeyBinding[] => { { action: KeyAction.SelectNextSendHistory, keyCombo: { - keys: [Key.ARROW_DOWN], + key: Key.ARROW_DOWN, altKey: true, ctrlKey: true, }, @@ -58,7 +57,7 @@ const messageComposerBindings = (): KeyBinding[] => { { action: KeyAction.EditLastMessage, keyCombo: { - keys: [Key.ARROW_UP], + key: Key.ARROW_UP, } }, ]; @@ -66,7 +65,7 @@ const messageComposerBindings = (): KeyBinding[] => { bindings.push({ action: KeyAction.Send, keyCombo: { - keys: [Key.ENTER], + key: Key.ENTER, ctrlOrCmd: true, }, }); @@ -74,7 +73,7 @@ const messageComposerBindings = (): KeyBinding[] => { bindings.push({ action: KeyAction.Send, keyCombo: { - keys: [Key.ENTER], + key: Key.ENTER, }, }); } @@ -88,7 +87,7 @@ const messageComposerBindings = (): KeyBinding[] => { * Note, this method is only exported for testing. */ export function isKeyComboMatch(ev: KeyboardEvent, combo: KeyCombo, onMac: boolean): boolean { - if (combo.keys.length > 0 && ev.key !== combo.keys[0]) { + if (combo.key !== undefined && ev.key !== combo.key) { return false; } diff --git a/test/KeyBindingsManager-test.ts b/test/KeyBindingsManager-test.ts index f272878658..28204be9c8 100644 --- a/test/KeyBindingsManager-test.ts +++ b/test/KeyBindingsManager-test.ts @@ -19,7 +19,7 @@ function mockKeyEvent(key: string, modifiers?: { describe('KeyBindingsManager', () => { it('should match basic key combo', () => { const combo1: KeyCombo = { - keys: ['k'], + key: 'k', }; assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo1, false), true); assert.strictEqual(isKeyComboMatch(mockKeyEvent('n'), combo1, false), false); @@ -28,7 +28,7 @@ describe('KeyBindingsManager', () => { it('should match key + modifier key combo', () => { const combo: KeyCombo = { - keys: ['k'], + key: 'k', ctrlKey: true, }; assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false), true); @@ -38,7 +38,7 @@ describe('KeyBindingsManager', () => { assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true, metaKey: true }), combo, false), false); const combo2: KeyCombo = { - keys: ['k'], + key: 'k', metaKey: true, }; assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo2, false), true); @@ -47,7 +47,7 @@ describe('KeyBindingsManager', () => { assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { altKey: true, metaKey: true }), combo2, false), false); const combo3: KeyCombo = { - keys: ['k'], + key: 'k', altKey: true, }; assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { altKey: true }), combo3, false), true); @@ -56,7 +56,7 @@ describe('KeyBindingsManager', () => { assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo3, false), false); const combo4: KeyCombo = { - keys: ['k'], + key: 'k', shiftKey: true, }; assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true }), combo4, false), true); @@ -67,7 +67,7 @@ describe('KeyBindingsManager', () => { it('should match key + multiple modifiers key combo', () => { const combo: KeyCombo = { - keys: ['k'], + key: 'k', ctrlKey: true, altKey: true, }; @@ -78,7 +78,7 @@ describe('KeyBindingsManager', () => { false), false); const combo2: KeyCombo = { - keys: ['k'], + key: 'k', ctrlKey: true, shiftKey: true, altKey: true, @@ -92,7 +92,7 @@ describe('KeyBindingsManager', () => { { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo2, false), false); const combo3: KeyCombo = { - keys: ['k'], + key: 'k', ctrlKey: true, shiftKey: true, altKey: true, @@ -108,7 +108,7 @@ describe('KeyBindingsManager', () => { it('should match ctrlOrMeta key combo', () => { const combo: KeyCombo = { - keys: ['k'], + key: 'k', ctrlOrCmd: true, }; // PC: @@ -123,7 +123,7 @@ describe('KeyBindingsManager', () => { it('should match advanced ctrlOrMeta key combo', () => { const combo: KeyCombo = { - keys: ['k'], + key: 'k', ctrlOrCmd: true, altKey: true, }; From d73648dfb03594e0a1a8e9772b9b15fd8cdcbe20 Mon Sep 17 00:00:00 2001 From: Jaiwanth Date: Mon, 15 Feb 2021 17:46:32 +0530 Subject: [PATCH 0136/2197] Modified regex to account for an immediate new line after slash commands Signed-off-by: Jaiwanth --- src/SlashCommands.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index a39ad33b04..38e5a95f7f 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -1182,7 +1182,7 @@ export function parseCommandString(input: string) { input = input.replace(/\s+$/, ''); if (input[0] !== '/') return {}; // not a command - const bits = input.match(/^(\S+?)(?: +((.|\n)*))?$/); + const bits = input.match(/^(\S+?)(?:[ \n]+((.|\n)*))?$/); let cmd; let args; if (bits) { From f420c8598516b4f939b7ab90f5fa263db2aaa94a Mon Sep 17 00:00:00 2001 From: Jaiwanth Date: Mon, 15 Feb 2021 19:13:09 +0530 Subject: [PATCH 0137/2197] Added invite option to room's context menu Signed-off-by: Jaiwanth --- res/css/views/rooms/_RoomTile.scss | 4 ++++ src/components/views/rooms/RoomTile.tsx | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 8eca3f1efa..377b207490 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -188,6 +188,10 @@ limitations under the License. .mx_RoomTile_iconSettings::before { mask-image: url('$(res)/img/element-icons/settings.svg'); } + + .mx_RoomTile_iconInvite::before { + mask-image: url('$(res)/img/element-icons/room/invite.svg'); + } .mx_RoomTile_iconSignOut::before { mask-image: url('$(res)/img/element-icons/leave.svg'); diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index 835447dc18..3a34a0daaa 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -331,6 +331,17 @@ export default class RoomTile extends React.PureComponent { this.setState({generalMenuPosition: null}); // hide the menu }; + private onInviteClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + dis.dispatch({ + action: 'view_invite', + roomId: this.props.room.roomId, + }); + this.setState({generalMenuPosition: null}); // hide the menu + }; + private async saveNotifState(ev: ButtonEvent, newState: Volume) { ev.preventDefault(); ev.stopPropagation(); @@ -470,7 +481,11 @@ export default class RoomTile extends React.PureComponent { label={lowPriorityLabel} iconClassName="mx_RoomTile_iconArrowDown" /> - + Date: Mon, 15 Feb 2021 15:04:01 +0000 Subject: [PATCH 0138/2197] Add missing 'd' --- src/CallHandler.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index ca9f96c7c2..6cebae1093 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -103,7 +103,7 @@ enum AudioID { Busy = 'busyAudio', } -interface ThirpartyLookupResponse { +interface ThirdpartyLookupResponse { userid: string, protocol: string, fields: {[key: string]: any}, @@ -238,7 +238,7 @@ export default class CallHandler { return this.supportsPstnProtocol; } - public pstnLookup(phoneNumber: string): Promise { + public pstnLookup(phoneNumber: string): Promise { return MatrixClientPeg.get().getThirdpartyUser( this.pstnSupportPrefixed ? PROTOCOL_PSTN_PREFIXED : PROTOCOL_PSTN, { 'm.id.phone': phoneNumber, @@ -246,7 +246,7 @@ export default class CallHandler { ); } - public sipVirtualLookup(nativeMxid: string): Promise { + public sipVirtualLookup(nativeMxid: string): Promise { return MatrixClientPeg.get().getThirdpartyUser( PROTOCOL_SIP_VIRTUAL, { 'native_mxid': nativeMxid, @@ -254,7 +254,7 @@ export default class CallHandler { ); } - public sipNativeLookup(virtualMxid: string): Promise { + public sipNativeLookup(virtualMxid: string): Promise { return MatrixClientPeg.get().getThirdpartyUser( PROTOCOL_SIP_NATIVE, { 'virtual_mxid': virtualMxid, From 554dfdb6bf58a82f9f09b047f2cca6835a733d57 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 15 Feb 2021 15:05:01 +0000 Subject: [PATCH 0139/2197] Typo Co-authored-by: J. Ryan Stinnett --- src/VoipUserMapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VoipUserMapper.ts b/src/VoipUserMapper.ts index c317402327..bf592de001 100644 --- a/src/VoipUserMapper.ts +++ b/src/VoipUserMapper.ts @@ -45,7 +45,7 @@ export default class VoipUserMapper { const virtualUser = await this.userToVirtualUser(userId); if (!virtualUser) return null; - // There's quite a bit of acromatics here to prevent the virtual room being shown + // There's quite a bit of acrobatics here to prevent the virtual room being shown // while it's being created: forstly, we have to stop the RoomListStore from showing // new rooms for a bit, because we can't set the room account data to say it's a virtual // room until we have the room ID. Secondly, once we have the new room ID, we have to From 65ef4f2f221fce653828a636131ebe202950f2e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 15 Feb 2021 16:05:14 +0100 Subject: [PATCH 0140/2197] Removed the commented out code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../tabs/user/AppearanceUserSettingsTab.tsx | 56 ------------------- 1 file changed, 56 deletions(-) diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index 5d3596911f..80a20d8afa 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -28,13 +28,11 @@ import { FontWatcher } from "../../../../../settings/watchers/FontWatcher"; import { RecheckThemePayload } from '../../../../../dispatcher/payloads/RecheckThemePayload'; import { Action } from '../../../../../dispatcher/actions'; import { IValidationResult, IFieldState } from '../../../elements/Validation'; -//import StyledRadioButton from '../../../elements/StyledRadioButton'; import StyledCheckbox from '../../../elements/StyledCheckbox'; import SettingsFlag from '../../../elements/SettingsFlag'; import Field from '../../../elements/Field'; import EventTilePreview from '../../../elements/EventTilePreview'; import StyledRadioGroup from "../../../elements/StyledRadioGroup"; -//import classNames from 'classnames'; import { SettingLevel } from "../../../../../settings/SettingLevel"; import {UIFeature} from "../../../../../settings/UIFeature"; import {Layout} from "../../../../../settings/Layout"; @@ -214,16 +212,6 @@ export default class AppearanceUserSettingsTab extends React.Component): void => { - const val = e.target.value === "true"; - - this.setState({ - useIRCLayout: val, - }); - - SettingsStore.setValue("useIRCLayout", null, SettingLevel.DEVICE, val); - };*/ - private onIRCLayoutChange = (enabled: boolean) => { if (enabled) { this.setState({layout: Layout.IRC}); @@ -353,50 +341,6 @@ export default class AppearanceUserSettingsTab extends React.Component; } - /*private renderLayoutSection = () => { - return
- {_t("Message layout")} - -
-
- - - {_t("Compact")} - -
-
-
- - - {_t("Modern")} - -
-
-
; - };*/ - private renderAdvancedSection() { if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null; From 0b574327d77e267422eb9929358315e8810c52f9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 15 Feb 2021 15:05:16 +0000 Subject: [PATCH 0141/2197] More typo Co-authored-by: J. Ryan Stinnett --- src/VoipUserMapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VoipUserMapper.ts b/src/VoipUserMapper.ts index bf592de001..ceb87c1829 100644 --- a/src/VoipUserMapper.ts +++ b/src/VoipUserMapper.ts @@ -46,7 +46,7 @@ export default class VoipUserMapper { if (!virtualUser) return null; // There's quite a bit of acrobatics here to prevent the virtual room being shown - // while it's being created: forstly, we have to stop the RoomListStore from showing + // while it's being created: firstly, we have to stop the RoomListStore from showing // new rooms for a bit, because we can't set the room account data to say it's a virtual // room until we have the room ID. Secondly, once we have the new room ID, we have to // temporarily cache the fact it's a virtual room because there's no local echo on From 064afab239adec142ee8c410378f2fb7adbecbcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 15 Feb 2021 16:06:38 +0100 Subject: [PATCH 0142/2197] Use LEVELS_ACCOUNT_SETTINGS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/settings/Settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 3651e43d7d..d7d5ab8490 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -625,7 +625,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: 80, }, "layout": { - supportedLevels: LEVELS_ROOM_SETTINGS_WITH_ROOM, + supportedLevels: LEVELS_ACCOUNT_SETTINGS, default: Layout.Group, }, "showChatEffects": { From d339dc447f83ab82c9f135e355eb140e237f0678 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 15 Feb 2021 15:25:07 +0000 Subject: [PATCH 0143/2197] Add specific fields to third party lookup response fields --- src/CallHandler.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 6cebae1093..63b849af4b 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -103,10 +103,27 @@ enum AudioID { Busy = 'busyAudio', } +interface ThirdpartyLookupResponseFields { + /* eslint-disable camelcase */ + + // im.vector.sip_native + virtual_mxid: string; + is_virtual: boolean; + + // im.vector.sip_virtual + native_mxid: string; + is_native: boolean; + + // common + lookup_success: boolean; + + /* eslint-enable camelcase */ +} + interface ThirdpartyLookupResponse { userid: string, protocol: string, - fields: {[key: string]: any}, + fields: ThirdpartyLookupResponseFields, } // Unlike 'CallType' in js-sdk, this one includes screen sharing From 04d2bf1d6a5b6fae7672dccd29fc85e8b5145bd3 Mon Sep 17 00:00:00 2001 From: Jaiwanth Date: Mon, 15 Feb 2021 20:52:19 +0530 Subject: [PATCH 0144/2197] Check whether user has permission to invite Signed-off-by: Jaiwanth --- res/css/views/rooms/_RoomTile.scss | 2 +- src/components/views/rooms/RoomTile.tsx | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 377b207490..72d29dfd4c 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -188,7 +188,7 @@ limitations under the License. .mx_RoomTile_iconSettings::before { mask-image: url('$(res)/img/element-icons/settings.svg'); } - + .mx_RoomTile_iconInvite::before { mask-image: url('$(res)/img/element-icons/room/invite.svg'); } diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index 3a34a0daaa..f168235335 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -462,6 +462,16 @@ export default class RoomTile extends React.PureComponent { const isLowPriority = roomTags.includes(DefaultTagID.LowPriority); const lowPriorityLabel = _t("Low Priority"); + const inRoom = this.props.room && this.props.room.getMyMembership() === "join"; + const userId = MatrixClientPeg.get().getUserId(); + let canInvite = inRoom; + const powerLevels = this.props.room.currentState + .getStateEvents("m.room.power_levels", "") + ?.getContent(); + const me = this.props.room.getMember(userId); + if (powerLevels && me && powerLevels.invite > me.powerLevel) { + canInvite = false; + } contextMenu = { label={lowPriorityLabel} iconClassName="mx_RoomTile_iconArrowDown" /> - + {canInvite ? ( + + ) : null} Date: Mon, 15 Feb 2021 08:53:37 -0700 Subject: [PATCH 0145/2197] Use randomly generated conference names for Jitsi Fixes https://github.com/vector-im/element-web/issues/15205 Replaces https://github.com/matrix-org/matrix-react-sdk/pull/5322 This change uses a random ID for the Jitsi conference name to avoid any badly-named Jitsi calls, and overrides the "subject" (title) of the call so end users aren't typically exposed to the random ID. --- package.json | 1 - src/CallHandler.tsx | 5 +++-- src/utils/NamingUtils.ts | 29 ----------------------------- src/utils/WidgetUtils.ts | 1 + yarn.lock | 13 ------------- 5 files changed, 4 insertions(+), 45 deletions(-) delete mode 100644 src/utils/NamingUtils.ts diff --git a/package.json b/package.json index 2263c7de32..61c8cb7a2c 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,6 @@ "pako": "^2.0.3", "parse5": "^6.0.1", "png-chunks-extract": "^1.0.0", - "project-name-generator": "^2.1.9", "prop-types": "^15.7.2", "qrcode": "^1.4.4", "qs": "^6.9.6", diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 41a5941092..e09a47cf97 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -64,7 +64,6 @@ import dis from './dispatcher/dispatcher'; import WidgetUtils from './utils/WidgetUtils'; import WidgetEchoStore from './stores/WidgetEchoStore'; import SettingsStore from './settings/SettingsStore'; -import {generateHumanReadableId} from "./utils/NamingUtils"; import {Jitsi} from "./widgets/Jitsi"; import {WidgetType} from "./widgets/WidgetType"; import {SettingLevel} from "./settings/SettingLevel"; @@ -86,6 +85,7 @@ import DesktopCapturerSourcePicker from "./components/views/elements/DesktopCapt import { Action } from './dispatcher/actions'; import { roomForVirtualRoom, getOrCreateVirtualRoomForRoom } from './VoipUserMapper'; import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid'; +import { randomString } from "matrix-js-sdk/lib/randomstring"; const CHECK_PSTN_SUPPORT_ATTEMPTS = 3; @@ -780,7 +780,7 @@ export default class CallHandler { confId = base32.stringify(Buffer.from(roomId), { pad: false }); } else { // Create a random human readable conference ID - confId = `JitsiConference${generateHumanReadableId()}`; + confId = `JitsiConference${randomString(32)}`; } let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl({auth: jitsiAuth}); @@ -796,6 +796,7 @@ export default class CallHandler { isAudioOnly: type === 'voice', domain: jitsiDomain, auth: jitsiAuth, + roomName: room.name, }; const widgetId = ( diff --git a/src/utils/NamingUtils.ts b/src/utils/NamingUtils.ts deleted file mode 100644 index 44ccb9b030..0000000000 --- a/src/utils/NamingUtils.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2020 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 * as projectNameGenerator from "project-name-generator"; - -/** - * Generates a human readable identifier. This should not be used for anything - * which needs secure/cryptographic random: just a level uniquness that is offered - * by something like Date.now(). - * @returns {string} The randomly generated ID - */ -export function generateHumanReadableId(): string { - return projectNameGenerator({words: 3}).raw.map(w => { - return w[0].toUpperCase() + w.substring(1).toLowerCase(); - }).join(''); -} diff --git a/src/utils/WidgetUtils.ts b/src/utils/WidgetUtils.ts index db2b10b295..c67f3bad13 100644 --- a/src/utils/WidgetUtils.ts +++ b/src/utils/WidgetUtils.ts @@ -477,6 +477,7 @@ export default class WidgetUtils { 'userId=$matrix_user_id', 'roomId=$matrix_room_id', 'theme=$theme', + 'roomName=$roomName', ]; if (opts.auth) { queryStringParts.push(`auth=${opts.auth}`); diff --git a/yarn.lock b/yarn.lock index 1a350f697a..9bd51f16a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2477,11 +2477,6 @@ commander@^4.0.1: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -commander@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" - integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== - commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -6453,14 +6448,6 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -project-name-generator@^2.1.9: - version "2.1.9" - resolved "https://registry.yarnpkg.com/project-name-generator/-/project-name-generator-2.1.9.tgz#959177f1feb2355d74fa98745d72a65a5f75b674" - integrity sha512-QmLHqz2C4VHmAyDEAFlVfnuWAHr4vwZhK2bbm4IrwuHNzNKOdG9b4U+NmQbsm1uOoV4kGWv7+FVLsu7Bb/ieYQ== - dependencies: - commander "^6.1.0" - lodash "^4.17.20" - promise@^7.0.3, promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" From cb757b5b50a645932547a94f7b46d851ea34e9c1 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 15 Feb 2021 08:56:38 -0700 Subject: [PATCH 0146/2197] Fix import --- src/CallHandler.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index e09a47cf97..f106d75f8b 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -85,7 +85,7 @@ import DesktopCapturerSourcePicker from "./components/views/elements/DesktopCapt import { Action } from './dispatcher/actions'; import { roomForVirtualRoom, getOrCreateVirtualRoomForRoom } from './VoipUserMapper'; import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid'; -import { randomString } from "matrix-js-sdk/lib/randomstring"; +import { randomString } from "matrix-js-sdk/src/randomstring"; const CHECK_PSTN_SUPPORT_ATTEMPTS = 3; From d96043d5ab3e246cec1f6891352066ed2782a932 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 15 Feb 2021 17:34:28 +0000 Subject: [PATCH 0147/2197] Use config for host signup branding --- res/css/structures/_UserMenu.scss | 2 +- src/components/structures/HostSignupAction.tsx | 13 ++++++++++++- src/i18n/strings/en_EN.json | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index f2577938e5..2a4453df70 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -128,7 +128,7 @@ limitations under the License. } .mx_UserMenu_contextMenu { - width: 247px; + width: 258px; // These override the styles already present on the user menu rather than try to // define a new menu. They are specifically for the stacked menu when a community diff --git a/src/components/structures/HostSignupAction.tsx b/src/components/structures/HostSignupAction.tsx index 39185acd05..9cf84a9379 100644 --- a/src/components/structures/HostSignupAction.tsx +++ b/src/components/structures/HostSignupAction.tsx @@ -21,6 +21,7 @@ import { } from "../views/context_menus/IconizedContextMenu"; import { _t } from "../../languageHandler"; import { HostSignupStore } from "../../stores/HostSignupStore"; +import SdkConfig from "../../SdkConfig"; interface IProps {} @@ -32,11 +33,21 @@ export default class HostSignupAction extends React.PureComponent diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a9d31bb9f2..d023984f7d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2450,7 +2450,7 @@ "Send a Direct Message": "Send a Direct Message", "Explore Public Rooms": "Explore Public Rooms", "Create a Group Chat": "Create a Group Chat", - "Upgrade to pro": "Upgrade to pro", + "Upgrade to %(hostSignupBrand)s": "Upgrade to %(hostSignupBrand)s", "Explore rooms": "Explore rooms", "Failed to reject invitation": "Failed to reject invitation", "Cannot create rooms in this community": "Cannot create rooms in this community", From 234f1bf2c47369c86d8c1a437c959dff828a1cb5 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 15 Feb 2021 17:38:11 +0000 Subject: [PATCH 0148/2197] Test domains in config directly --- src/components/structures/UserMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 5b34d36d09..7f96e2d142 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -300,7 +300,7 @@ export default class UserMenu extends React.Component { const hostSignupDomains = hostSignupConfig.domains || []; const mxDomain = MatrixClientPeg.get().getDomain(); const validDomains = hostSignupDomains.filter(d => (d === mxDomain || mxDomain.endsWith(`.${d}`))); - if (!hostSignupDomains || validDomains.length > 0) { + if (!hostSignupConfig.domains || validDomains.length > 0) { topSection =
; From 3919fcd80e6f709ae74ec4cf7b93d36ea3d2f651 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 15 Feb 2021 17:34:28 +0000 Subject: [PATCH 0149/2197] Use config for host signup branding --- res/css/structures/_UserMenu.scss | 2 +- src/components/structures/HostSignupAction.tsx | 13 ++++++++++++- src/i18n/strings/en_EN.json | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index f2577938e5..2a4453df70 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -128,7 +128,7 @@ limitations under the License. } .mx_UserMenu_contextMenu { - width: 247px; + width: 258px; // These override the styles already present on the user menu rather than try to // define a new menu. They are specifically for the stacked menu when a community diff --git a/src/components/structures/HostSignupAction.tsx b/src/components/structures/HostSignupAction.tsx index 39185acd05..9cf84a9379 100644 --- a/src/components/structures/HostSignupAction.tsx +++ b/src/components/structures/HostSignupAction.tsx @@ -21,6 +21,7 @@ import { } from "../views/context_menus/IconizedContextMenu"; import { _t } from "../../languageHandler"; import { HostSignupStore } from "../../stores/HostSignupStore"; +import SdkConfig from "../../SdkConfig"; interface IProps {} @@ -32,11 +33,21 @@ export default class HostSignupAction extends React.PureComponent diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a9d31bb9f2..d023984f7d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2450,7 +2450,7 @@ "Send a Direct Message": "Send a Direct Message", "Explore Public Rooms": "Explore Public Rooms", "Create a Group Chat": "Create a Group Chat", - "Upgrade to pro": "Upgrade to pro", + "Upgrade to %(hostSignupBrand)s": "Upgrade to %(hostSignupBrand)s", "Explore rooms": "Explore rooms", "Failed to reject invitation": "Failed to reject invitation", "Cannot create rooms in this community": "Cannot create rooms in this community", From b2834d7f919ff655ad1a85fcc8931d1ffd40260b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 15 Feb 2021 17:38:11 +0000 Subject: [PATCH 0150/2197] Test domains in config directly --- src/components/structures/UserMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 5b34d36d09..7f96e2d142 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -300,7 +300,7 @@ export default class UserMenu extends React.Component { const hostSignupDomains = hostSignupConfig.domains || []; const mxDomain = MatrixClientPeg.get().getDomain(); const validDomains = hostSignupDomains.filter(d => (d === mxDomain || mxDomain.endsWith(`.${d}`))); - if (!hostSignupDomains || validDomains.length > 0) { + if (!hostSignupConfig.domains || validDomains.length > 0) { topSection =
; From 89b2dae035f33728c45a8193d234d688b6ba209b Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 15 Feb 2021 18:13:13 +0000 Subject: [PATCH 0151/2197] Send onNewInvitedRoom via VisibilityProvider --- src/stores/room-list/RoomListStore.ts | 12 ++++++------ src/stores/room-list/filters/VisibilityProvider.ts | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index 15e385f7e4..a050442eae 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -426,12 +426,12 @@ export class RoomListStoreClass extends AsyncStoreWithClient { this.roomHoldingPen.push(room); return; } else { - // we call straight out to VoipUserMapper here which is a bit of a hack: probably this - // should be calling the visibility provider which in turn farms out to various visibility - // providers? Anyway, the point of this is that we delay doing anything about this room - // until the VoipUserMapper had had a chance to do the things it needs to do to decide - // if we should show this room or not. - await VoipUserMapper.sharedInstance().onNewInvitedRoom(room); + // Let the visibility provider know that there is a new invited room. It would be nice + // if this could just be an event that things listen for but the point of this is that + // we delay doing anything about this room until the VoipUserMapper had had a chance + // to do the things it needs to do to decide if we should show this room or not, so + // an even wouldn't et us do that. + await VisibilityProvider.instance.onNewInvitedRoom(room); } } diff --git a/src/stores/room-list/filters/VisibilityProvider.ts b/src/stores/room-list/filters/VisibilityProvider.ts index 45233357c1..2239f9e1ac 100644 --- a/src/stores/room-list/filters/VisibilityProvider.ts +++ b/src/stores/room-list/filters/VisibilityProvider.ts @@ -32,6 +32,10 @@ export class VisibilityProvider { return VisibilityProvider.internalInstance; } + public async onNewInvitedRoom(room: Room) { + await VoipUserMapper.sharedInstance().onNewInvitedRoom(room); + } + public isRoomVisible(room: Room): boolean { let isVisible = true; // Returned at the end of this function let forced = false; // When true, this function won't bother calling the customisation points From 79455d99b477d42f1738120780a34f83e903b1a2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 15 Feb 2021 19:38:17 +0000 Subject: [PATCH 0152/2197] Unused import --- src/stores/room-list/RoomListStore.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts index a050442eae..f420dd2b08 100644 --- a/src/stores/room-list/RoomListStore.ts +++ b/src/stores/room-list/RoomListStore.ts @@ -35,7 +35,6 @@ import { AsyncStoreWithClient } from "../AsyncStoreWithClient"; import { NameFilterCondition } from "./filters/NameFilterCondition"; import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore"; import { VisibilityProvider } from "./filters/VisibilityProvider"; -import VoipUserMapper from "../../VoipUserMapper"; interface IState { tagsEnabled?: boolean; From 12387b497862393c286eedc2c31466ae69b10c83 Mon Sep 17 00:00:00 2001 From: Clemens Zeidler Date: Tue, 16 Feb 2021 19:05:39 +1300 Subject: [PATCH 0153/2197] Use the KeyBindingsManager in EditMessageComposer --- src/KeyBindingsManager.ts | 40 +++++++++--- .../views/rooms/EditMessageComposer.js | 64 ++++++++++--------- 2 files changed, 65 insertions(+), 39 deletions(-) diff --git a/src/KeyBindingsManager.ts b/src/KeyBindingsManager.ts index 030cd94e99..b411c7ff27 100644 --- a/src/KeyBindingsManager.ts +++ b/src/KeyBindingsManager.ts @@ -2,21 +2,33 @@ import { isMac, Key } from './Keyboard'; import SettingsStore from './settings/SettingsStore'; export enum KeyBindingContext { - SendMessageComposer = 'SendMessageComposer', + /** Key bindings for the chat message composer component */ + MessageComposer = 'MessageComposer', } export enum KeyAction { None = 'None', + // SendMessageComposer actions: + + /** Send a message */ Send = 'Send', + /** Go backwards through the send history and use the message in composer view */ SelectPrevSendHistory = 'SelectPrevSendHistory', + /** Go forwards through the send history */ SelectNextSendHistory = 'SelectNextSendHistory', - EditLastMessage = 'EditLastMessage', + /** Start editing the user's last sent message */ + EditPrevMessage = 'EditPrevMessage', + /** Start editing the user's next sent message */ + EditNextMessage = 'EditNextMessage', + + /** Cancel editing a message */ + CancelEditing = 'CancelEditing', } /** * Represent a key combination. - * + * * The combo is evaluated strictly, i.e. the KeyboardEvent must match exactly what is specified in the KeyCombo. */ export type KeyCombo = { @@ -55,10 +67,22 @@ const messageComposerBindings = (): KeyBinding[] => { }, }, { - action: KeyAction.EditLastMessage, + action: KeyAction.EditPrevMessage, keyCombo: { key: Key.ARROW_UP, - } + }, + }, + { + action: KeyAction.EditNextMessage, + keyCombo: { + key: Key.ARROW_DOWN, + }, + }, + { + action: KeyAction.CancelEditing, + keyCombo: { + key: Key.ESCAPE, + }, }, ]; if (SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend')) { @@ -83,7 +107,7 @@ const messageComposerBindings = (): KeyBinding[] => { /** * Helper method to check if a KeyboardEvent matches a KeyCombo - * + * * Note, this method is only exported for testing. */ export function isKeyComboMatch(ev: KeyboardEvent, combo: KeyCombo, onMac: boolean): boolean { @@ -130,12 +154,12 @@ export type KeyBindingsGetter = () => KeyBinding[]; export class KeyBindingsManager { /** * Map of KeyBindingContext to a KeyBinding getter arrow function. - * + * * Returning a getter function allowed to have dynamic bindings, e.g. when settings change the bindings can be * recalculated. */ contextBindings: Record = { - [KeyBindingContext.SendMessageComposer]: messageComposerBindings, + [KeyBindingContext.MessageComposer]: messageComposerBindings, }; /** diff --git a/src/components/views/rooms/EditMessageComposer.js b/src/components/views/rooms/EditMessageComposer.js index c59b3555b9..8aa637f680 100644 --- a/src/components/views/rooms/EditMessageComposer.js +++ b/src/components/views/rooms/EditMessageComposer.js @@ -29,11 +29,10 @@ import EditorStateTransfer from '../../../utils/EditorStateTransfer'; import classNames from 'classnames'; import {EventStatus} from 'matrix-js-sdk'; import BasicMessageComposer from "./BasicMessageComposer"; -import {Key, isOnlyCtrlOrCmdKeyEvent} from "../../../Keyboard"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {Action} from "../../../dispatcher/actions"; -import SettingsStore from "../../../settings/SettingsStore"; import CountlyAnalytics from "../../../CountlyAnalytics"; +import {getKeyBindingsManager, KeyAction, KeyBindingContext} from '../../../KeyBindingsManager'; function _isReply(mxEvent) { const relatesTo = mxEvent.getContent()["m.relates_to"]; @@ -134,38 +133,41 @@ export default class EditMessageComposer extends React.Component { if (this._editorRef.isComposing(event)) { return; } - if (event.metaKey || event.altKey || event.shiftKey) { - return; - } - const ctrlEnterToSend = !!SettingsStore.getValue('MessageComposerInput.ctrlEnterToSend'); - const send = ctrlEnterToSend ? event.key === Key.ENTER && isOnlyCtrlOrCmdKeyEvent(event) - : event.key === Key.ENTER; - if (send) { - this._sendEdit(); - event.preventDefault(); - } else if (event.key === Key.ESCAPE) { - this._cancelEdit(); - } else if (event.key === Key.ARROW_UP) { - if (this._editorRef.isModified() || !this._editorRef.isCaretAtStart()) { - return; - } - const previousEvent = findEditableEvent(this._getRoom(), false, this.props.editState.getEvent().getId()); - if (previousEvent) { - dis.dispatch({action: 'edit_event', event: previousEvent}); + const action = getKeyBindingsManager().getAction(KeyBindingContext.MessageComposer, event); + switch (action) { + case KeyAction.Send: + this._sendEdit(); event.preventDefault(); + break; + case KeyAction.CancelEditing: + this._cancelEdit(); + break; + case KeyAction.EditPrevMessage: { + if (this._editorRef.isModified() || !this._editorRef.isCaretAtStart()) { + return; + } + const previousEvent = findEditableEvent(this._getRoom(), false, + this.props.editState.getEvent().getId()); + if (previousEvent) { + dis.dispatch({action: 'edit_event', event: previousEvent}); + event.preventDefault(); + } + break; } - } else if (event.key === Key.ARROW_DOWN) { - if (this._editorRef.isModified() || !this._editorRef.isCaretAtEnd()) { - return; + case KeyAction.EditNextMessage: { + if (this._editorRef.isModified() || !this._editorRef.isCaretAtEnd()) { + return; + } + const nextEvent = findEditableEvent(this._getRoom(), true, this.props.editState.getEvent().getId()); + if (nextEvent) { + dis.dispatch({action: 'edit_event', event: nextEvent}); + } else { + dis.dispatch({action: 'edit_event', event: null}); + dis.fire(Action.FocusComposer); + } + event.preventDefault(); + break; } - const nextEvent = findEditableEvent(this._getRoom(), true, this.props.editState.getEvent().getId()); - if (nextEvent) { - dis.dispatch({action: 'edit_event', event: nextEvent}); - } else { - dis.dispatch({action: 'edit_event', event: null}); - dis.fire(Action.FocusComposer); - } - event.preventDefault(); } } From ac7963b509ba630276424f86634f2a068e73bdbd Mon Sep 17 00:00:00 2001 From: Clemens Zeidler Date: Tue, 16 Feb 2021 19:05:51 +1300 Subject: [PATCH 0154/2197] Fix lint and style issues --- src/components/views/rooms/SendMessageComposer.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 0559c71e9e..5b018f2f0e 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -38,17 +38,16 @@ import * as sdk from '../../../index'; import Modal from '../../../Modal'; import {_t, _td} from '../../../languageHandler'; import ContentMessages from '../../../ContentMessages'; -import {Key, isOnlyCtrlOrCmdKeyEvent} from "../../../Keyboard"; +import {Key} from "../../../Keyboard"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import RateLimitedFunc from '../../../ratelimitedfunc'; import {Action} from "../../../dispatcher/actions"; import {containsEmoji} from "../../../effects/utils"; import {CHAT_EFFECTS} from '../../../effects'; -import SettingsStore from "../../../settings/SettingsStore"; import CountlyAnalytics from "../../../CountlyAnalytics"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import EMOJI_REGEX from 'emojibase-regex'; -import { getKeyBindingsManager, KeyAction, KeyBindingContext } from '../../../KeyBindingsManager'; +import {getKeyBindingsManager, KeyAction, KeyBindingContext} from '../../../KeyBindingsManager'; function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) { const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent); @@ -152,7 +151,7 @@ export default class SendMessageComposer extends React.Component { event.preventDefault(); break; case KeyAction.SelectPrevSendHistory: - case KeyAction.SelectNextSendHistory: + case KeyAction.SelectNextSendHistory: { // Try select composer history const selected = this.selectSendHistory(action === KeyAction.SelectPrevSendHistory); if (selected) { @@ -160,7 +159,8 @@ export default class SendMessageComposer extends React.Component { event.preventDefault(); } break; - case KeyAction.EditLastMessage: + } + case KeyAction.EditPrevMessage: // selection must be collapsed and caret at start if (this._editorRef.isSelectionCollapsed() && this._editorRef.isCaretAtStart()) { const editEvent = findEditableEvent(this.props.room, false); @@ -251,7 +251,7 @@ export default class SendMessageComposer extends React.Component { const myReactionKeys = [...myReactionEvents] .filter(event => !event.isRedacted()) .map(event => event.getRelation().key); - shouldReact = !myReactionKeys.includes(reaction); + shouldReact = !myReactionKeys.includes(reaction); } if (shouldReact) { MatrixClientPeg.get().sendEvent(lastMessage.getRoomId(), "m.reaction", { @@ -486,7 +486,7 @@ export default class SendMessageComposer extends React.Component { _insertQuotedMessage(event) { const {model} = this; const {partCreator} = model; - const quoteParts = parseEvent(event, partCreator, { isQuotedMessage: true }); + const quoteParts = parseEvent(event, partCreator, {isQuotedMessage: true}); // add two newlines quoteParts.push(partCreator.newline()); quoteParts.push(partCreator.newline()); From c84ad9bedc1299935b7cb8c8f4d1ebbf333ef7d3 Mon Sep 17 00:00:00 2001 From: Clemens Zeidler Date: Tue, 16 Feb 2021 19:12:18 +1300 Subject: [PATCH 0155/2197] Use key binding for cancelling a message reply --- src/KeyBindingsManager.ts | 2 +- src/components/views/rooms/SendMessageComposer.js | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/KeyBindingsManager.ts b/src/KeyBindingsManager.ts index b411c7ff27..e8f4126fbd 100644 --- a/src/KeyBindingsManager.ts +++ b/src/KeyBindingsManager.ts @@ -22,7 +22,7 @@ export enum KeyAction { /** Start editing the user's next sent message */ EditNextMessage = 'EditNextMessage', - /** Cancel editing a message */ + /** Cancel editing a message or cancel replying to a message */ CancelEditing = 'CancelEditing', } diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 5b018f2f0e..adfa38b56a 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -38,7 +38,6 @@ import * as sdk from '../../../index'; import Modal from '../../../Modal'; import {_t, _td} from '../../../languageHandler'; import ContentMessages from '../../../ContentMessages'; -import {Key} from "../../../Keyboard"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import RateLimitedFunc from '../../../ratelimitedfunc'; import {Action} from "../../../dispatcher/actions"; @@ -174,13 +173,14 @@ export default class SendMessageComposer extends React.Component { } } break; + case KeyAction.CancelEditing: + dis.dispatch({ + action: 'reply_to_event', + event: null, + }); + break; default: - if (event.key === Key.ESCAPE) { - dis.dispatch({ - action: 'reply_to_event', - event: null, - }); - } else if (this._prepareToEncrypt) { + if (this._prepareToEncrypt) { // This needs to be last! this._prepareToEncrypt(); } From f9d4d679ee6cdb3fb66bd54e4a89d755c3b21f68 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 16 Feb 2021 10:59:39 +0000 Subject: [PATCH 0156/2197] Upgrade matrix-js-sdk to 9.7.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 5beb383791..4700f6d0cb 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "9.7.0-rc.1", + "matrix-js-sdk": "9.7.0", "matrix-widget-api": "^0.1.0-beta.13", "minimist": "^1.2.5", "pako": "^2.0.3", diff --git a/yarn.lock b/yarn.lock index e3d7a5ac28..6aff5fecdc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5577,10 +5577,10 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@9.7.0-rc.1: - version "9.7.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-9.7.0-rc.1.tgz#30cabda3f2b416d09c95afc4fe138bba44534c76" - integrity sha512-y6D2bXYKQqGrtf1PLHjjHf6wW67DIhdrwQ77nKOhEd/rfaEqcMf99wqSvF/nzFexbG1Y7PG4IdM4YTdOQ9UD+g== +matrix-js-sdk@9.7.0: + version "9.7.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-9.7.0.tgz#fbd03c188546f9733b28ea1752476b22ca63d2f2" + integrity sha512-cVBHhQVGk2WWQ2kv0Ov8CTcgZVJhMuZBejXZnKqp6qEgSpmb4xQRxewbxjF53ixO7uRUzGFtAlDiW4BeHZnN/g== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From a99f080e97acf82ed774b98b7eb62ccdf16a0127 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 16 Feb 2021 11:05:24 +0000 Subject: [PATCH 0157/2197] Prepare changelog for v3.14.0 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50880b1d53..c87f1c62e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +Changes in [3.14.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.14.0) (2021-02-16) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.14.0-rc.1...v3.14.0) + + * Upgrade to JS SDK 9.7.0 + * [Release] Use config for host signup branding + [\#5651](https://github.com/matrix-org/matrix-react-sdk/pull/5651) + Changes in [3.14.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.14.0-rc.1) (2021-02-10) =============================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.13.1...v3.14.0-rc.1) From e70085ed2e14a74dc4f482645b89bbb82101de38 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 16 Feb 2021 11:05:25 +0000 Subject: [PATCH 0158/2197] v3.14.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4700f6d0cb..203673eef4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.14.0-rc.1", + "version": "3.14.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From df3a9526a5780c4a71e8c951cf47991bbd43b9ec Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 16 Feb 2021 11:06:42 +0000 Subject: [PATCH 0159/2197] Resetting package fields for development --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c05a007d70..9b2f60655c 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "matrix-gen-i18n": "scripts/gen-i18n.js", "matrix-prune-i18n": "scripts/prune-i18n.js" }, - "main": "./lib/index.js", + "main": "./src/index.js", "matrix_src_main": "./src/index.js", "matrix_lib_main": "./lib/index.js", "matrix_lib_typings": "./lib/index.d.ts", @@ -189,6 +189,5 @@ "transformIgnorePatterns": [ "/node_modules/(?!matrix-js-sdk).+$" ] - }, - "typings": "./lib/index.d.ts" + } } From b6a4876c8a6d6b12b5eaad93ee91869422f02837 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 16 Feb 2021 11:06:55 +0000 Subject: [PATCH 0160/2197] Reset matrix-js-sdk back to develop branch --- package.json | 2 +- yarn.lock | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9b2f60655c..d4f931d811 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "9.7.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^0.1.0-beta.13", "minimist": "^1.2.5", "pako": "^2.0.3", diff --git a/yarn.lock b/yarn.lock index 4108c0499c..01450908cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5572,10 +5572,9 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@9.7.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "9.7.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-9.7.0.tgz#fbd03c188546f9733b28ea1752476b22ca63d2f2" - integrity sha512-cVBHhQVGk2WWQ2kv0Ov8CTcgZVJhMuZBejXZnKqp6qEgSpmb4xQRxewbxjF53ixO7uRUzGFtAlDiW4BeHZnN/g== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/c82bc35202f93efa2cb9b27b140f83df37c64ab2" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From 75d789bb4330b497c9cd43cbe7b38c6e1c1d4f19 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Tue, 16 Feb 2021 12:07:36 +0000 Subject: [PATCH 0161/2197] Translated using Weblate (Czech) Currently translated at 100.0% (2764 of 2764 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/cs/ --- src/i18n/strings/cs.json | 229 +++++++++++++++++++++------------------ 1 file changed, 121 insertions(+), 108 deletions(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index c304623544..864c6208fa 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -55,7 +55,7 @@ "Custom Server Options": "Vlastní nastavení serveru", "Add a widget": "Přidat widget", "Accept": "Přijmout", - "%(targetName)s accepted an invitation.": "%(targetName)s přijal/a pozvání.", + "%(targetName)s accepted an invitation.": "%(targetName)s přijal(a) pozvání.", "Account": "Účet", "Access Token:": "Přístupový token:", "Add": "Přidat", @@ -86,10 +86,10 @@ "Bans user with given id": "Vykáže uživatele s daným id", "Cannot add any more widgets": "Nelze přidat žádné další widgety", "Change Password": "Změnit heslo", - "%(senderName)s changed their profile picture.": "%(senderName)s změnil/a svůj profilový obrázek.", - "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s změnil/a název místnosti na %(roomName)s.", - "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s odstranil/a název místnosti.", - "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s změnil/a téma na „%(topic)s“.", + "%(senderName)s changed their profile picture.": "%(senderName)s změnil(a) svůj profilový obrázek.", + "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s změnil(a) název místnosti na %(roomName)s.", + "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s odstranil(a) název místnosti.", + "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s změnil(a) téma na „%(topic)s“.", "Changes your display nickname": "Změní vaši zobrazovanou přezdívku", "Command error": "Chyba příkazu", "Commands": "Příkazy", @@ -113,8 +113,8 @@ "Email address": "E-mailová adresa", "Emoji": "Emoji", "Enable automatic language detection for syntax highlighting": "Zapnout automatické rozpoznávání jazyků pro zvýrazňování syntaxe", - "%(senderName)s ended the call.": "%(senderName)s ukončil/a hovor.", - "Enter passphrase": "Zadejte heslo", + "%(senderName)s ended the call.": "%(senderName)s ukončil(a) hovor.", + "Enter passphrase": "Zadejte přístupovou frázi", "Error decrypting attachment": "Chyba při dešifrování přílohy", "Error: Problem communicating with the given homeserver.": "Chyba: problém v komunikaci s daným domovským serverem.", "Existing Call": "Probíhající hovor", @@ -136,18 +136,18 @@ "Forget room": "Zapomenout místnost", "For security, this session has been signed out. Please sign in again.": "Z bezpečnostních důvodů bylo toto přihlášení ukončeno. Přihlašte se prosím znovu.", "and %(count)s others...|other": "a %(count)s další...", - "%(widgetName)s widget modified by %(senderName)s": "%(senderName)s upravil/a widget %(widgetName)s", - "%(widgetName)s widget removed by %(senderName)s": "%(senderName)s odstranil/a widget %(widgetName)s", - "%(widgetName)s widget added by %(senderName)s": "%(senderName)s přidal/a widget %(widgetName)s", + "%(widgetName)s widget modified by %(senderName)s": "%(senderName)s upravil(a) widget %(widgetName)s", + "%(widgetName)s widget removed by %(senderName)s": "%(senderName)s odstranil(a) widget %(widgetName)s", + "%(widgetName)s widget added by %(senderName)s": "%(senderName)s přidal(a) widget %(widgetName)s", "Automatically replace plain text Emoji": "Automaticky nahrazovat textové emoji", "Failed to upload image": "Obrázek se nepodařilo nahrát", - "%(senderName)s answered the call.": "%(senderName)s přijal/a hovor.", + "%(senderName)s answered the call.": "%(senderName)s přijal(a) hovor.", "Click to mute audio": "Klepněte pro vypnutí zvuku", "Failed to verify email address: make sure you clicked the link in the email": "E-mailovou adresu se nepodařilo ověřit. Přesvědčte se, že jste klepli na odkaz v e-mailové zprávě", "Guests cannot join this room even if explicitly invited.": "Hosté nemohou vstoupit do této místnosti, i když jsou přímo pozváni.", "Homeserver is": "Domovský server je", "Identity Server is": "Server identity je", - "I have verified my email address": "Ověřil/a jsem svou e-mailovou adresu", + "I have verified my email address": "Ověřil(a) jsem svou e-mailovou adresu", "Import": "Importovat", "Import E2E room keys": "Importovat end-to-end klíče místností", "Incoming call from %(name)s": "Příchozí hovor od %(name)s", @@ -156,12 +156,12 @@ "Incorrect username and/or password.": "Nesprávné uživatelské jméno nebo heslo.", "Incorrect verification code": "Nesprávný ověřovací kód", "Invalid Email Address": "Neplatná e-mailová adresa", - "%(senderName)s invited %(targetName)s.": "%(senderName)s pozval/a uživatele %(targetName)s.", + "%(senderName)s invited %(targetName)s.": "%(senderName)s pozval(a) uživatele %(targetName)s.", "Invites": "Pozvánky", "Invites user with given id to current room": "Pozve do aktuální místnosti uživatele s daným id", "Join Room": "Vstoupit do místnosti", - "%(targetName)s joined the room.": "%(targetName)s vstoupil/a do místnosti.", - "%(senderName)s kicked %(targetName)s.": "%(senderName)s vykopl/a uživatele %(targetName)s.", + "%(targetName)s joined the room.": "%(targetName)s vstoupil(a) do místnosti.", + "%(senderName)s kicked %(targetName)s.": "%(senderName)s vykopl(a) uživatele %(targetName)s.", "Kick": "Vykopnout", "Kicks user with given id": "Vykopne uživatele s daným id", "Last seen": "Naposledy aktivní", @@ -184,7 +184,7 @@ "Passwords can't be empty": "Hesla nemohou být prázdná", "Permissions": "Oprávnění", "Phone": "Telefon", - "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s změnil/a úroveň oprávnění o %(powerLevelDiffText)s.", + "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s změnil(a) úroveň oprávnění o %(powerLevelDiffText)s.", "Define the power level of a user": "Stanovte úroveň oprávnění uživatele", "Failed to change power level": "Nepodařilo se změnit úroveň oprávnění", "Power level must be positive integer.": "Úroveň oprávnění musí být kladné celé číslo.", @@ -201,15 +201,15 @@ "%(roomName)s is not accessible at this time.": "Místnost %(roomName)s není v tuto chvíli dostupná.", "Save": "Uložit", "Send Reset Email": "Poslat resetovací e-mail", - "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s poslal/a obrázek.", - "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s pozval/a uživatele %(targetDisplayName)s ke vstupu do místnosti.", + "%(senderDisplayName)s sent an image.": "%(senderDisplayName)s poslal(a) obrázek.", + "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s pozval(a) uživatele %(targetDisplayName)s ke vstupu do místnosti.", "Server error": "Chyba serveru", "Server may be unavailable, overloaded, or search timed out :(": "Server může být nedostupný, přetížený nebo vyhledávání vypršelo :(", "Server may be unavailable, overloaded, or you hit a bug.": "Server může být nedostupný, přetížený nebo jste narazili na chybu.", "Server unavailable, overloaded, or something else went wrong.": "Server je nedostupný, přetížený nebo se něco pokazilo.", "Session ID": "ID sezení", - "%(senderName)s set a profile picture.": "%(senderName)s si nastavil/a profilový obrázek.", - "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s si změnil/a zobrazované jméno na %(displayName)s.", + "%(senderName)s set a profile picture.": "%(senderName)s si nastavil(a) profilový obrázek.", + "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s si změnil(a) zobrazované jméno na %(displayName)s.", "Show timestamps in 12 hour format (e.g. 2:30pm)": "Zobrazovat čas v 12hodinovém formátu (např. 2:30 odp.)", "Sign in": "Přihlásit", "Sign out": "Odhlásit", @@ -235,9 +235,9 @@ "Online": "Online", "Offline": "Offline", "Check for update": "Zkontrolovat aktualizace", - "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s přijal/a pozvání pro %(displayName)s.", + "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s přijal(a) pozvání pro %(displayName)s.", "Active call (%(roomName)s)": "Probíhající hovor (%(roomName)s)", - "%(senderName)s banned %(targetName)s.": "%(senderName)s vykázal/a uživatele %(targetName)s.", + "%(senderName)s banned %(targetName)s.": "%(senderName)s vykázal(a) uživatele %(targetName)s.", "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Nelze se připojit k domovskému serveru přes HTTP, pokud je v adresním řádku HTTPS. Buď použijte HTTPS, nebo povolte nezabezpečené skripty.", "Click here to fix": "Pro opravu klepněte zde", "Click to mute video": "Klepněte pro zakázání videa", @@ -258,7 +258,7 @@ "Unable to remove contact information": "Nepodařilo se smazat kontaktní údaje", "Unable to verify email address.": "Nepodařilo se ověřit e-mailovou adresu.", "Unban": "Přijmout zpět", - "%(senderName)s unbanned %(targetName)s.": "%(senderName)s přijal/a zpět uživatele %(targetName)s.", + "%(senderName)s unbanned %(targetName)s.": "%(senderName)s přijal(a) zpět uživatele %(targetName)s.", "Unable to capture screen": "Nepodařilo se zachytit obrazovku", "Unable to enable Notifications": "Nepodařilo se povolit oznámení", "unknown caller": "neznámý volající", @@ -305,11 +305,11 @@ "Reason": "Důvod", "VoIP conference started.": "VoIP konference započata.", "VoIP conference finished.": "VoIP konference ukončena.", - "%(targetName)s left the room.": "%(targetName)s opustil/a místnost.", + "%(targetName)s left the room.": "%(targetName)s opustil(a) místnost.", "You are already in a call.": "Již máte probíhající hovor.", "%(senderName)s requested a VoIP conference.": "Uživatel %(senderName)s požádal o VoIP konferenci.", - "%(senderName)s removed their profile picture.": "%(senderName)s odstranil/a svůj profilový obrázek.", - "%(targetName)s rejected the invitation.": "%(targetName)s odmítl/a pozvání.", + "%(senderName)s removed their profile picture.": "%(senderName)s odstranil(a) svůj profilový obrázek.", + "%(targetName)s rejected the invitation.": "%(targetName)s odmítl(a) pozvání.", "Communities": "Skupiny", "Message Pinning": "Připíchnutí zprávy", "Your browser does not support the required cryptography extensions": "Váš prohlížeč nepodporuje požadovaná kryptografická rozšíření", @@ -320,20 +320,20 @@ "Admin Tools": "Nástroje pro správce", "No pinned messages.": "Žádné připíchnuté zprávy.", "Pinned Messages": "Připíchnuté zprávy", - "%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s odstranil/a své zobrazované jméno (%(oldDisplayName)s).", - "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s zrušil/a pozvání pro uživatele %(targetName)s.", - "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s nastavil/a viditelnost budoucích zpráv v této místnosti pro všechny její členy, a to od chvíle jejich pozvání.", - "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s nastavil/a viditelnost budoucích zpráv v této místnosti pro všechny její členy, a to od chvíle jejich vstupu.", - "%(senderName)s made future room history visible to all room members.": "%(senderName)s nastavil/a viditelnost budoucích zpráv v této místnosti pro všechny její členy.", - "%(senderName)s made future room history visible to anyone.": "%(senderName)s nastavil/a viditelnost budoucích zpráv pro kohokoliv.", - "%(senderName)s changed the pinned messages for the room.": "%(senderName)s změnil/a připíchnuté zprávy této místnosti.", + "%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s odstranil(a) své zobrazované jméno (%(oldDisplayName)s).", + "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s zrušil(a) pozvání pro uživatele %(targetName)s.", + "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s nastavil(a) viditelnost budoucích zpráv v této místnosti pro všechny její členy, a to od chvíle jejich pozvání.", + "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s nastavil(a) viditelnost budoucích zpráv v této místnosti pro všechny její členy, a to od chvíle jejich vstupu.", + "%(senderName)s made future room history visible to all room members.": "%(senderName)s nastavil(a) viditelnost budoucích zpráv v této místnosti pro všechny její členy.", + "%(senderName)s made future room history visible to anyone.": "%(senderName)s nastavil(a) viditelnost budoucích zpráv pro kohokoliv.", + "%(senderName)s changed the pinned messages for the room.": "%(senderName)s změnil(a) připíchnuté zprávy této místnosti.", "Authentication check failed: incorrect password?": "Kontrola ověření selhala: špatné heslo?", "You need to be able to invite users to do that.": "Pro tuto akci musíte mít právo zvát uživatele.", "Delete Widget": "Smazat widget", "Error decrypting image": "Chyba při dešifrování obrázku", "Error decrypting video": "Chyba při dešifrování videa", - "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s odstranil/a avatar místnosti.", - "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s změnil/a avatar místnosti na ", + "%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s odstranil(a) avatar místnosti.", + "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s změnil(a) avatar místnosti na ", "Copied!": "Zkopírováno!", "Failed to copy": "Nepodařilo se zkopírovat", "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Smazáním widgetu ho odstraníte všem uživatelům v této místnosti. Opravdu chcete tento widget smazat?", @@ -384,9 +384,9 @@ "Invalid community ID": "Neplatné ID skupiny", "'%(groupId)s' is not a valid community ID": "'%(groupId)s' není platné ID skupiny", "New community ID (e.g. +foo:%(localDomain)s)": "Nové ID skupiny (např. +neco:%(localDomain)s)", - "%(senderName)s sent an image": "%(senderName)s poslal/a obrázek", - "%(senderName)s sent a video": "%(senderName)s poslal/a video", - "%(senderName)s uploaded a file": "%(senderName)s nahrál/a soubor", + "%(senderName)s sent an image": "%(senderName)s poslal(a) obrázek", + "%(senderName)s sent a video": "%(senderName)s poslal(a) video", + "%(senderName)s uploaded a file": "%(senderName)s nahrál(a) soubor", "Disinvite this user?": "Odvolat pozvání tohoto uživatele?", "Kick this user?": "Vykopnout tohoto uživatele?", "Unban this user?": "Přijmout zpět tohoto uživatele?", @@ -398,16 +398,16 @@ "You have disabled URL previews by default.": "Vypnuli jste automatické náhledy webových adres.", "You have enabled URL previews by default.": "Zapnuli jste automatické náhledy webových adres.", "URL Previews": "Náhledy webových adres", - "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s změnil/a avatar místnosti %(roomName)s", + "%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s změnil(a) avatar místnosti %(roomName)s", "Add an Integration": "Přidat začlenění", "An email has been sent to %(emailAddress)s": "Na adresu %(emailAddress)s jsme poslali e-mail", "File to import": "Soubor k importu", - "Passphrases must match": "Hesla se musí shodovat", - "Passphrase must not be empty": "Heslo nesmí být prázdné", + "Passphrases must match": "Přístupové fráze se musí shodovat", + "Passphrase must not be empty": "Přístupová fráze nesmí být prázdná", "Export room keys": "Exportovat klíče místnosti", "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "Tento proces vám umožňuje exportovat do souboru klíče ke zprávám, které jste dostali v šifrovaných místnostech. Když pak tento soubor importujete do jiného Matrix klienta, všechny tyto zprávy bude možné opět dešifrovat.", - "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "Kdokoliv, kdo získá přístup k exportovanému souboru, bude moci dešifrovat všechny vaše přijaté zprávy, a proto je třeba dbát zvýšenou pozornost jeho zabezpečení. Z toho důvodu byste měli do kolonky níže zadat heslo, se kterým exportovaná data zašifrujeme. Import pak bude možný pouze se znalostí zadaného hesla.", - "Confirm passphrase": "Potvrďte heslo", + "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "Kdokoliv, kdo získá přístup k exportovanému souboru, bude moci dešifrovat všechny vaše přijaté zprávy, a proto je třeba dbát zvýšenou pozornost jeho zabezpečení. Z toho důvodu byste měli do kolonky níže zadat přístupovou frázi, se kterým exportovaná data zašifrujeme. Import pak bude možný pouze se znalostí zadané přístupové fráze.", + "Confirm passphrase": "Potvrďte přístupovou frázi", "Import room keys": "Importovat klíče místnosti", "Call Timeout": "Časový limit hovoru", "Show these rooms to non-members on the community page and room list?": "Zobrazovat tyto místnosti na domovské stránce skupiny a v seznamu místností i pro nečleny?", @@ -466,18 +466,18 @@ "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)s%(count)s krát vstoupili", "%(severalUsers)sjoined %(count)s times|one": "%(severalUsers)svstoupili", - "%(oneUser)sjoined %(count)s times|one": "%(oneUser)svstoupil/a", + "%(oneUser)sjoined %(count)s times|one": "%(oneUser)svstoupil(a)", "%(severalUsers)sleft %(count)s times|other": "%(severalUsers)s %(count)s krát opustili", "%(severalUsers)sleft %(count)s times|one": "%(severalUsers)sopustili", "%(oneUser)sleft %(count)s times|other": "%(oneUser)s %(count)s krát opustil", "%(oneUser)sleft %(count)s times|one": "%(oneUser)sopustil", "%(severalUsers)sjoined and left %(count)s times|other": "%(severalUsers)s %(count)s krát vstoupili a opustili", "%(severalUsers)sjoined and left %(count)s times|one": "%(severalUsers)svstoupili a opustili", - "%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)s %(count)s krát vstoupil/a a opustil/a", - "%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)svstoupil/a a opustil/a", + "%(oneUser)sjoined and left %(count)s times|other": "%(oneUser)s %(count)s krát vstoupil(a) a opustil(a)", + "%(oneUser)sjoined and left %(count)s times|one": "%(oneUser)svstoupil(a) a opustil(a)", "%(severalUsers)sleft and rejoined %(count)s times|other": "%(severalUsers)s %(count)s krát opustili a znovu vstoupili", "%(severalUsers)sleft and rejoined %(count)s times|one": "%(severalUsers)sopustili a znovu vstoupili", - "%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)s %(count)s krát opustil/a a znovu vstoupil/a", + "%(oneUser)sleft and rejoined %(count)s times|other": "%(oneUser)s %(count)s krát opustil(a) a znovu vstoupil(a)", "%(oneUser)sleft and rejoined %(count)s times|one": "%(oneUser)sopustil a znovu vstoupil", "%(severalUsers)srejected their invitations %(count)s times|other": "%(severalUsers)s %(count)s krát odmítli pozvání", "%(severalUsers)srejected their invitations %(count)s times|one": "%(severalUsers)sodmítli pozvání", @@ -505,12 +505,12 @@ "was kicked %(count)s times|one": "byl vyhozen", "%(severalUsers)schanged their name %(count)s times|other": "%(severalUsers)s si %(count)s krát změnili jméno", "%(severalUsers)schanged their name %(count)s times|one": "%(severalUsers)s si změnili jméno", - "%(oneUser)schanged their name %(count)s times|other": "%(oneUser)s si %(count)s krát změnil/a jméno", - "%(oneUser)schanged their name %(count)s times|one": "%(oneUser)s si změnil/ jméno", + "%(oneUser)schanged their name %(count)s times|other": "%(oneUser)s si %(count)s krát změnil(a) jméno", + "%(oneUser)schanged their name %(count)s times|one": "%(oneUser)s si změnil(a) jméno", "%(severalUsers)schanged their avatar %(count)s times|other": "%(severalUsers)ssi %(count)s krát změnili avatary", "%(severalUsers)schanged their avatar %(count)s times|one": "%(severalUsers)ssi změnili avatary", - "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)s si %(count)s krát změnil/a avatar", - "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)s si změnil/a avatar", + "%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)s si %(count)s krát změnil(a) avatar", + "%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)s si změnil(a) avatar", "%(items)s and %(count)s others|other": "%(items)s a %(count)s další", "%(items)s and %(count)s others|one": "%(items)s a jeden další", "%(items)s and %(lastItem)s": "%(items)s a také %(lastItem)s", @@ -539,7 +539,7 @@ "To get started, please pick a username!": "Začněte tím, že si zvolíte uživatelské jméno!", "This will be your account name on the homeserver, or you can pick a different server.": "Toto bude název vašeho účtu na domovském serveru , anebo si můžete zvolit jiný server.", "If you already have a Matrix account you can log in instead.": "Pokud už účet v síti Matrix máte, můžete se ihned Přihlásit.", - "%(oneUser)sjoined %(count)s times|other": "%(oneUser)s %(count)s krát vstoupil/a", + "%(oneUser)sjoined %(count)s times|other": "%(oneUser)s %(count)s krát vstoupil(a)", "Private Chat": "Soukromá konverzace", "Public Chat": "Veřejná konverzace", "You must register to use this functionality": "Pro využívání této funkce se zaregistrujte", @@ -564,7 +564,7 @@ "These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "Tyto místnosti se zobrazují všem členům na stránce skupiny. Členové skupiny mohou vstoupit do místnosti klepnutím.", "Featured Rooms:": "Hlavní místnosti:", "Featured Users:": "Významní uživatelé:", - "%(inviter)s has invited you to join this community": "%(inviter)s vás pozval/a do této skupiny", + "%(inviter)s has invited you to join this community": "%(inviter)s vás pozval(a) do této skupiny", "You are an administrator of this community": "Jste správcem této skupiny", "You are a member of this community": "Jste členem této skupiny", "Your community hasn't got a Long Description, a HTML page to show to community members.
Click here to open settings and give it one!": "Vaše skupina nemá vyplněný dlouhý popis, který je součástí HTML stránky skupiny a která se zobrazuje jejím členům.
Klepnutím zde otevřete nastavení, kde ho můžete doplnit!", @@ -606,7 +606,7 @@ "Notify the whole room": "Oznámení pro celou místnost", "Room Notification": "Oznámení místnosti", "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "Tento proces vás provede importem šifrovacích klíčů, které jste si stáhli z jiného Matrix klienta. Po úspěšném naimportování budete v tomto klientovi moci dešifrovat všechny zprávy, které jste mohli dešifrovat v původním klientovi.", - "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Stažený soubor je chráněn heslem. Soubor můžete naimportovat pouze pokud zadáte odpovídající heslo.", + "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Stažený soubor je chráněn přístupovou frází. Soubor můžete naimportovat pouze pokud zadáte odpovídající přístupovou frázi.", "Call Failed": "Hovor selhal", "Send": "Odeslat", "collapse": "sbalit", @@ -753,7 +753,7 @@ "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s", "Missing roomId.": "Chybějící ID místnosti.", "Opens the Developer Tools dialog": "Otevře dialog nástrojů pro vývojáře", - "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s si změnil/a zobrazované jméno na %(displayName)s.", + "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s si změnil(a) zobrazované jméno na %(displayName)s.", "Always show encryption icons": "Vždy zobrazovat ikonu stavu šifrovaní", "Send analytics data": "Odesílat analytická data", "Enable widget screenshots on supported widgets": "Povolit screenshot widgetu pro podporované widgety", @@ -943,14 +943,14 @@ "Upgrades a room to a new version": "Upgraduje místnost na novou verzi", "This room has no topic.": "Tato místnost nemá žádné specifické téma.", "Sets the room name": "Nastaví název místnosti", - "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s upgradoval/a místnost.", - "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s zveřejnil/a místnost pro všechny s odkazem.", - "%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s zpřístupnil/a místnost pouze na pozvání.", - "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s změnil/a pravidlo k připojení na %(rule)s", - "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s povolil/a přístup hostům.", - "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s zakázal/a přístup hostům.", - "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s změnil/a pravidlo pro přístup hostů na %(rule)s", - "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s nastavil/a hlavní adresu této místnosti na %(address)s.", + "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s upgradoval(a) místnost.", + "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s zveřejnil(a) místnost pro všechny s odkazem.", + "%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s zpřístupnil(a) místnost pouze na pozvání.", + "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s změnil(a) pravidlo k připojení na %(rule)s", + "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s povolil(a) přístup hostům.", + "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s zakázal(a) přístup hostům.", + "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s změnil(a) pravidlo pro přístup hostů na %(rule)s", + "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s nastavil(a) hlavní adresu této místnosti na %(address)s.", "%(senderName)s removed the main address for this room.": "%(senderName)s zrušil hlavní adresu této místnosti.", "%(displayName)s is typing …": "%(displayName)s píše …", "%(names)s and %(count)s others are typing …|other": "%(names)s a %(count)s dalších píše …", @@ -1231,10 +1231,10 @@ "You cannot modify widgets in this room.": "V této místnosti nemůžete manipulovat s widgety.", "Sends the given message coloured as a rainbow": "Pošle zprávu v barvách duhy", "Sends the given emote coloured as a rainbow": "Pošle reakci v barvách duhy", - "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s přidal/a této místnosti příslušnost ke skupině %(groups)s.", - "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s odebral/a této místnosti příslušnost ke skupině %(groups)s.", - "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s přidal/a této místnosti příslušnost ke skupině %(newGroups)s a odebral/a k %(oldGroups)s.", - "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s zrušil/a pozvání do této místnosti pro uživatele %(targetDisplayName)s.", + "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s přidal(a) této místnosti příslušnost ke skupině %(groups)s.", + "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s odebral(a) této místnosti příslušnost ke skupině %(groups)s.", + "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s přidal(a) této místnosti příslušnost ke skupině %(newGroups)s a odebral(a) k %(oldGroups)s.", + "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s zrušil(a) pozvání do této místnosti pro uživatele %(targetDisplayName)s.", "No homeserver URL provided": "Nebyla zadána URL adresa domovského server", "Unexpected error resolving homeserver configuration": "Chyba při zjišťování konfigurace domovského serveru", "The user's homeserver does not support the version of the room.": "Uživatelův domovský server nepodporuje verzi této místnosti.", @@ -1253,11 +1253,11 @@ "Join the conversation with an account": "Připojte se ke konverzaci s účtem", "Sign Up": "Zaregistrovat se", "Sign In": "Přihlásit se", - "You were kicked from %(roomName)s by %(memberName)s": "%(memberName)s vás vykopl/a z místnosti %(roomName)s", + "You were kicked from %(roomName)s by %(memberName)s": "%(memberName)s vás vykopl(a) z místnosti %(roomName)s", "Reason: %(reason)s": "Důvod: %(reason)s", "Forget this room": "Zapomenout na tuto místnost", "Re-join": "Znovu vstoupit", - "You were banned from %(roomName)s by %(memberName)s": "%(memberName)s vás vykázal/a z místnosti %(roomName)s", + "You were banned from %(roomName)s by %(memberName)s": "%(memberName)s vás vykázal(a) z místnosti %(roomName)s", "Something went wrong with your invite to %(roomName)s": "S vaší pozvánkou do místnosti %(roomName)s se něco pokazilo", "You can only join it with a working invite.": "Vstoupit můžete jen s funkční pozvánkou.", "You can still join it because this is a public room.": "I přesto můžete vstoupit, protože tato místnost je veřejná.", @@ -1265,7 +1265,7 @@ "Try to join anyway": "Stejně se pokusit vstoupit", "Do you want to chat with %(user)s?": "Chcete si povídat s %(user)s?", "Do you want to join %(roomName)s?": "Chcete vstoupit do místnosti %(roomName)s?", - " invited you": " vás pozval/a", + " invited you": " vás pozval(a)", "You're previewing %(roomName)s. Want to join it?": "Nahlížíte do místnosti %(roomName)s. Chcete do ní vstoupit?", "%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s si nelze jen tak prohlížet. Chcete do ní vstoupit?", "This room doesn't exist. Are you sure you're at the right place?": "Tato místnost neexistuje. Jste si jistí, že jste na správném místě?", @@ -1280,7 +1280,7 @@ "Invited by %(sender)s": "Pozván od uživatele %(sender)s", "Error updating flair": "Nepovedlo se změnit příslušnost ke skupině", "There was an error updating the flair for this room. The server may not allow it or a temporary error occurred.": "Pro tuto místnost se nepovedlo změnit příslušnost ke skupině. Možná to server neumožňuje, nebo došlo k dočasné chybě.", - "reacted with %(shortName)s": " reagoval/a s %(shortName)s", + "reacted with %(shortName)s": " reagoval(a) s %(shortName)s", "edited": "upraveno", "Maximize apps": "Maximalizovat aplikace", "Rotate Left": "Otočit doleva", @@ -1509,11 +1509,11 @@ "Show image": "Zobrazit obrázek", "You verified %(name)s": "Ověřili jste %(name)s", "You cancelled verifying %(name)s": "Zrušili jste ověření %(name)s", - "%(name)s cancelled verifying": "%(name)s zrušil/a ověření", + "%(name)s cancelled verifying": "%(name)s zrušil(a) ověření", "You accepted": "Přijali jste", - "%(name)s accepted": "%(name)s přijal/a", + "%(name)s accepted": "%(name)s přijal(a)", "You cancelled": "Zrušili jste", - "%(name)s cancelled": "%(name)s zrušil/a", + "%(name)s cancelled": "%(name)s zrušil(a)", "%(name)s wants to verify": "%(name)s chce ověřit", "You sent a verification request": "Poslali jste požadavek na ověření", "Show all": "Zobrazit vše", @@ -1532,8 +1532,8 @@ "Please create a new issue on GitHub so that we can investigate this bug.": "Vyrobte prosím nové issue na GitHubu abychom mohli chybu opravit.", "%(severalUsers)smade no changes %(count)s times|other": "%(severalUsers)s neudělali %(count)s krát žádnou změnu", "%(severalUsers)smade no changes %(count)s times|one": "%(severalUsers)s neudělali žádnou změnu", - "%(oneUser)smade no changes %(count)s times|other": "%(oneUser)s neudělal/a %(count)s krát žádnou změnu", - "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)s neudělal/a žádnou změnu", + "%(oneUser)smade no changes %(count)s times|other": "%(oneUser)s neudělal(a) %(count)s krát žádnou změnu", + "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)s neudělal(a) žádnou změnu", "e.g. my-room": "např. moje-mistnost", "Use bots, bridges, widgets and sticker packs": "Použít roboty, propojení, widgety a balíky samolepek", "Terms of Service": "Podmínky použití", @@ -1555,7 +1555,7 @@ "Explore": "Procházet", "Filter": "Filtr místností", "Filter rooms…": "Najít místnost…", - "%(creator)s created and configured the room.": "%(creator)s vytvořil/a a nakonfiguroval/a místnost.", + "%(creator)s created and configured the room.": "%(creator)s vytvořil(a) a nakonfiguroval(a) místnost.", "Preview": "Náhled", "View": "Zobrazit", "Find a room…": "Najít místnost…", @@ -1593,23 +1593,23 @@ "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s spustil hovor. (není podporováno tímto prohlížečem)", "%(senderName)s placed a video call.": "%(senderName)s spustil videohovor.", "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s spustil videohovor. (není podporováno tímto prohlížečem)", - "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s odstranil/a pravidlo blokující uživatele odpovídající %(glob)s", + "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s odstranil(a) pravidlo blokující uživatele odpovídající %(glob)s", "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s odstranil pravidlo blokující místnosti odpovídající %(glob)s", "%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s odstranil pravidlo blokující servery odpovídající %(glob)s", "%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s odstranil blokující pravidlo %(glob)s", "%(senderName)s updated an invalid ban rule": "%(senderName)s aktualizoval neplatné pravidlo blokování", - "%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s": "%(senderName)s aktualizoval/a pravidlo blokující uživatele odpovídající %(glob)s z důvodu %(reason)s", + "%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s": "%(senderName)s aktualizoval(a) pravidlo blokující uživatele odpovídající %(glob)s z důvodu %(reason)s", "%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s aktualizoval pravidlo blokující místnosti odpovídající %(glob)s z důvodu %(reason)s", "%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s aktualizoval pravidlo blokující servery odpovídající %(glob)s z důvodu %(reason)s", "%(senderName)s updated a ban rule matching %(glob)s for %(reason)s": "%(senderName)s aktualizoval blokovací pravidlo odpovídající %(glob)s z důvodu %(reason)s", - "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s": "%(senderName)s vytvořil/a pravidlo blokující uživatele odpovídající %(glob)s z důvodu %(reason)s", + "%(senderName)s created a rule banning users matching %(glob)s for %(reason)s": "%(senderName)s vytvořil(a) pravidlo blokující uživatele odpovídající %(glob)s z důvodu %(reason)s", "%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s": "%(senderName)s vytvořil pravidlo blokující místnosti odpovídající %(glob)s z důvodu %(reason)s", "%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s": "%(senderName)s vytvořil pravidlo blokující servery odpovídající %(glob)s z důvodu %(reason)s", "%(senderName)s created a ban rule matching %(glob)s for %(reason)s": "%(senderName)s vytvořil blokovací pravidlo odpovídající %(glob)s z důvodu %(reason)s", - "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s změnil/a pravidlo blokující uživatele odpovídající %(oldGlob)s na uživatele odpovídající %(newGlob)s z důvodu %(reason)s", - "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s změnil/a pravidlo blokující místnosti odpovídající %(oldGlob)s na místnosti odpovídající %(newGlob)s z důvodu %(reason)s", - "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s změnil/a pravidlo blokující servery odpovídající %(oldGlob)s na servery odpovídající %(newGlob)s z důvodu %(reason)s", - "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s změnil/a blokovací pravidlo odpovídající %(oldGlob)s na odpovídající %(newGlob)s z důvodu %(reason)s", + "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s změnil(a) pravidlo blokující uživatele odpovídající %(oldGlob)s na uživatele odpovídající %(newGlob)s z důvodu %(reason)s", + "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s změnil(a) pravidlo blokující místnosti odpovídající %(oldGlob)s na místnosti odpovídající %(newGlob)s z důvodu %(reason)s", + "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s změnil(a) pravidlo blokující servery odpovídající %(oldGlob)s na servery odpovídající %(newGlob)s z důvodu %(reason)s", + "%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s for %(reason)s": "%(senderName)s změnil(a) blokovací pravidlo odpovídající %(oldGlob)s na odpovídající %(newGlob)s z důvodu %(reason)s", "Try out new ways to ignore people (experimental)": "Vyzkoušejte nové metody ignorování lidí (experimentální)", "Match system theme": "Nastavit podle vzhledu systému", "My Ban List": "Můj seznam zablokovaných", @@ -1873,7 +1873,7 @@ "New session": "Nová relace", "Use this session to verify your new one, granting it access to encrypted messages:": "Použijte tuto relaci pro ověření nové a udělení jí přístupu k vašim šifrovaným zprávám:", "If you didn’t sign in to this session, your account may be compromised.": "Pokud jste se do této nové relace nepřihlásili, váš účet může být kompromitován.", - "This wasn't me": "To jsem nebyl/a já", + "This wasn't me": "To jsem nebyl(a) já", "This will allow you to return to your account after signing out, and sign in on other sessions.": "Toto vám umožní se přihlásit do dalších relací a vrátit se ke svému účtu poté, co se odhlásíte.", "Recovery key mismatch": "Obnovovací klíč neodpovídá", "Incorrect recovery passphrase": "Nesprávné heslo pro obnovení", @@ -1893,7 +1893,7 @@ "Warning: Your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.": "Varování: Vaše osobní data (včetně šifrovacích klíčů) jsou tu pořád uložena. Smažte je, pokud chcete tuto relaci zahodit, nebo se přihlaste pod jiný účet.", "If you cancel now, you won't complete verifying the other user.": "Pokud teď proces zrušíte, tak nebude druhý uživatel ověřen.", "If you cancel now, you won't complete verifying your other session.": "Pokud teď proces zrušíte, tak nebude druhá relace ověřena.", - "Cancel entering passphrase?": "Zrušit zadávání hesla?", + "Cancel entering passphrase?": "Zrušit zadávání přístupové fráze?", "Setting up keys": "Příprava klíčů", "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with search components added.": "%(brand)su chybí nějaké komponenty, které jsou potřeba pro vyhledávání v zabezpečených místnostech. Pokud chcete s touto funkcí experimentovat, tak si pořiďte vlastní %(brand)s Desktop s přidanými komponentami.", "Subscribing to a ban list will cause you to join it!": "Odebíráním seznamu zablokovaných uživatelů se přidáte do jeho místnosti!", @@ -1922,7 +1922,7 @@ "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.": "Relace, kterou se snažíte ověřit, neumožňuje ověření QR kódem ani pomocí emoji, což je to, co %(brand)s podporuje. Zkuste použít jiného klienta.", "Verify by scanning": "Ověřte naskenováním", "You declined": "Odmítli jste", - "%(name)s declined": "%(name)s odmítl/a", + "%(name)s declined": "%(name)s odmítl(a)", "Keep a copy of it somewhere secure, like a password manager or even a safe.": "Uschovejte si kopii na bezpečném místě, například ve správci hesel nebo v trezoru.", "Your recovery key": "Váš obnovovací klíč", "Copy": "Zkopírovat", @@ -1948,7 +1948,7 @@ "Show shortcuts to recently viewed rooms above the room list": "Zobrazovat zkratky do nedávno zobrazených místností navrchu", "Cancelling…": "Rušení…", "Your homeserver does not support cross-signing.": "Váš domovský server nepodporuje cross-signing.", - "Homeserver feature support:": "Funkce podporované domovským serverem:", + "Homeserver feature support:": "Funkce podporovaná domovským serverem:", "Accepting…": "Přijímání…", "Accepting …": "Přijímání…", "Declining …": "Odmítání…", @@ -1963,14 +1963,14 @@ "Mark all as read": "Označit vše jako přečtené", "Not currently indexing messages for any room.": "Aktuálně neindexujeme žádné zprávy.", "%(doneRooms)s out of %(totalRooms)s": "%(doneRooms)s z %(totalRooms)s", - "%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.": "%(senderDisplayName)s změnil/a jméno místnosti z %(oldRoomName)s na %(newRoomName)s.", - "%(senderName)s added the alternative addresses %(addresses)s for this room.|other": "%(senderName)s přidal/a této místnosti alternativní adresy %(addresses)s.", - "%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(senderName)s přidal/a této místnosti alternativní adresu %(addresses)s.", - "%(senderName)s removed the alternative addresses %(addresses)s for this room.|other": "%(senderName)s odebral/a této místnosti alternativní adresy %(addresses)s.", - "%(senderName)s removed the alternative addresses %(addresses)s for this room.|one": "%(senderName)s odebral/a této místnosti alternativní adresu %(addresses)s.", - "%(senderName)s changed the alternative addresses for this room.": "%(senderName)s změnil/a alternativní adresy této místnosti.", - "%(senderName)s changed the main and alternative addresses for this room.": "%(senderName)s změnil/a hlavní a alternativní adresy této místnosti.", - "%(senderName)s changed the addresses for this room.": "%(senderName)s změnil/a adresy této místnosti.", + "%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.": "%(senderDisplayName)s změnil(a) jméno místnosti z %(oldRoomName)s na %(newRoomName)s.", + "%(senderName)s added the alternative addresses %(addresses)s for this room.|other": "%(senderName)s přidal(a) této místnosti alternativní adresy %(addresses)s.", + "%(senderName)s added the alternative addresses %(addresses)s for this room.|one": "%(senderName)s přidal(a) této místnosti alternativní adresu %(addresses)s.", + "%(senderName)s removed the alternative addresses %(addresses)s for this room.|other": "%(senderName)s odebral(a) této místnosti alternativní adresy %(addresses)s.", + "%(senderName)s removed the alternative addresses %(addresses)s for this room.|one": "%(senderName)s odebral(a) této místnosti alternativní adresu %(addresses)s.", + "%(senderName)s changed the alternative addresses for this room.": "%(senderName)s změnil(a) alternativní adresy této místnosti.", + "%(senderName)s changed the main and alternative addresses for this room.": "%(senderName)s změnil(a) hlavní a alternativní adresy této místnosti.", + "%(senderName)s changed the addresses for this room.": "%(senderName)s změnil(a) adresy této místnosti.", "Manually Verify by Text": "Manuální textové ověření", "Interactively verify by Emoji": "Interaktivní ověření s emotikonami", "Support adding custom themes": "Umožnit přidání vlastního vzhledu", @@ -2044,7 +2044,7 @@ "Start verification again from their profile.": "Proces ověření začněte znovu z profilu kontaktu.", "Verification timed out.": "Ověření vypršelo.", "You cancelled verification on your other session.": "Na druhé relace jste proces ověření zrušili.", - "%(displayName)s cancelled verification.": "%(displayName)s zrušil/a proces ověření.", + "%(displayName)s cancelled verification.": "%(displayName)s zrušil(a) proces ověření.", "You cancelled verification.": "Zrušili jste proces ověření.", "Message deleted": "Zpráva smazána", "Message deleted by %(name)s": "Zpráva smazána uživatelem %(name)s", @@ -2104,7 +2104,7 @@ "Restart": "Restartovat", "Upgrade your %(brand)s": "Aktualizovat %(brand)s", "A new version of %(brand)s is available!": "Je dostupná nová verze %(brand)su!", - "Are you sure you want to cancel entering passphrase?": "Chcete určitě zrušit zadávání hesla?", + "Are you sure you want to cancel entering passphrase?": "Chcete určitě zrušit zadávání přístupové fráze?", "Use your account to sign in to the latest version": "Přihlašte se za pomoci svého účtu do nejnovější verze", "Riot is now Element!": "Riot je nyní Element!", "Learn More": "Zjistit více", @@ -2172,7 +2172,7 @@ "%(senderName)s left the call": "%(senderName)s opustil/a hovor", "Call ended": "Hovor skončil", "You started a call": "Začali jste hovor", - "%(senderName)s started a call": "%(senderName)s začal/a hovor", + "%(senderName)s started a call": "%(senderName)s začal(a) hovor", "Waiting for answer": "Čekání na odpověď", "%(senderName)s is calling": "%(senderName)s volá", "* %(senderName)s %(emote)s": "* %(senderName)s %(emote)s", @@ -2193,7 +2193,7 @@ "Incoming call": "Příchozí hovor", "Your server isn't responding to some requests.": "Váš server neodpovídá na některé požadavky.", "Master private key:": "Hlavní soukromý klíč:", - "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.": "%(brand)s nemůže v prohlížeči lokálně bezpečně uložit zprávy. Použijte %(brand)s Desktop aby fungovalo vyhledávání v šifrovaných zprávách.", + "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.": "%(brand)s nemůže bezpečně ukládat šifrované zprávy lokálně v prohlížeči. Pro zobrazení šifrovaných zpráv ve výsledcích vyhledávání použijte %(brand)s Desktop.", "Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Váš administrátor vypnul šifrování ve výchozím nastavení soukromých místností a přímých chatů.", "To link to this room, please add an address.": "Přidejte prosím místnosti adresu aby na ní šlo odkazovat.", "The authenticity of this encrypted message can't be guaranteed on this device.": "Pravost této šifrované zprávy nelze na tomto zařízení ověřit.", @@ -2256,7 +2256,7 @@ "Start a new chat": "Založit novou konverzaci", "Which officially provided instance you are using, if any": "Kterou oficiální instanci Riot.im používáte (a jestli vůbec)", "Change the topic of this room": "Změnit téma této místnosti", - "%(senderName)s declined the call.": "%(senderName)s odmítl/a hovor.", + "%(senderName)s declined the call.": "%(senderName)s odmítl(a) hovor.", "(an error occurred)": "(došlo k chybě)", "(their device couldn't start the camera / microphone)": "(zařízení druhé strany nemohlo spustit kameru / mikrofon)", "(connection failed)": "(spojení selhalo)", @@ -2295,7 +2295,7 @@ "Attach files from chat or just drag and drop them anywhere in a room.": "Připojte soubory z chatu nebo je jednoduše přetáhněte kamkoli do místnosti.", "No files visible in this room": "V této místnosti nejsou viditelné žádné soubory", "Show files": "Zobrazit soubory", - "%(count)s people|other": "%(count)s lidé/í", + "%(count)s people|other": "%(count)s lidí", "About": "O", "You’re all caught up": "Vše vyřízeno", "You have no visible notifications in this room.": "V této místnosti nemáte žádná viditelná oznámení.", @@ -2333,10 +2333,10 @@ "Switch to light mode": "Přepnout do světlého režimu", "User settings": "Uživatelská nastavení", "Community settings": "Nastavení skupiny", - "Confirm your recovery passphrase": "Potvrďte vaši frázi pro obnovení", + "Confirm your recovery passphrase": "Potvrďte vaši přístupovou frázi pro obnovení", "Repeat your recovery passphrase...": "Opakujte přístupovou frázi pro obnovení...", "Please enter your recovery passphrase a second time to confirm.": "Potvrďte prosím podruhé svou frázi pro obnovení.", - "Use a different passphrase?": "Použít jinou frázi?", + "Use a different passphrase?": "Použít jinou přístupovou frázi?", "Great! This recovery passphrase looks strong enough.": "Skvělé! Tato fráze pro obnovení vypadá dostatečně silně.", "Enter a recovery passphrase": "Zadejte frázi pro obnovení", "%(ssoButtons)s Or %(usernamePassword)s": "%(ssoButtons)s nebo %(usernamePassword)s", @@ -2439,7 +2439,7 @@ "There are two ways you can provide feedback and help us improve %(brand)s.": "Jsou dva způsoby, jak můžete poskytnout zpětnou vazbu a pomoci nám vylepšit %(brand)s.", "Rate %(brand)s": "Ohodnotit %(brand)s", "You've previously used a newer version of %(brand)s with this session. To use this version again with end to end encryption, you will need to sign out and back in again.": "V této relaci jste již dříve používali novější verzi %(brand)s. Chcete-li tuto verzi znovu použít s šifrováním, budete se muset odhlásit a znovu přihlásit.", - "Homeserver": "Homeserver", + "Homeserver": "Domovský server", "Continue with %(provider)s": "Pokračovat s %(provider)s", "Server Options": "Možnosti serveru", "This version of %(brand)s does not support viewing some encrypted files": "Tato verze %(brand)s nepodporuje zobrazení některých šifrovaných souborů", @@ -2789,7 +2789,7 @@ "Fill Screen": "Vyplnit obrazovku", "Voice Call": "Hlasový hovor", "Video Call": "Videohovor", - "%(senderName)s ended the call": "%(senderName)s ukončil/a hovor", + "%(senderName)s ended the call": "%(senderName)s ukončil(a) hovor", "You ended the call": "Ukončili jste hovor", "New version of %(brand)s is available": "K dispozici je nová verze %(brand)s", "Error leaving room": "Při opouštění místnosti došlo k chybě", @@ -2879,7 +2879,7 @@ "Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message": "Vloží (╯°□°)╯︵ ┻━┻ na začátek zprávy", "Remain on your screen while running": "Při běhu zůstává na obrazovce", "Remain on your screen when viewing another room, when running": "Při prohlížení jiné místnosti zůstává při běhu na obrazovce", - "%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s změnil/a seznam přístupů serveru pro tuto místnost.", + "%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s změnil(a) seznam přístupů serveru pro tuto místnost.", "Offline encrypted messaging using dehydrated devices": "Offline šifrovaná komunikace pomocí dehydrovaných zařízení", "See emotes posted to your active room": "Prohlédněte si emoji zveřejněné ve vaší aktivní místnosti", "See emotes posted to this room": "Prohlédněte si emoji zveřejněné v této místnosti", @@ -2968,5 +2968,18 @@ "Share your screen": "Sdílejte svou obrazovku", "Expand code blocks by default": "Ve výchozím nastavení rozbalit bloky kódu", "Show line numbers in code blocks": "Zobrazit čísla řádků v blocích kódu", - "Recently visited rooms": "Nedávno navštívené místnosti" + "Recently visited rooms": "Nedávno navštívené místnosti", + "Upgrade to pro": "Upgradujte na profesionální verzi", + "Minimize dialog": "Minimalizovat dialog", + "Maximize dialog": "Maximalizovat dialog", + "%(hostSignupBrand)s Setup": "Nastavení %(hostSignupBrand)s", + "You should know": "Měli byste vědět", + "Privacy Policy": "Zásady ochrany osobních údajů", + "Cookie Policy": "Zásady používání souborů cookie", + "Learn more in our , and .": "Další informace najdete v našich , a .", + "Continuing temporarily allows the %(hostSignupBrand)s setup process to access your account to fetch verified email addresses. This data is not stored.": "Dočasné pokračování umožňuje procesu nastavení %(hostSignupBrand)s přístup k vašemu účtu za účelem načtení ověřených e-mailových adres. Tato data se neukládají.", + "Failed to connect to your homeserver. Please close this dialog and try again.": "Připojení k domovskému serveru se nezdařilo. Zavřete toto dialogové okno a zkuste to znovu.", + "Abort": "Přerušit", + "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Opravdu chcete přerušit vytváření hostitele? Proces nemůže být navázán.", + "Confirm abort of host creation": "Potvrďte přerušení vytváření hostitele" } From ee543277335320cf107dfa421d87952a247b4a8c Mon Sep 17 00:00:00 2001 From: Jaiwanth Date: Tue, 16 Feb 2021 18:59:22 +0530 Subject: [PATCH 0162/2197] Update src/components/views/rooms/RoomTile.tsx --- src/components/views/rooms/RoomTile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index f168235335..3894f557fc 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -462,7 +462,7 @@ export default class RoomTile extends React.PureComponent { const isLowPriority = roomTags.includes(DefaultTagID.LowPriority); const lowPriorityLabel = _t("Low Priority"); - const inRoom = this.props.room && this.props.room.getMyMembership() === "join"; + const inRoom = this.props.room.getMyMembership() === "join"; const userId = MatrixClientPeg.get().getUserId(); let canInvite = inRoom; const powerLevels = this.props.room.currentState From 5535d7fb97da0af2e1bdd776c6ded1030e5392ee Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 16 Feb 2021 14:52:11 +0000 Subject: [PATCH 0163/2197] Prepare to encrypt when a call arrives So we're ready to send an answer straight away if the user answers --- src/CallHandler.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index f106d75f8b..2be1c34a9f 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -681,6 +681,12 @@ export default class CallHandler { Analytics.trackEvent('voip', 'receiveCall', 'type', call.type); this.calls.set(mappedRoomId, call) this.setCallListeners(call); + + // get ready to send encrypted events in the room, so if the user does answer + // the call, we'll be ready to send. NB. This is the protocol-level room ID not + // the mapped one: that's where we'll send the events. + const cli = MatrixClientPeg.get(); + cli.prepareToEncrypt(cli.getRoom(call.roomId)); } break; case 'hangup': From bb6659f9839b87e94e4235ce113c6102bf7f1cc8 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 16 Feb 2021 15:48:58 +0000 Subject: [PATCH 0164/2197] Set ICE candidate pool size option So we can start candidate gathering in advance --- src/MatrixClientPeg.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts index bb4be663b6..98ca446532 100644 --- a/src/MatrixClientPeg.ts +++ b/src/MatrixClientPeg.ts @@ -279,6 +279,10 @@ class _MatrixClientPeg implements IMatrixClientPeg { timelineSupport: true, forceTURN: !SettingsStore.getValue('webRtcAllowPeerToPeer'), fallbackICEServerAllowed: !!SettingsStore.getValue('fallbackICEServerAllowed'), + // Gather up to 20 ICE candidates when a call arrives: this should be more than we'd + // ever normally need, so effectively this should make all the gathering happen when + // the call arrives. + iceCandidatePoolSize: 20, verificationMethods: [ verificationMethods.SAS, SHOW_QR_CODE_METHOD, From 6cb3381df59d576389ac69f1e4b01c99515fb0e6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 16 Feb 2021 18:03:12 +0000 Subject: [PATCH 0165/2197] Remove redundant lockOrigin parameter from usercontent now that each Element has its own, and not usercontent.riot.im it only has to permit its own origin --- src/components/views/messages/MFileBody.js | 2 +- src/usercontent/index.js | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/components/views/messages/MFileBody.js b/src/components/views/messages/MFileBody.js index d17a1c4ce3..770cd4fff3 100644 --- a/src/components/views/messages/MFileBody.js +++ b/src/components/views/messages/MFileBody.js @@ -288,7 +288,7 @@ export default class MFileBody extends React.Component {