From 7d22bbc00f49356cd5fec3565a19376ef8b0ef05 Mon Sep 17 00:00:00 2001 From: Bryan Kok Date: Sat, 17 Oct 2020 23:52:18 +0800 Subject: [PATCH 001/420] 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 002/420] 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 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 003/420] 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 004/420] 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 005/420] 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 006/420] 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 007/420] 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 008/420] 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 009/420] 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 010/420] 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 011/420] 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 012/420] 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 013/420] 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 014/420] 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 015/420] 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 016/420] 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 017/420] 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 018/420] 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 019/420] 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 020/420] 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 021/420] 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 022/420] 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 023/420] 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 024/420] 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 025/420] 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 026/420] 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 027/420] 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 028/420] 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 029/420] 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 030/420] 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 031/420] 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 032/420] 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 5de92b68d954ba3f997f2d5713d954ee05303b2e Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Wed, 27 Jan 2021 11:39:57 +0000 Subject: [PATCH 033/420] 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 034/420] 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 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 035/420] 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 =
); } - } else if (this.state.view === Views.WELCOME) { + } else if (this.state.view === Views.WELCOME && !shouldUseLoginForWelcome(SdkConfig.get())) { const Welcome = sdk.getComponent('auth.Welcome'); view = ; } else if (this.state.view === Views.REGISTER && SettingsStore.getValue(UIFeature.Registration)) { @@ -2020,7 +2021,8 @@ export default class MatrixChat extends React.PureComponent { {...this.getServerProperties()} /> ); - } else if (this.state.view === Views.LOGIN) { + } else if (this.state.view === Views.LOGIN + || (this.state.view === Views.WELCOME && shouldUseLoginForWelcome(SdkConfig.get()))) { const showPasswordReset = SettingsStore.getValue(UIFeature.PasswordReset); const Login = sdk.getComponent('structures.auth.Login'); view = ( diff --git a/src/utils/pages.js b/src/utils/pages.ts similarity index 68% rename from src/utils/pages.js rename to src/utils/pages.ts index d63ca3f2c7..bae76be29d 100644 --- a/src/utils/pages.js +++ b/src/utils/pages.ts @@ -1,5 +1,5 @@ /* -Copyright 2019 New Vector Ltd +Copyright 2019, 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. @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -export function getHomePageUrl(appConfig) { +import { ConfigOptions } from "../SdkConfig"; + +export function getHomePageUrl(appConfig: ConfigOptions): string | null { const pagesConfig = appConfig.embeddedPages; - let pageUrl = null; - if (pagesConfig) { - pageUrl = pagesConfig.homeUrl; - } + let pageUrl = pagesConfig?.homeUrl; + if (!pageUrl) { // This is a deprecated config option for the home page // (despite the name, given we also now have a welcome @@ -29,3 +29,8 @@ export function getHomePageUrl(appConfig) { return pageUrl; } + +export function shouldUseLoginForWelcome(appConfig: ConfigOptions): boolean { + const pagesConfig = appConfig.embeddedPages; + return pagesConfig?.loginForWelcome === true; +} From 5de99c7708c58b2808d288160ce8fb69f4f5027c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 18 Feb 2021 19:40:24 +0100 Subject: [PATCH 046/420] Fix licenses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/settings/_SpellCheckLanguages.scss | 3 +-- src/components/views/elements/SpellCheckLanguagesDropdown.tsx | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/res/css/views/settings/_SpellCheckLanguages.scss b/res/css/views/settings/_SpellCheckLanguages.scss index ddfa0bf9e0..bb322c983f 100644 --- a/res/css/views/settings/_SpellCheckLanguages.scss +++ b/res/css/views/settings/_SpellCheckLanguages.scss @@ -1,6 +1,5 @@ /* -Copyright 2019 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +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. diff --git a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx index 53c3f310b7..029d162573 100644 --- a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx +++ b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx @@ -1,6 +1,5 @@ /* -Copyright 2017 Marcel Radzio (MTRNord) -Copyright 2017 Vector Creations Ltd. +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. From ed02503462d93e43659bddd3280a19a0b31e26f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 18 Feb 2021 19:41:19 +0100 Subject: [PATCH 047/420] Fix one more license MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/settings/SpellCheckSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/SpellCheckSettings.tsx b/src/components/views/settings/SpellCheckSettings.tsx index bfe0774570..d08f263b5f 100644 --- a/src/components/views/settings/SpellCheckSettings.tsx +++ b/src/components/views/settings/SpellCheckSettings.tsx @@ -1,5 +1,5 @@ /* -Copyright 2019 New Vector Ltd +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. From 2ebc1252cbbfc9731dc412947287ef5e4c9ce460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 18 Feb 2021 19:54:54 +0100 Subject: [PATCH 048/420] Removed unnecessary functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../elements/SpellCheckLanguagesDropdown.tsx | 37 ++++++++++--------- .../tabs/user/GeneralUserSettingsTab.js | 6 ++- src/languageHandler.tsx | 14 ------- 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx index 029d162573..c647f6e410 100644 --- a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx +++ b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx @@ -18,7 +18,7 @@ import React from 'react'; import Dropdown from "../../views/elements/Dropdown" import * as sdk from '../../../index'; -import * as languageHandler from '../../../languageHandler'; +import PlatformPeg from "../../../PlatformPeg"; import SettingsStore from "../../../settings/SettingsStore"; import { _t } from "../../../languageHandler"; @@ -52,23 +52,26 @@ export default class SpellCheckLanguagesDropdown extends React.Component { - languages.sort(function(a, b) { - if (a < b) return -1; - if (a > b) return 1; - return 0; - }); - const langs = []; - languages.forEach((language) => { - langs.push({ - label: language, - value: language, + const plaf = PlatformPeg.get(); + if (plaf) { + plaf.getAvailableSpellCheckLanguages().then((languages) => { + languages.sort(function(a, b) { + if (a < b) return -1; + if (a > b) return 1; + return 0; + }); + const langs = []; + languages.forEach((language) => { + langs.push({ + label: language, + value: language, + }) }) - }) - this.setState({languages: langs}); - }).catch((e) => { - this.setState({languages: ['en']}); - }); + this.setState({languages: langs}); + }).catch((e) => { + this.setState({languages: ['en']}); + }); + } } _onSearchChange(search) { diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index febbcc8e36..e87dca88c8 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -188,7 +188,10 @@ export default class GeneralUserSettingsTab extends React.Component { SettingsStore.setValue("spell-check-languages", null, SettingLevel.DEVICE, languages); this.setState({spellCheckLanguages: languages}); - languageHandler.setSpellCheckLanguages(languages); + const plaf = PlatformPeg.get(); + if (plaf) { + plaf.setSpellCheckLanguages(languages); + } }; _onPasswordChangeError = (err) => { @@ -402,6 +405,7 @@ export default class GeneralUserSettingsTab extends React.Component { render() { const plaf = PlatformPeg.get(); const supportsMultiLanguageSpellCheck = plaf.supportsMultiLanguageSpellCheck(); + console.log("LOG", supportsMultiLanguageSpellCheck); const discoWarning = this.state.requiredPolicyInfo.hasTerms ? { - const plaf = PlatformPeg.get(); - if (plaf) { - return plaf.getAvailableSpellCheckLanguages(); - } -} - export function getAllLanguagesFromJson() { return getLangsJson().then((langsObject) => { const langs = []; From 305d64cda88aebef8e2a0e799606224baeac4dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 18 Feb 2021 20:09:39 +0100 Subject: [PATCH 049/420] Removed log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/settings/tabs/user/GeneralUserSettingsTab.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index e87dca88c8..41597604e9 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -405,7 +405,6 @@ export default class GeneralUserSettingsTab extends React.Component { render() { const plaf = PlatformPeg.get(); const supportsMultiLanguageSpellCheck = plaf.supportsMultiLanguageSpellCheck(); - console.log("LOG", supportsMultiLanguageSpellCheck); const discoWarning = this.state.requiredPolicyInfo.hasTerms ? Date: Thu, 18 Feb 2021 20:12:48 +0100 Subject: [PATCH 050/420] Use getSpellCheckLanguages() instead of a setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/BasePlatform.ts | 4 ++++ .../settings/tabs/user/GeneralUserSettingsTab.js | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index fe655371a5..9d7077097b 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -250,6 +250,10 @@ export default abstract class BasePlatform { setSpellCheckLanguages(preferredLangs: string[]) {} + getSpellCheckLanguages(): Promise | null { + return null; + } + 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 41597604e9..3936864215 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, false), + spellCheckLanguages: [], haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()), serverSupportsSeparateAddAndBind: null, idServerHasUnsignedTerms: false, @@ -87,6 +87,15 @@ export default class GeneralUserSettingsTab extends React.Component { this._getThreepidState(); } + async componentDidMount() { + const plaf = PlatformPeg.get(); + if (plaf) { + this.setState({ + spellCheckLanguages: await plaf.getSpellCheckLanguages(), + }); + } + } + componentWillUnmount() { dis.unregister(this.dispatcherRef); } From 5a6e393fa2c6344a00efc14ab4eb17fc6a258a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 18 Feb 2021 20:13:55 +0100 Subject: [PATCH 051/420] Removed spell-check-languages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/settings/tabs/user/GeneralUserSettingsTab.js | 1 - src/settings/Settings.ts | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js index 3936864215..b17ab18c39 100644 --- a/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/GeneralUserSettingsTab.js @@ -194,7 +194,6 @@ export default class GeneralUserSettingsTab extends React.Component { }; _onSpellCheckLanguagesChange = (languages) => { - SettingsStore.setValue("spell-check-languages", null, SettingLevel.DEVICE, languages); this.setState({spellCheckLanguages: languages}); const plaf = PlatformPeg.get(); diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b486f0fbf8..ca5e2f1d04 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -424,10 +424,6 @@ 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: ["en-US"], - }, "breadcrumb_rooms": { // not really a setting supportedLevels: [SettingLevel.ACCOUNT], From ad85764a8e3d1025c76db29e9f53ba690ac2eb4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 25 Feb 2021 18:23:32 +0100 Subject: [PATCH 052/420] Fix timeline expansion 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 | 1 + res/css/structures/_RoomView.scss | 1 + 2 files changed, 2 insertions(+) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index f05f24d0d7..9597083e9c 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -19,6 +19,7 @@ limitations under the License. flex-direction: row; min-width: 0; height: 100%; + justify-content: space-between; } .mx_MainSplit > .mx_RightPanel_ResizeWrapper { diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index cd8c640132..5240a0650f 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -121,6 +121,7 @@ limitations under the License. position: relative; //for .mx_RoomView_auxPanel_fullHeight display: flex; flex-direction: column; + width: 100%; } .mx_RoomView_body { From aa4ec9fca1a65202306ee77d705b15f1782de188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 25 Feb 2021 18:27:52 +0100 Subject: [PATCH 053/420] Make $droptarget-bg-color more opaque MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/themes/light/css/_light.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 1c89d83c01..ea7b0472e0 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -68,7 +68,7 @@ $groupFilterPanel-bg-color: rgba(232, 232, 232, 0.77); $plinth-bg-color: $secondary-accent-color; // used by RoomDropTarget -$droptarget-bg-color: rgba(255,255,255,0.5); +$droptarget-bg-color: rgba(255,255,255,0.95); // used by AddressSelector $selected-color: $secondary-accent-color; From 8551855a5626d06e6feda6f849f50b26af85a194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 25 Feb 2021 18:30:14 +0100 Subject: [PATCH 054/420] Add $droptarget-bg-color to the dark theme 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 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index a878aa3cdd..f6f415ce70 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -42,6 +42,9 @@ $preview-bar-bg-color: $header-panel-bg-color; $groupFilterPanel-bg-color: rgba(38, 39, 43, 0.82); $inverted-bg-color: $base-color; +// used by RoomDropTarget +$droptarget-bg-color: rgba(21,25,30,0.95); + // used by AddressSelector $selected-color: $room-highlight-color; From a3001f77e46e7c3ac3479a70eb56db312c6f1361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 25 Feb 2021 18:30:39 +0100 Subject: [PATCH 055/420] Remove rounded corners of the drop area 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 | 3 --- 1 file changed, 3 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 5240a0650f..e80ac062b6 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -30,9 +30,6 @@ limitations under the License. pointer-events: none; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - background-color: $droptarget-bg-color; position: absolute; From 26b70b62280b7f4fa21c6287faf20a309a399abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 25 Feb 2021 18:32:04 +0100 Subject: [PATCH 056/420] Remove label 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 | 5 ----- 1 file changed, 5 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index e80ac062b6..8ba31fac20 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -42,11 +42,6 @@ limitations under the License. .mx_RoomView_fileDropTargetLabel { position: absolute; - - border-radius: 10px; - padding: 10px; - - background-color: $menu-bg-color; } .mx_RoomView_auxPanel { From 6a7340e8be3844881b1c114d74687983b9c0ba20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 25 Feb 2021 18:46:48 +0100 Subject: [PATCH 057/420] Use new upload icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/img/upload-big.svg | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/res/img/upload-big.svg b/res/img/upload-big.svg index 6099c2e976..9a6a265fdb 100644 --- a/res/img/upload-big.svg +++ b/res/img/upload-big.svg @@ -1,19 +1,3 @@ - - - - icons_upload_drop - Created with bin/sketchtool. - - - - - - - - - - - - - + + From 1c48804d96c3cdda150e38e2d520a2268dd5728e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 25 Feb 2021 19:28:08 +0100 Subject: [PATCH 058/420] Remove unnecessary class 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 | 4 ---- src/components/views/rooms/AuxPanel.tsx | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 8ba31fac20..28d8d1e196 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -40,10 +40,6 @@ limitations under the License. align-items: center; } -.mx_RoomView_fileDropTargetLabel { - position: absolute; -} - .mx_RoomView_auxPanel { min-width: 0px; width: 100%; diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 7966643084..543a50d59f 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -156,7 +156,7 @@ export default class AuxPanel extends React.Component { if (this.props.draggingFile) { fileDropTarget = (
-
+

{ _t("Drop file here to upload") } From 43e1144ae7ca7eff08c1666b4a179ba828d41432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 25 Feb 2021 19:36:55 +0100 Subject: [PATCH 059/420] Don't use TintableSVG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This seemed to have caused a little lag and it was unnecessary Signed-off-by: Šimon Brandner --- src/components/views/rooms/AuxPanel.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 543a50d59f..c9150d588f 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -150,14 +150,12 @@ export default class AuxPanel extends React.Component { } render() { - const TintableSvg = sdk.getComponent("elements.TintableSvg"); - let fileDropTarget = null; if (this.props.draggingFile) { fileDropTarget = (
- +
{ _t("Drop file here to upload") }
From 7277c285a9326928555e05766ee3ce33603de18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 25 Feb 2021 20:10:38 +0100 Subject: [PATCH 060/420] Fix weird crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/AuxPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index c9150d588f..cc3408476c 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -155,7 +155,7 @@ export default class AuxPanel extends React.Component { fileDropTarget = (
- +
{ _t("Drop file here to upload") }
From 49ea9a4788243346b20fcf5b4b79f46a7c3a80ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 25 Feb 2021 20:10:58 +0100 Subject: [PATCH 061/420] Remove sdk import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/AuxPanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index cc3408476c..59ea8e237a 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -17,7 +17,6 @@ limitations under the License. import React from 'react'; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import { Room } from 'matrix-js-sdk/src/models/room' -import * as sdk from '../../../index'; import dis from "../../../dispatcher/dispatcher"; import * as ObjectUtils from '../../../ObjectUtils'; import AppsDrawer from './AppsDrawer'; From 563620484df6fa79f666ef72f414838cf0e342f1 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Thu, 25 Feb 2021 14:39:20 -0500 Subject: [PATCH 062/420] Support replying with a message command Signed-off-by: Robin Townsend --- src/SlashCommands.tsx | 28 +++++++----- .../views/rooms/SendMessageComposer.js | 44 +++++++++++++------ 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 6b5f261374..06468c135e 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -20,6 +20,7 @@ limitations under the License. import * as React from 'react'; +import { ContentHelpers } from 'matrix-js-sdk'; import {MatrixClientPeg} from './MatrixClientPeg'; import dis from './dispatcher/dispatcher'; import * as sdk from './index'; @@ -126,10 +127,10 @@ export class Command { return this.getCommand() + " " + this.args; } - run(roomId: string, args: string, cmd: string) { + run(roomId: string, args: string) { // if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me` if (!this.runFn) return reject(_t("Command error")); - return this.runFn.bind(this)(roomId, args, cmd); + return this.runFn.bind(this)(roomId, args); } getUsage() { @@ -163,7 +164,7 @@ export const Commands = [ if (args) { message = message + ' ' + args; } - return success(MatrixClientPeg.get().sendTextMessage(roomId, message)); + return success(ContentHelpers.makeTextMessage(message)); }, category: CommandCategories.messages, }), @@ -176,7 +177,7 @@ export const Commands = [ if (args) { message = message + ' ' + args; } - return success(MatrixClientPeg.get().sendTextMessage(roomId, message)); + return success(ContentHelpers.makeTextMessage(message)); }, category: CommandCategories.messages, }), @@ -189,7 +190,7 @@ export const Commands = [ if (args) { message = message + ' ' + args; } - return success(MatrixClientPeg.get().sendTextMessage(roomId, message)); + return success(ContentHelpers.makeTextMessage(message)); }, category: CommandCategories.messages, }), @@ -202,7 +203,7 @@ export const Commands = [ if (args) { message = message + ' ' + args; } - return success(MatrixClientPeg.get().sendTextMessage(roomId, message)); + return success(ContentHelpers.makeTextMessage(message)); }, category: CommandCategories.messages, }), @@ -211,7 +212,7 @@ export const Commands = [ args: '', description: _td('Sends a message as plain text, without interpreting it as markdown'), runFn: function(roomId, messages) { - return success(MatrixClientPeg.get().sendTextMessage(roomId, messages)); + return success(ContentHelpers.makeTextMessage(messages)); }, category: CommandCategories.messages, }), @@ -220,7 +221,7 @@ export const Commands = [ args: '', description: _td('Sends a message as html, without interpreting it as markdown'), runFn: function(roomId, messages) { - return success(MatrixClientPeg.get().sendHtmlMessage(roomId, messages, messages)); + return success(ContentHelpers.makeHtmlMessage(messages, messages)); }, category: CommandCategories.messages, }), @@ -966,7 +967,7 @@ export const Commands = [ args: '', runFn: function(roomId, args) { if (!args) return reject(this.getUserId()); - return success(MatrixClientPeg.get().sendHtmlMessage(roomId, args, textToHtmlRainbow(args))); + return success(ContentHelpers.makeHtmlMessage(args, textToHtmlRainbow(args))); }, category: CommandCategories.messages, }), @@ -976,7 +977,7 @@ export const Commands = [ args: '', runFn: function(roomId, args) { if (!args) return reject(this.getUserId()); - return success(MatrixClientPeg.get().sendHtmlEmote(roomId, args, textToHtmlRainbow(args))); + return success(ContentHelpers.makeHtmlEmote(args, textToHtmlRainbow(args))); }, category: CommandCategories.messages, }), @@ -1201,10 +1202,13 @@ export function parseCommandString(input: string) { * processing the command, or 'promise' if a request was sent out. * Returns null if the input didn't match a command. */ -export function getCommand(roomId: string, input: string) { +export function getCommand(input: string) { const {cmd, args} = parseCommandString(input); if (CommandMap.has(cmd) && CommandMap.get(cmd).isEnabled()) { - return () => CommandMap.get(cmd).run(roomId, args, cmd); + return { + cmd: CommandMap.get(cmd), + args, + }; } } diff --git a/src/components/views/rooms/SendMessageComposer.js b/src/components/views/rooms/SendMessageComposer.js index 62c474e417..d1482c0df6 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -33,7 +33,7 @@ import ReplyThread from "../elements/ReplyThread"; import {parseEvent} from '../../../editor/deserialize'; import {findEditableEvent} from '../../../utils/EventUtils'; import SendHistoryManager from "../../../SendHistoryManager"; -import {getCommand} from '../../../SlashCommands'; +import {CommandCategories, getCommand} from '../../../SlashCommands'; import * as sdk from '../../../index'; import Modal from '../../../Modal'; import {_t, _td} from '../../../languageHandler'; @@ -287,15 +287,22 @@ export default class SendMessageComposer extends React.Component { } return text + part.text; }, ""); - return [getCommand(this.props.room.roomId, commandText), commandText]; + const {cmd, args} = getCommand(commandText); + return [cmd, args, commandText]; } - async _runSlashCommand(fn) { - const cmd = fn(); - let error = cmd.error; - if (cmd.promise) { + async _runSlashCommand(cmd, args) { + const result = cmd.run(this.props.room.roomId, args); + let messageContent; + let error = result.error; + if (result.promise) { try { - await cmd.promise; + if (cmd.category === CommandCategories.messages) { + // The command returns a modified message that we need to pass on + messageContent = await result.promise; + } else { + await result.promise; + } } catch (err) { error = err; } @@ -304,7 +311,7 @@ export default class SendMessageComposer extends React.Component { console.error("Command failure: %s", error); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); // assume the error is a server error when the command is async - const isServerError = !!cmd.promise; + const isServerError = !!result.promise; const title = isServerError ? _td("Server error") : _td("Command error"); let errText; @@ -322,6 +329,7 @@ export default class SendMessageComposer extends React.Component { }); } else { console.log("Command success."); + if (messageContent) return messageContent; } } @@ -330,13 +338,22 @@ export default class SendMessageComposer extends React.Component { return; } + const replyToEvent = this.props.replyToEvent; let shouldSend = true; + let content; if (!containsEmote(this.model) && this._isSlashCommand()) { - const [cmd, commandText] = this._getSlashCommand(); + const [cmd, args, commandText] = this._getSlashCommand(); if (cmd) { - shouldSend = false; - this._runSlashCommand(cmd); + if (cmd.category === CommandCategories.messages) { + content = await this._runSlashCommand(cmd, args); + if (replyToEvent) { + addReplyToMessageContent(content, replyToEvent, this.props.permalinkCreator); + } + } else { + this._runSlashCommand(cmd, args); + shouldSend = false; + } } else { // ask the user if their unknown command should be sent as a message const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); @@ -371,11 +388,12 @@ export default class SendMessageComposer extends React.Component { this._sendQuickReaction(); } - const replyToEvent = this.props.replyToEvent; if (shouldSend) { const startTime = CountlyAnalytics.getTimestamp(); const {roomId} = this.props.room; - const content = createMessageContent(this.model, this.props.permalinkCreator, replyToEvent); + if (!content) { + content = createMessageContent(this.model, this.props.permalinkCreator, replyToEvent); + } // don't bother sending an empty message if (!content.body.trim()) return; From 1a7f9091b4fc41eac6d5cc1b71d1dbe61f5d7f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 07:51:03 +0100 Subject: [PATCH 063/420] Animate icon size 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 ++++++++++++ src/components/views/rooms/AuxPanel.tsx | 10 +++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 28d8d1e196..d5caee5e8b 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -36,10 +36,22 @@ limitations under the License. z-index: 3000; display: flex; + flex-direction: column; justify-content: center; align-items: center; } +@keyframes mx_RoomView_fileDropTarget_image_animation { + from {width: 0px;} + to {width: 32px;} +} + +.mx_RoomView_fileDropTarget_image { + animation: mx_RoomView_fileDropTarget_image_animation; + animation-duration: 0.5s; + margin-bottom: 16px; +} + .mx_RoomView_auxPanel { min-width: 0px; width: 100%; diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 59ea8e237a..b3ef8c3cc8 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -153,11 +153,11 @@ export default class AuxPanel extends React.Component { if (this.props.draggingFile) { fileDropTarget = (
-
- -
- { _t("Drop file here to upload") } -
+ + { _t("Drop file here to upload") }
); } From f0c26846c75559e5be013eb25ebe5f54b4f6e264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 08:11:58 +0100 Subject: [PATCH 064/420] Fix formatting 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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index d5caee5e8b..2c3fb2b32b 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -42,8 +42,8 @@ limitations under the License. } @keyframes mx_RoomView_fileDropTarget_image_animation { - from {width: 0px;} - to {width: 32px;} + from {width: 0px;} + to {width: 32px;} } .mx_RoomView_fileDropTarget_image { From 172cc01f7d3dcba08235a621cff118efe3e76d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 08:12:10 +0100 Subject: [PATCH 065/420] Add background animation 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, 11 insertions(+), 1 deletion(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 2c3fb2b32b..5870e107c6 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -20,6 +20,12 @@ limitations under the License. flex-direction: column; } + +@keyframes mx_RoomView_fileDropTarget_animation { + from {opacity: 0;} + to {opacity: 0.95;} +} + .mx_RoomView_fileDropTarget { min-width: 0px; width: 100%; @@ -30,7 +36,8 @@ limitations under the License. pointer-events: none; - background-color: $droptarget-bg-color; + background-color: $primary-bg-color; + opacity: 0.95; position: absolute; z-index: 3000; @@ -39,6 +46,9 @@ limitations under the License. flex-direction: column; justify-content: center; align-items: center; + + animation: mx_RoomView_fileDropTarget_animation; + animation-duration: 0.5s; } @keyframes mx_RoomView_fileDropTarget_image_animation { From 3e0558f4d97bc618cc8e6d71f411370e894d642f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 08:12:38 +0100 Subject: [PATCH 066/420] Remove droptarget colors 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 | 3 --- res/themes/light/css/_light.scss | 3 --- 2 files changed, 6 deletions(-) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index f6f415ce70..a878aa3cdd 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -42,9 +42,6 @@ $preview-bar-bg-color: $header-panel-bg-color; $groupFilterPanel-bg-color: rgba(38, 39, 43, 0.82); $inverted-bg-color: $base-color; -// used by RoomDropTarget -$droptarget-bg-color: rgba(21,25,30,0.95); - // used by AddressSelector $selected-color: $room-highlight-color; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index ea7b0472e0..c92e491ca2 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -67,9 +67,6 @@ $groupFilterPanel-bg-color: rgba(232, 232, 232, 0.77); // used by RoomDirectory permissions $plinth-bg-color: $secondary-accent-color; -// used by RoomDropTarget -$droptarget-bg-color: rgba(255,255,255,0.95); - // used by AddressSelector $selected-color: $secondary-accent-color; From 49ea83edb93dfa0e6f572aeac002adaaa0370fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 08:14:27 +0100 Subject: [PATCH 067/420] 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 | 1 - 1 file changed, 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5bbbdf60b5..9af8ccc172 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1381,7 +1381,6 @@ "Remove %(phone)s?": "Remove %(phone)s?", "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.", "Phone Number": "Phone Number", - "Drop File Here": "Drop File Here", "Drop file here to upload": "Drop file here to upload", "This user has not verified all of their sessions.": "This user has not verified all of their sessions.", "You have not verified this user.": "You have not verified this user.", From e90ae2ea7596bff850cf4014c1109f93234132b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 08:18:05 +0100 Subject: [PATCH 068/420] Delint 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 | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 5870e107c6..5e8d84ff32 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -22,8 +22,12 @@ limitations under the License. @keyframes mx_RoomView_fileDropTarget_animation { - from {opacity: 0;} - to {opacity: 0.95;} + from { + opacity: 0; + } + to { + opacity: 0.95; + } } .mx_RoomView_fileDropTarget { @@ -52,8 +56,12 @@ limitations under the License. } @keyframes mx_RoomView_fileDropTarget_image_animation { - from {width: 0px;} - to {width: 32px;} + from { + width: 0px; + } + to { + width: 32px; + } } .mx_RoomView_fileDropTarget_image { From 819a0b013fda927bea3197e458741f4f4f85a271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 11:08:31 +0100 Subject: [PATCH 069/420] min-width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need to allow the container to be smaller 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 5e8d84ff32..28591ad7a4 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -140,6 +140,7 @@ limitations under the License. display: flex; flex-direction: column; width: 100%; + min-width: 0; } .mx_RoomView_body { From 0d6a9fce67d24f441a62e140a2a73f669b959cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 11:12:14 +0100 Subject: [PATCH 070/420] Remove weird styling 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index ff09af454e..42eafe5bdc 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2058,7 +2058,7 @@ export default class RoomView extends React.Component { appsShown={this.state.showApps} /> -
+
{auxPanel}
From 11c5aa02d290739fff31bfa8365fe76562032594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 11:19:45 +0100 Subject: [PATCH 071/420] Remove mx_RoomView_container 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 | 1 + res/css/structures/_RoomView.scss | 8 ------- src/components/structures/RoomView.tsx | 30 ++++++++++++-------------- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index 9597083e9c..5fa62e921d 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -20,6 +20,7 @@ limitations under the License. min-width: 0; height: 100%; justify-content: space-between; + min-height: 0; } .mx_MainSplit > .mx_RightPanel_ResizeWrapper { diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 28591ad7a4..b3dab5f992 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -135,14 +135,6 @@ limitations under the License. height: 50px; } -.mx_RoomView_container { - position: relative; //for .mx_RoomView_auxPanel_fullHeight - display: flex; - flex-direction: column; - width: 100%; - min-width: 0; -} - .mx_RoomView_body { display: flex; flex-direction: column; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 42eafe5bdc..be2f81a176 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2058,24 +2058,22 @@ export default class RoomView extends React.Component { appsShown={this.state.showApps} /> -
+
{auxPanel} -
-
- {topUnreadMessagesBar} - {jumpToBottom} - {messagePanel} - {searchResultsPanel} -
-
-
-
- {statusBar} -
-
- {previewBar} - {messageComposer} +
+ {topUnreadMessagesBar} + {jumpToBottom} + {messagePanel} + {searchResultsPanel}
+
+
+
+ {statusBar} +
+
+ {previewBar} + {messageComposer}
From 9a5ba072ba0f551dc618cdf8bfb7afba004d01fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 11:23:58 +0100 Subject: [PATCH 072/420] Fix auxPanel 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 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index be2f81a176..5b79f23e0b 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2059,8 +2059,8 @@ export default class RoomView extends React.Component { />
- {auxPanel}
+ {auxPanel} {topUnreadMessagesBar} {jumpToBottom} {messagePanel} From 3bed37463fea1ff1a7c86ef5fe9a0a123e06008f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 11:38:05 +0100 Subject: [PATCH 073/420] Remove unnecessary code 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 | 1 - 1 file changed, 1 deletion(-) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index 5fa62e921d..2d9ea2729c 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -19,7 +19,6 @@ limitations under the License. flex-direction: row; min-width: 0; height: 100%; - justify-content: space-between; min-height: 0; } From 3a643e5b9df189916d2a3b8162636e2d05a5e2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 11:46:54 +0100 Subject: [PATCH 074/420] Remove unnecessary changes 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 | 34 ++++++++++++------------------ res/css/structures/_RoomView.scss | 2 -- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/res/css/structures/_MainSplit.scss b/res/css/structures/_MainSplit.scss index 2d9ea2729c..8199121420 100644 --- a/res/css/structures/_MainSplit.scss +++ b/res/css/structures/_MainSplit.scss @@ -18,35 +18,27 @@ limitations under the License. display: flex; flex-direction: row; min-width: 0; - height: 100%; min-height: 0; + height: 100%; } .mx_MainSplit > .mx_RightPanel_ResizeWrapper { - padding: 0 5px 5px 0px; + padding: 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 - .mx_RightPanel_ResizeHandle { - width: 9px; - } - &:hover .mx_RightPanel_ResizeHandle { - &::before { - position: absolute; - left: 6px; - top: 50%; - transform: translate(0, -50%); + // Need to use important to override element style attributes + // set by re-resizable + top: 50% !important; + transform: translate(0, -50%); - height: 64px; - width: 4px; - border-radius: 4px; + height: 64px !important; // to match width of the ones on roomlist + width: 4px !important; + border-radius: 4px !important; - content: ' '; - - background-color: $primary-fg-color; - opacity: 0.8; - - margin-left: -10px; - } + background-color: $primary-fg-color; + opacity: 0.8; } } diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index b3dab5f992..26382b55e8 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -144,8 +144,6 @@ limitations under the License. .mx_RoomView_messagePanel, .mx_RoomView_messagePanelSpinner, .mx_RoomView_messagePanelSearchSpinner { order: 2; } - - margin-right: 10px; } .mx_RoomView_body .mx_RoomView_timeline { From a0200de7b4a0afdecfdf5f0e06a68e0d990876f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 18:34:54 +0100 Subject: [PATCH 075/420] Add scrollToBottomOnMessageSent setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/settings/Settings.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index ca5e2f1d04..dd431f9b75 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -317,6 +317,11 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td('Show line numbers in code blocks'), default: true, }, + "scrollToBottomOnMessageSent": { + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + displayName: _td('Jump to the bottom of the timeline when you send a message'), + default: true, + }, "Pill.shouldShowPillAvatar": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Show avatars in user and room mentions'), From 1387c9f94db2815184f920c516a40b2809916fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 18:35:19 +0100 Subject: [PATCH 076/420] Display scrollToBottomOnMessageSent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 04fcea39dc..5e1c2e7288 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -48,6 +48,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'showRedactions', 'enableSyntaxHighlightLanguageDetection', 'expandCodeByDefault', + `scrollToBottomOnMessageSent`, 'showCodeLineNumbers', 'showJoinLeaves', 'showAvatarChanges', From 361420bf6826dc309f6f46c4f9b4d0bed4790982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 18:35:45 +0100 Subject: [PATCH 077/420] Use scrollToBottomOnMessageSent 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 62c474e417..c2baa2762c 100644 --- a/src/components/views/rooms/SendMessageComposer.js +++ b/src/components/views/rooms/SendMessageComposer.js @@ -403,7 +403,9 @@ export default class SendMessageComposer extends React.Component { this._editorRef.clearUndoHistory(); this._editorRef.focus(); this._clearStoredEditorState(); - dis.dispatch({action: "scroll_to_bottom"}); + if (SettingsStore.getValue("scrollToBottomOnMessageSent")) { + dis.dispatch({action: "scroll_to_bottom"}); + } } componentWillUnmount() { From 83df645dbbadcab2a003e14f946f11cf4daa59e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Feb 2021 18:35:53 +0100 Subject: [PATCH 078/420] 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 | 1 + 1 file changed, 1 insertion(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5bbbdf60b5..4bd7131bfe 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -808,6 +808,7 @@ "Enable automatic language detection for syntax highlighting": "Enable automatic language detection for syntax highlighting", "Expand code blocks by default": "Expand code blocks by default", "Show line numbers in code blocks": "Show line numbers in code blocks", + "Jump to the bottom of the timeline when you send a message": "Jump to the bottom of the timeline when you send a message", "Show avatars in user and room mentions": "Show avatars in user and room mentions", "Enable big emoji in chat": "Enable big emoji in chat", "Send typing notifications": "Send typing notifications", From 414f18b19f1c33d4cfaff8fa0440130b76d86dcf Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Fri, 26 Feb 2021 18:01:24 +0000 Subject: [PATCH 079/420] Translated using Weblate (German) Currently translated at 99.2% (2759 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 3f657e105b..269fef0da1 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -16,7 +16,7 @@ "Deops user with given id": "Setzt das Berechtigungslevel des/der Benutzer:in mit der angegebenen ID zurück", "Invites user with given id to current room": "Lädt den/die Benutzer:in mit der angegebenen ID in den aktuellen Raum ein", "Kicks user with given id": "Benutzer:in mit der angegebenen ID kicken", - "Changes your display nickname": "Ändert deinen angezeigten Nicknamen", + "Changes your display nickname": "Ändert deinen Anzeigenamen", "Change Password": "Passwort ändern", "Searches DuckDuckGo for results": "Verwendet DuckDuckGo für Suchergebnisse", "Commands": "Kommandos", @@ -115,7 +115,7 @@ "You are already in a call.": "Du bist bereits in einem Gespräch.", "You cannot place a call with yourself.": "Du kannst keinen Anruf mit dir selbst starten.", "You cannot place VoIP calls in this browser.": "VoIP-Gespräche werden von diesem Browser nicht unterstützt.", - "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Deine E-Mail-Adresse scheint nicht mit einer Matrix-ID auf diesem Home-Server verbunden zu sein.", + "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Deine E-Mail-Adresse scheint nicht mit einer Matrix-ID auf diesem Homeserver verbunden zu sein.", "Sun": "So", "Mon": "Mo", "Tue": "Di", @@ -636,7 +636,7 @@ "Which officially provided instance you are using, if any": "Welche offiziell angebotene Instanz du nutzt, wenn überhaupt eine", "In reply to ": "Als Antwort auf ", "This room is not public. You will not be able to rejoin without an invite.": "Dies ist kein öffentlicher Raum. Du wirst diesen nicht ohne Einladung wieder beitreten können.", - "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s änderte den Anzeigenamen auf %(displayName)s.", + "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s hat den Anzeigenamen zu %(displayName)s geändert.", "Failed to set direct chat tag": "Fehler beim Setzen der Direkt-Chat-Markierung", "Failed to remove tag %(tagName)s from room": "Entfernen der Raum-Kennzeichnung %(tagName)s fehlgeschlagen", "Failed to add tag %(tagName)s to room": "Fehler beim Hinzufügen des \"%(tagName)s\"-Tags an dem Raum", @@ -834,7 +834,7 @@ "This event could not be displayed": "Dieses Ereignis konnte nicht angezeigt werden", "A call is currently being placed!": "Ein Anruf wurde schon gestartet!", "Permission Required": "Berechtigung benötigt", - "You do not have permission to start a conference call in this room": "Du hast keine Berechtigung um ein Konferenzgespräch in diesem Raum zu starten", + "You do not have permission to start a conference call in this room": "Du hast keine Berechtigung ein Konferenzgespräch in diesem Raum zu starten", "Failed to remove widget": "Widget konnte nicht entfernt werden", "An error ocurred whilst trying to remove the widget from the room": "Ein Fehler trat auf während versucht wurde, das Widget aus diesem Raum zu entfernen", "System Alerts": "System-Benachrichtigung", @@ -2944,8 +2944,8 @@ "Homeserver": "Heimserver", "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use Element with an existing Matrix account on a different homeserver.": "Du kannst in den benutzerdefinierten Serveroptionen eine andere Heimserver-URL angeben, um dich bei anderen Matrixservern anzumelden.", "Server Options": "Servereinstellungen", - "No other application is using the webcam": "Keine andere Anwendung auf die Webcam zugreift", - "Permission is granted to use the webcam": "Auf die Webcam zugegriffen werden darf", + "No other application is using the webcam": "keine andere Anwendung auf die Webcam zugreift", + "Permission is granted to use the webcam": "auf die Webcam zugegriffen werden darf", "A microphone and webcam are plugged in and set up correctly": "Mikrofon und Webcam eingesteckt und richtig eingerichtet sind", "Call failed because no microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "Der Anruf ist fehlgeschlagen weil nicht auf das Mikrofon zugegriffen werden konnte. Stelle sicher, dass das Mikrofon richtig eingesteckt und eingerichtet ist.", "Call failed because no webcam or microphone could not be accessed. Check that:": "Der Anruf ist fehlgeschlagen weil nicht auf das Mikrofon oder die Webcam zugegriffen werden konnte. Stelle sicher, dass:", From d83b935fc0f80bf9460b94254a54eb2cf0e52a63 Mon Sep 17 00:00:00 2001 From: libexus Date: Thu, 25 Feb 2021 07:30:54 +0000 Subject: [PATCH 080/420] Translated using Weblate (German) Currently translated at 99.2% (2759 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 269fef0da1..ff754e7d7e 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -181,7 +181,7 @@ "%(senderName)s unbanned %(targetName)s.": "%(senderName)s hat die Verbannung von %(targetName)s aufgehoben.", "Usage": "Verwendung", "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s hat die Einladung für %(targetName)s zurückgezogen.", - "You need to be able to invite users to do that.": "Du brauchst die Berechtigung Benutzer:innen einzuladen haben, um diese Aktion ausführen zu können.", + "You need to be able to invite users to do that.": "Du musst die Berechtigung \"Benutzer:innen einladen\" haben, um diese Aktion ausführen zu können.", "You need to be logged in.": "Du musst angemeldet sein.", "There are no visible files in this room": "Es gibt keine sichtbaren Dateien in diesem Raum", "Connectivity to the server has been lost.": "Verbindung zum Server wurde unterbrochen.", @@ -1636,7 +1636,7 @@ "Use Single Sign On to continue": "Single-Sign-On zum Fortfahren nutzen", "Confirm adding this email address by using Single Sign On to prove your identity.": "Bestätige die hinzugefügte E-Mail-Adresse mit Single Sign-On, um deine Identität nachzuweisen.", "Single Sign On": "Single Sign-On", - "Confirm adding email": "Bestätige hinzugefügte E-Mail-Addresse", + "Confirm adding email": "Hinzugefügte E-Mail-Addresse bestätigen", "Confirm adding this phone number by using Single Sign On to prove your identity.": "Bestätige die hinzugefügte Telefonnummer, indem du deine Identität mittels Single Sign-On nachweist.", "Click the button below to confirm adding this phone number.": "Klicke unten die Schaltfläche, um die hinzugefügte Telefonnummer zu bestätigen.", "If you cancel now, you won't complete your operation.": "Wenn du jetzt abbrichst, wirst du deinen Vorgang nicht fertigstellen.", @@ -3059,5 +3059,16 @@ "Cookie Policy": "Cookie-Richtlinie", "Learn more in our , and .": "Erfahren mehr in unserer , und .", "Failed to connect to your homeserver. Please close this dialog and try again.": "Verbindung zum Homeserver fehlgeschlagen. Bitte schließe diesen Dialog and versuche es erneut.", - "Abort": "Abbrechen" + "Abort": "Abbrechen", + "Upgrade to %(hostSignupBrand)s": "Zu %(hostSignupBrand)s upgraden", + "Edit Values": "Werte bearbeiten", + "Value in this room:": "Wert in diesem Raum:", + "Value:": "Wert:", + "Level": "Level", + "This UI does NOT check the types of the values. Use at your own risk.": "Diese Benutzeroberfläche prüft nicht auf richtige Datentypen. Benutzung auf eigene Gefahr.", + "Setting:": "Einstellung:", + "Value": "Wert", + "Setting ID": "Einstellungs-ID", + "Failed to save settings": "Einstellungen konnten nicht gespeichert werden", + "Show chat effects (animations when receiving e.g. confetti)": "Animierte Chateffekte zeigen, wenn z.B. Konfetti-Emojis erhalten werden" } From de73cef7e61ea57e1750f246e0a9f7d9ef785472 Mon Sep 17 00:00:00 2001 From: libexus Date: Fri, 26 Feb 2021 18:02:04 +0000 Subject: [PATCH 081/420] Translated using Weblate (German) Currently translated at 99.2% (2759 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index ff754e7d7e..29b86ac641 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -636,7 +636,7 @@ "Which officially provided instance you are using, if any": "Welche offiziell angebotene Instanz du nutzt, wenn überhaupt eine", "In reply to ": "Als Antwort auf ", "This room is not public. You will not be able to rejoin without an invite.": "Dies ist kein öffentlicher Raum. Du wirst diesen nicht ohne Einladung wieder beitreten können.", - "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s hat den Anzeigenamen zu %(displayName)s geändert.", + "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s hat den Anzeigenamen auf %(displayName)s geändert.", "Failed to set direct chat tag": "Fehler beim Setzen der Direkt-Chat-Markierung", "Failed to remove tag %(tagName)s from room": "Entfernen der Raum-Kennzeichnung %(tagName)s fehlgeschlagen", "Failed to add tag %(tagName)s to room": "Fehler beim Hinzufügen des \"%(tagName)s\"-Tags an dem Raum", From e3065f5a02533c38f0a7b770c519c5314d27880d Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Fri, 26 Feb 2021 17:09:54 -0500 Subject: [PATCH 082/420] Support sending invite reasons with MultiInviter Signed-off-by: Robin Townsend --- src/utils/MultiInviter.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils/MultiInviter.js b/src/utils/MultiInviter.js index 7d1c900360..63d3942b37 100644 --- a/src/utils/MultiInviter.js +++ b/src/utils/MultiInviter.js @@ -53,13 +53,15 @@ export default class MultiInviter { * instance of the class. * * @param {array} addrs Array of addresses to invite + * @param {string} reason Reason for inviting (optional) * @returns {Promise} Resolved when all invitations in the queue are complete */ - invite(addrs) { + invite(addrs, reason) { if (this.addrs.length > 0) { throw new Error("Already inviting/invited"); } this.addrs.push(...addrs); + this.reason = reason; for (const addr of this.addrs) { if (getAddressType(addr) === null) { @@ -123,7 +125,7 @@ export default class MultiInviter { } } - return MatrixClientPeg.get().invite(roomId, addr); + return MatrixClientPeg.get().invite(roomId, addr, undefined, this.reason); } else { throw new Error('Unsupported address'); } From c25a8b70fa051afe102a301d146448f44cd51c3d Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Fri, 26 Feb 2021 17:10:20 -0500 Subject: [PATCH 083/420] Support sending invite reasons with /invite command Signed-off-by: Robin Townsend --- src/SlashCommands.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx index 6b5f261374..aedcf7af8c 100644 --- a/src/SlashCommands.tsx +++ b/src/SlashCommands.tsx @@ -441,15 +441,14 @@ export const Commands = [ }), new Command({ command: 'invite', - args: '', + args: ' []', description: _td('Invites user with given id to current room'), runFn: function(roomId, args) { if (args) { - const matches = args.match(/^(\S+)$/); - if (matches) { + const [address, reason] = args.split(/\s+(.+)/); + if (address) { // We use a MultiInviter to re-use the invite logic, even though // we're only inviting one user. - const address = matches[1]; // If we need an identity server but don't have one, things // get a bit more complex here, but we try to show something // meaningful. @@ -490,7 +489,7 @@ export const Commands = [ } const inviter = new MultiInviter(roomId); return success(prom.then(() => { - return inviter.invite([address]); + return inviter.invite([address], reason); }).then(() => { if (inviter.getCompletionState(address) !== "invited") { throw new Error(inviter.getErrorText(address)); From 208faf6d46e30a77db7f4dd4b6d6a2bd2dba138f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 26 Feb 2021 22:21:14 -0700 Subject: [PATCH 084/420] Update velocity-animate to the latest beta This is the primary change in this PR: the new beta (which has been untouched for a year as of writing) actually does a better job of handling concurrent read receipts, this patching holes. The beta doesn't have the same leak as v1, so we can remove the metadata hack from our side (it doesn't use jQuery's data anymore). Note that this change on its own introduces an annoying bug where every second update to a read receipt will throw it 14px to the right - more on that in the next commit. --- package.json | 2 +- src/Velociraptor.js | 23 ++++------------------- yarn.lock | 8 ++++---- 3 files changed, 9 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index d4f931d811..10480b8af9 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "tar-js": "^0.3.0", "text-encoding-utf-8": "^1.0.2", "url": "^0.11.0", - "velocity-animate": "^1.5.2", + "velocity-animate": "^2.0.6", "what-input": "^5.2.10", "zxcvbn": "^4.4.2" }, diff --git a/src/Velociraptor.js b/src/Velociraptor.js index ce52f60dbd..2da54babe5 100644 --- a/src/Velociraptor.js +++ b/src/Velociraptor.js @@ -118,25 +118,10 @@ export default class Velociraptor extends React.Component { domNode.style.visibility = restingStyle.visibility; }); - /* - console.log("enter:", - JSON.stringify(transitionOpts[i-1]), - "->", - JSON.stringify(restingStyle)); - */ - } else if (node === null) { - // Velocity stores data on elements using the jQuery .data() - // method, and assumes you'll be using jQuery's .remove() to - // remove the element, but we don't use jQuery, so we need to - // blow away the element's data explicitly otherwise it will leak. - // This uses Velocity's internal jQuery compatible wrapper. - // See the bug at - // https://github.com/julianshapiro/velocity/issues/300 - // and the FAQ entry, "Preventing memory leaks when - // creating/destroying large numbers of elements" - // (https://github.com/julianshapiro/velocity/issues/47) - const domNode = ReactDom.findDOMNode(this.nodes[k]); - if (domNode) Velocity.Utilities.removeData(domNode); + // console.log("enter:", + // JSON.stringify(transitionOpts[i-1]), + // "->", + // JSON.stringify(restingStyle)); } this.nodes[k] = node; } diff --git a/yarn.lock b/yarn.lock index 01450908cc..5939a89f58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8114,10 +8114,10 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -velocity-animate@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/velocity-animate/-/velocity-animate-1.5.2.tgz#5a351d75fca2a92756f5c3867548b873f6c32105" - integrity sha512-m6EXlCAMetKztO1ppBhGU1/1MR3IiEevO6ESq6rcrSQ3Q77xYSW13jkfXW88o4xMrkXJhy/U7j4wFR/twMB0Eg== +velocity-animate@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/velocity-animate/-/velocity-animate-2.0.6.tgz#1811ca14df7fbbef05740256f6cec0fd1b76575f" + integrity sha512-tU+/UtSo3GkIjEfk2KM4e24DvpgX0+FzfLr7XqNwm9BCvZUtbCHPq/AFutx/Mkp2bXlUS9EcX8yxu8XmzAv2Kw== verror@1.10.0: version "1.10.0" From b3142d613806ed243947ddbf304a33f6e97219b5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 26 Feb 2021 22:24:36 -0700 Subject: [PATCH 085/420] Offset read receipt start positions by 1px As mentioned in 208faf6d46e30a77db7f4dd4b6d6a2bd2dba138f, the velocity-animate update causes read receipts to occasionally show up 14px to the right of where they should be. This is because the read receipt width is 14px, and velocity-animate will *not* translate `left` if it isn't changing. Unfortunately, it's smart enough to realize that `-0px` is `0px`, so we end up having to specify `1px`. The comment already mentions it, but this should have no perceived effect for the user. During development I could not tell if the 1px was being applied during the animation, implying that it's a meaningless value. It's a bit unfortunate for those who know that it's translating left by 1px, but hopefully they'll be able to unsee that in time. --- src/components/views/rooms/ReadReceiptMarker.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.js index c19247ef5a..f5c676b841 100644 --- a/src/components/views/rooms/ReadReceiptMarker.js +++ b/src/components/views/rooms/ReadReceiptMarker.js @@ -155,7 +155,13 @@ export default class ReadReceiptMarker extends React.Component { // then shift to the rightmost column, // and then it will drop down to its resting position - startStyles.push({ top: startTopOffset+'px', left: '0px' }); + // + // XXX: We use a `left: 1px` to trick velocity-animate into actually animating. This + // is a very annoying bug where if it thinks there's no change to `left` then it'll + // skip applying it, thus making our read receipt at +14px instead of +0px like it + // should be. This does cause 1px of drift for read receipts, however nobody should + // notice this while it's also falling. + startStyles.push({ top: startTopOffset+'px', left: '1px' }); enterTransitionOpts.push({ duration: bounce ? Math.min(Math.log(Math.abs(startTopOffset)) * 200, 3000) : 300, easing: bounce ? 'easeOutBounce' : 'easeOutCubic', From 76ad93b9370e2e6922b54f2268a0d4f9ebe3dc9e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 26 Feb 2021 22:25:50 -0700 Subject: [PATCH 086/420] Put speed holes in the code We can make read receipts more efficient (and avoid double-animation) by using `PureComponent` which no-ops useless updates for us. --- src/components/views/rooms/ReadReceiptMarker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.js index f5c676b841..8b2f5d27b5 100644 --- a/src/components/views/rooms/ReadReceiptMarker.js +++ b/src/components/views/rooms/ReadReceiptMarker.js @@ -32,7 +32,7 @@ try { } catch (e) { } -export default class ReadReceiptMarker extends React.Component { +export default class ReadReceiptMarker extends React.PureComponent { static propTypes = { // the RoomMember to show the RR for member: PropTypes.object, From 0dd4d45c49eeb985c7febda543145021c49aa92f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 26 Feb 2021 22:36:42 -0700 Subject: [PATCH 087/420] Disable velocity mock option This appears to have been removed in the beta --- test/components/structures/MessagePanel-test.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/test/components/structures/MessagePanel-test.js b/test/components/structures/MessagePanel-test.js index f40f8c5187..2fd5bd6ad1 100644 --- a/test/components/structures/MessagePanel-test.js +++ b/test/components/structures/MessagePanel-test.js @@ -35,7 +35,6 @@ const mockclock = require('../../mock-clock'); import Adapter from "enzyme-adapter-react-16"; import { configure, mount } from "enzyme"; -import Velocity from 'velocity-animate'; import MatrixClientContext from "../../../src/contexts/MatrixClientContext"; import RoomContext from "../../../src/contexts/RoomContext"; import DMRoomMap from "../../../src/utils/DMRoomMap"; @@ -75,18 +74,10 @@ describe('MessagePanel', function() { return arg === "showDisplaynameChanges"; }); - // This option clobbers the duration of all animations to be 1ms - // which makes unit testing a lot simpler (the animation doesn't - // complete without this even if we mock the clock and tick it - // what should be the correct amount of time). - Velocity.mock = true; - DMRoomMap.makeShared(); }); afterEach(function() { - delete Velocity.mock; - clock.uninstall(); }); From 905f5300f49e2770d77d8d3e9c86484735bcbdaa Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Thu, 25 Feb 2021 11:01:31 +0000 Subject: [PATCH 088/420] Translated using Weblate (Russian) Currently translated at 99.4% (2765 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index a72eef2c58..65aeaf82d9 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -3057,5 +3057,20 @@ "Your homeserver was unreachable and was not able to log you in. Please try again. If this continues, please contact your homeserver administrator.": "Ваш домашний сервер был недоступен, и мы не смогли войти в систему. Пожалуйста, попробуйте снова через пару минут. Если ситуация по-прежнему не меняется, обратитесь к администратору домашнего сервера за дополнительной информацией.", "Try again": "Попробовать ещё раз", "We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "Мы попросили браузер запомнить, какой домашний сервер вы используете для входа в систему, но, к сожалению, ваш браузер забыл об этом. Перейдите на страницу входа и попробуйте ещё раз.", - "We couldn't log you in": "Нам не удалось войти в систему" + "We couldn't log you in": "Нам не удалось войти в систему", + "Upgrade to %(hostSignupBrand)s": "Перейти на %(hostSignupBrand)s", + "Edit Values": "Изменить значения", + "Value in this room:": "Значение в этой комнате:", + "Value:": "Значение:", + "Setting:": "Настройки:", + "Setting ID": "ID настроек", + "Save setting values": "Сохранить значения настроек", + "Settable at room": "Устанавливается для комнаты", + "Settable at global": "Устанавливается на глобальном уровне", + "Level": "Уровень", + "This UI does NOT check the types of the values. Use at your own risk.": "Этот пользовательский интерфейс НЕ проверяет типы значений. Используйте на свой риск.", + "Value in this room": "Значение в этой комнате", + "Value": "Значение", + "Failed to save settings": "Не удалось сохранить настройки", + "Show chat effects (animations when receiving e.g. confetti)": "Показать эффекты чата (анимация при получении, например, конфетти)" } From 4b771b0634594dcc8205fcc19ac49e107079c24f Mon Sep 17 00:00:00 2001 From: libexus Date: Sat, 27 Feb 2021 14:58:24 +0000 Subject: [PATCH 089/420] Translated using Weblate (German) Currently translated at 99.2% (2759 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index 29b86ac641..e2e2e0718a 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -1569,7 +1569,7 @@ "%(senderName)s removed the alternative addresses %(addresses)s for this room.|one": "%(senderName)s hat die alternative Adresse %(addresses)s für diesen Raum entfernt.", "%(senderName)s changed the alternative addresses for this room.": "%(senderName)s hat die alternative Adresse für diesen Raum geändert.", "%(senderName)s changed the main and alternative addresses for this room.": "%(senderName)s hat die Haupt- und Alternativadressen für diesen Raum geändert.", - "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s entfernte die Ausschluss-Regel für Nutzer!nnen, die %(glob)s entsprechen", + "%(senderName)s removed the rule banning users matching %(glob)s": "%(senderName)s entfernte die Ausschluss-Regel für Benutzer:innen, die %(glob)s entsprechen", "%(senderName)s removed the rule banning rooms matching %(glob)s": "%(senderName)s entfernte die Ausschluss-Regel für Räume, die %(glob)s entsprechen", "%(senderName)s removed the rule banning servers matching %(glob)s": "%(senderName)s entfernte die Ausschluss-Regel für Server, die %(glob)s entsprechen", "%(senderName)s removed a ban rule matching %(glob)s": "%(senderName)s entfernte die Ausschluss-Regel, die %(glob)s entspricht", @@ -2567,7 +2567,7 @@ "See when the name changes in this room": "Sehen wenn sich der Name in diesem Raum ändert", "Change the name of your active room": "Den Namen deines aktiven Raums ändern", "See when the name changes in your active room": "Sehen wenn der Name sich in deinem aktiven Raum ändert", - "Change the avatar of this room": "Avatar von diesem Raum ändern", + "Change the avatar of this room": "Icon von diesem Raum ändern", "See when the avatar changes in this room": "Sehen wenn der Avatar sich in diesem Raum ändert", "Change the avatar of your active room": "Den Avatar deines aktiven Raums ändern", "See when the avatar changes in your active room": "Sehen wenn ein Avatar in deinem aktiven Raum geändert wird", @@ -3036,7 +3036,7 @@ "Converts the room to a DM": "Wandelt den Raum zu Direktnachricht um", "Something went wrong in confirming your identity. Cancel and try again.": "Bei der Bestätigung deiner Identität ist ein Fehler aufgetreten. Abbrechen und erneut versuchen.", "Use app": "App verwenden", - "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "Element Web ist experimentell auf mobilen Endgeräten. Für eine bessere Erfahrung und die neuesten Erweiterungen, nutze unsere freie, native App.", + "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "Element Web ist auf mobilen Endgeräten experimentell. Für eine bessere Erfahrung und die neuesten Features, nutze unsere freie, native App.", "Use app for a better experience": "Nutze die App für eine bessere Erfahrung", "We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "Wir haben deinen Browser gebeten, sich zu merken, bei welchem Homeserver du dich anmeldest, aber dein Browser hat dies leider vergessen. Gehe zur Anmeldeseite und versuche es erneut.", "Show stickers button": "Sticker-Schaltfläche anzeigen", From 844ed6b0b9c5e8a99aad99192e9506b047d72904 Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Fri, 26 Feb 2021 18:04:45 +0000 Subject: [PATCH 090/420] Translated using Weblate (German) Currently translated at 99.2% (2759 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index e2e2e0718a..b615cda081 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -636,7 +636,7 @@ "Which officially provided instance you are using, if any": "Welche offiziell angebotene Instanz du nutzt, wenn überhaupt eine", "In reply to ": "Als Antwort auf ", "This room is not public. You will not be able to rejoin without an invite.": "Dies ist kein öffentlicher Raum. Du wirst diesen nicht ohne Einladung wieder beitreten können.", - "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s hat den Anzeigenamen auf %(displayName)s geändert.", + "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s hat den Anzeigenamen zu %(displayName)s geändert.", "Failed to set direct chat tag": "Fehler beim Setzen der Direkt-Chat-Markierung", "Failed to remove tag %(tagName)s from room": "Entfernen der Raum-Kennzeichnung %(tagName)s fehlgeschlagen", "Failed to add tag %(tagName)s to room": "Fehler beim Hinzufügen des \"%(tagName)s\"-Tags an dem Raum", @@ -2664,7 +2664,7 @@ "Fill Screen": "Bildschirm ausfüllen", "Voice Call": "Sprachanruf", "Video Call": "Videoanruf", - "Remain on your screen while running": "Bleiben Sie auf Ihrem Bildschirm während der Ausführung von", + "Remain on your screen while running": "Bleib auf deinem Bildschirm während der Ausführung von", "Remain on your screen when viewing another room, when running": "Bleiben Sie auf Ihrem Bildschirm, während Sie einen anderen Raum betrachten, wenn Sie ausführen", "Zimbabwe": "Simbabwe", "Zambia": "Sambia", From e43853d6b0aeda0dd0cf342e378977167a9ae614 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 27 Feb 2021 12:02:24 -0700 Subject: [PATCH 091/420] Use a small fractional value instead --- src/components/views/rooms/ReadReceiptMarker.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.js index 8b2f5d27b5..ba2b3064fd 100644 --- a/src/components/views/rooms/ReadReceiptMarker.js +++ b/src/components/views/rooms/ReadReceiptMarker.js @@ -156,12 +156,14 @@ export default class ReadReceiptMarker extends React.PureComponent { // then shift to the rightmost column, // and then it will drop down to its resting position // - // XXX: We use a `left: 1px` to trick velocity-animate into actually animating. This - // is a very annoying bug where if it thinks there's no change to `left` then it'll + // XXX: We use a fractional left value to trick velocity-animate into actually animating. + // This is a very annoying bug where if it thinks there's no change to `left` then it'll // skip applying it, thus making our read receipt at +14px instead of +0px like it - // should be. This does cause 1px of drift for read receipts, however nobody should - // notice this while it's also falling. - startStyles.push({ top: startTopOffset+'px', left: '1px' }); + // should be. This does cause a tiny amount of drift for read receipts, however with a + // value so small it's not perceived by a user. + // Note: Any smaller values (or trying to interchange units) might cause read receipts to + // fail to fall down or cause gaps. + startStyles.push({ top: startTopOffset+'px', left: '0.001px' }); enterTransitionOpts.push({ duration: bounce ? Math.min(Math.log(Math.abs(startTopOffset)) * 200, 3000) : 300, easing: bounce ? 'easeOutBounce' : 'easeOutCubic', From 188b728f5311e0c607623a58cc336fa434d07b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 28 Feb 2021 13:32:17 +0100 Subject: [PATCH 092/420] Fix read receipts for compact layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/rooms/_GroupLayout.scss | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/res/css/views/rooms/_GroupLayout.scss b/res/css/views/rooms/_GroupLayout.scss index 543e6ed685..903fabc8fd 100644 --- a/res/css/views/rooms/_GroupLayout.scss +++ b/res/css/views/rooms/_GroupLayout.scss @@ -105,16 +105,9 @@ $left-gutter: 64px; } .mx_EventTile_readAvatars { - top: 27px; - } - - &.mx_EventTile_continuation .mx_EventTile_readAvatars, - &.mx_EventTile_emote .mx_EventTile_readAvatars { - top: 5px; - } - - &.mx_EventTile_info .mx_EventTile_readAvatars { - top: 4px; + // This aligns the avatar with the last line of the + // message. We want to move it one line up - 2rem + top: -2rem; } .mx_EventTile_content .markdown-body { From 94fbd7c9b127faaab1c668a101b6b0506798d767 Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Sat, 27 Feb 2021 14:58:41 +0000 Subject: [PATCH 093/420] Translated using Weblate (German) Currently translated at 99.2% (2759 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/de/ --- src/i18n/strings/de_DE.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json index b615cda081..8a07b6cd9f 100644 --- a/src/i18n/strings/de_DE.json +++ b/src/i18n/strings/de_DE.json @@ -3036,7 +3036,7 @@ "Converts the room to a DM": "Wandelt den Raum zu Direktnachricht um", "Something went wrong in confirming your identity. Cancel and try again.": "Bei der Bestätigung deiner Identität ist ein Fehler aufgetreten. Abbrechen und erneut versuchen.", "Use app": "App verwenden", - "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "Element Web ist auf mobilen Endgeräten experimentell. Für eine bessere Erfahrung und die neuesten Features, nutze unsere freie, native App.", + "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "Element Web ist auf mobilen Endgeräten experimentell. Für eine bessere Erfahrung und die neuesten Erweiterungen, nutze unsere freie, native App.", "Use app for a better experience": "Nutze die App für eine bessere Erfahrung", "We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "Wir haben deinen Browser gebeten, sich zu merken, bei welchem Homeserver du dich anmeldest, aber dein Browser hat dies leider vergessen. Gehe zur Anmeldeseite und versuche es erneut.", "Show stickers button": "Sticker-Schaltfläche anzeigen", From d6d5455a11915a313b137b4a8f98bde762441117 Mon Sep 17 00:00:00 2001 From: Thibault Martin Date: Thu, 25 Feb 2021 17:17:39 +0000 Subject: [PATCH 094/420] Translated using Weblate (French) Currently translated at 99.2% (2758 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 260 +++++++++++++++++++-------------------- 1 file changed, 130 insertions(+), 130 deletions(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index c942cae520..304599cd44 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -1,6 +1,6 @@ { "Disinvite": "Désinviter", - "Displays action": "Affiche l'action", + "Displays action": "Affiche l’action", "Download %(text)s": "Télécharger %(text)s", "Emoji": "Émojis", "%(senderName)s ended the call.": "%(senderName)s a terminé l’appel.", @@ -32,14 +32,14 @@ "%(senderName)s banned %(targetName)s.": "%(senderName)s a banni %(targetName)s.", "Ban": "Bannir", "Banned users": "Utilisateurs bannis", - "Bans user with given id": "Bannit l'utilisateur à partir de son identifiant", + "Bans user with given id": "Bannit l’utilisateur à partir de son identifiant", "Call Timeout": "L’appel a dépassé le délai d'attente maximal", "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Impossible de se connecter au serveur d'accueil en HTTP si l'URL dans la barre de votre explorateur est en HTTPS. Utilisez HTTPS ou activez le support des scripts non-vérifiés.", "Change Password": "Changer le mot de passe", "%(senderName)s changed their profile picture.": "%(senderName)s a changé son image de profil.", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s a changé le rang de %(powerLevelDiffText)s.", "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s a changé le nom du salon en %(roomName)s.", - "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s a changé le sujet du salon en \"%(topic)s\".", + "%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s a changé le sujet du salon en « %(topic)s ».", "Changes your display nickname": "Change votre nom affiché", "Click here to fix": "Cliquer ici pour réparer", "Click to mute audio": "Cliquer pour couper le son", @@ -54,7 +54,7 @@ "Create Room": "Créer un salon", "Cryptography": "Chiffrement", "Current password": "Mot de passe actuel", - "/ddg is not a command": "/ddg n'est pas une commande", + "/ddg is not a command": "/ddg n’est pas une commande", "Deactivate Account": "Fermer le compte", "Decrypt %(text)s": "Déchiffrer %(text)s", "Deops user with given id": "Retire le rang d’opérateur d’un utilisateur à partir de son identifiant", @@ -66,7 +66,7 @@ "Failed to reject invite": "Échec du rejet de l'invitation", "Failed to reject invitation": "Échec du rejet de l'invitation", "Failed to send email": "Échec de l’envoi de l’e-mail", - "Failed to send request.": "Échec de l'envoi de la requête.", + "Failed to send request.": "Échec de l’envoi de la requête.", "Failed to set display name": "Échec de l'enregistrement du nom affiché", "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s a accepté l’invitation de %(displayName)s.", "Access Token:": "Jeton d’accès :", @@ -101,17 +101,17 @@ "%(targetName)s joined the room.": "%(targetName)s a rejoint le salon.", "%(senderName)s kicked %(targetName)s.": "%(senderName)s a expulsé %(targetName)s.", "Kick": "Expulser", - "Kicks user with given id": "Expulse l'utilisateur à partir de son identifiant", + "Kicks user with given id": "Expulse l’utilisateur à partir de son identifiant", "Labs": "Labo", "Leave room": "Quitter le salon", "%(targetName)s left the room.": "%(targetName)s a quitté le salon.", "Logout": "Se déconnecter", "Low priority": "Priorité basse", - "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s a rendu l'historique visible à tous les membres du salon, depuis le moment où ils ont été invités.", - "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s a rendu l'historique visible à tous les membres du salon, depuis le moment où ils ont rejoint.", - "%(senderName)s made future room history visible to all room members.": "%(senderName)s a rendu l'historique visible à tous les membres du salon.", - "%(senderName)s made future room history visible to anyone.": "%(senderName)s a rendu l'historique visible à n'importe qui.", - "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s a rendu l'historique visible à inconnu (%(visibility)s).", + "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s a rendu l’historique visible à tous les membres du salon, depuis le moment où ils ont été invités.", + "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s a rendu l’historique visible à tous les membres du salon, à partir de leur arrivée.", + "%(senderName)s made future room history visible to all room members.": "%(senderName)s a rendu l’historique visible à tous les membres du salon.", + "%(senderName)s made future room history visible to anyone.": "%(senderName)s a rendu l’historique visible à tout le monde.", + "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s a rendu l’historique visible à inconnu (%(visibility)s).", "Manage Integrations": "Gestion des intégrations", "Missing room_id in request": "Absence du room_id dans la requête", "Missing user_id in request": "Absence du user_id dans la requête", @@ -120,7 +120,7 @@ "New passwords don't match": "Les mots de passe ne correspondent pas", "New passwords must match each other.": "Les nouveaux mots de passe doivent être identiques.", "not specified": "non spécifié", - "(not supported by this browser)": "(non supporté par ce navigateur)", + "(not supported by this browser)": "(non pris en charge par ce navigateur)", "": "", "No more results": "Fin des résultats", "No results": "Pas de résultat", @@ -141,7 +141,7 @@ "No users have specific privileges in this room": "Aucun utilisateur n’a de privilège spécifique dans ce salon", "olm version:": "version de olm :", "Please check your email and click on the link it contains. Once this is done, click continue.": "Veuillez vérifier vos e-mails et cliquer sur le lien que vous avez reçu. Puis cliquez sur continuer.", - "Power level must be positive integer.": "Le niveau d'autorité doit être un entier positif.", + "Power level must be positive integer.": "Le rang doit être un entier positif.", "Privileged Users": "Utilisateurs privilégiés", "Profile": "Profil", "Reason": "Raison", @@ -151,10 +151,10 @@ "%(senderName)s removed their profile picture.": "%(senderName)s a supprimé son image de profil.", "%(senderName)s requested a VoIP conference.": "%(senderName)s a demandé une téléconférence audio.", "Return to login screen": "Retourner à l’écran de connexion", - "%(brand)s does not have permission to send you notifications - please check your browser settings": "%(brand)s n’a pas la permission de vous envoyer des notifications - merci de vérifier les paramètres de votre navigateur", - "%(brand)s was not given permission to send notifications - please try again": "%(brand)s n’a pas reçu la permission de vous envoyer des notifications - veuillez réessayer", + "%(brand)s does not have permission to send you notifications - please check your browser settings": "%(brand)s n’a pas l’autorisation de vous envoyer des notifications - merci de vérifier les paramètres de votre navigateur", + "%(brand)s was not given permission to send notifications - please try again": "%(brand)s n’a pas reçu l’autorisation de vous envoyer des notifications - veuillez réessayer", "%(brand)s version:": "Version de %(brand)s :", - "Room %(roomId)s not visible": "Le salon %(roomId)s n'est pas visible", + "Room %(roomId)s not visible": "Le salon %(roomId)s n’est pas visible", "Room Colour": "Couleur du salon", "Rooms": "Salons", "Search": "Rechercher", @@ -165,7 +165,7 @@ "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s a invité %(targetDisplayName)s à rejoindre le salon.", "Server error": "Erreur du serveur", "Server may be unavailable, overloaded, or search timed out :(": "Le serveur semble être inaccessible, surchargé ou la recherche a expiré :(", - "Server may be unavailable, overloaded, or you hit a bug.": "Le serveur semble être indisponible, surchargé ou vous avez rencontré un problème.", + "Server may be unavailable, overloaded, or you hit a bug.": "Le serveur semble être indisponible, surchargé ou vous êtes tombé sur un bug.", "Server unavailable, overloaded, or something else went wrong.": "Le serveur semble être inaccessible, surchargé ou quelque chose s'est mal passé.", "Session ID": "Identifiant de session", "%(senderName)s set a profile picture.": "%(senderName)s a défini une image de profil.", @@ -175,7 +175,7 @@ "Sign in": "Se connecter", "Sign out": "Se déconnecter", "%(count)s of your messages have not been sent.|other": "Certains de vos messages n’ont pas été envoyés.", - "Someone": "Quelqu'un", + "Someone": "Quelqu’un", "Submit": "Soumettre", "Success": "Succès", "This email address is already in use": "Cette adresse e-mail est déjà utilisée", @@ -183,7 +183,7 @@ "The email address linked to your account must be entered.": "L’adresse e-mail liée à votre compte doit être renseignée.", "The remote side failed to pick up": "Le correspondant n’a pas décroché", "This room has no local addresses": "Ce salon n'a pas d'adresse locale", - "This room is not recognised.": "Ce salon n'est pas reconnu.", + "This room is not recognised.": "Ce salon n’est pas reconnu.", "This doesn't appear to be a valid email address": "Cette adresse e-mail ne semble pas valide", "This phone number is already in use": "Ce numéro de téléphone est déjà utilisé", "This room is not accessible by remote Matrix servers": "Ce salon n’est pas accessible par les serveurs Matrix distants", @@ -195,8 +195,8 @@ "Unable to verify email address.": "Impossible de vérifier l’adresse e-mail.", "Unban": "Révoquer le bannissement", "%(senderName)s unbanned %(targetName)s.": "%(senderName)s a révoqué le bannissement de %(targetName)s.", - "Unable to capture screen": "Impossible de faire une capture d'écran", - "Unable to enable Notifications": "Impossible d'activer les notifications", + "Unable to capture screen": "Impossible de faire une capture d’écran", + "Unable to enable Notifications": "Impossible d’activer les notifications", "Unmute": "Activer le son", "Upload avatar": "Télécharger une photo de profil", "Upload Failed": "Échec de l’envoi", @@ -208,19 +208,19 @@ "Voice call": "Appel vocal", "VoIP conference finished.": "Téléconférence VoIP terminée.", "VoIP conference started.": "Téléconférence VoIP démarrée.", - "VoIP is unsupported": "Voix sur IP non gérée", + "VoIP is unsupported": "Voix sur IP non prise en charge", "Warning!": "Attention !", "Who can access this room?": "Qui peut accéder au salon ?", "Who can read history?": "Qui peut lire l'historique ?", "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s a annulé l’invitation de %(targetName)s.", "You are already in a call.": "Vous avez déjà un appel en cours.", - "You cannot place a call with yourself.": "Vous ne pouvez pas passer d'appel avec vous-même.", - "You cannot place VoIP calls in this browser.": "Vous ne pouvez pas passer d'appel en Voix sur IP dans ce navigateur.", + "You cannot place a call with yourself.": "Vous ne pouvez pas passer d’appel avec vous-même.", + "You cannot place VoIP calls in this browser.": "Vous ne pouvez pas passer d’appel en Voix sur IP dans ce navigateur.", "You do not have permission to post to this room": "Vous n’avez pas la permission de poster dans ce salon", "You have no visible notifications": "Vous n'avez pas de notification visible", - "You need to be able to invite users to do that.": "Vous devez être capable d’inviter des utilisateurs pour faire ça.", + "You need to be able to invite users to do that.": "Vous devez avoir l’autorisation d’inviter des utilisateurs pour faire ceci.", "You need to be logged in.": "Vous devez être identifié.", - "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Votre adresse e-mail ne semble pas être associée à un identifiant Matrix sur ce serveur d'accueil.", + "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Votre adresse e-mail ne semble pas être associée à un identifiant Matrix sur ce serveur d’accueil.", "You seem to be in a call, are you sure you want to quit?": "Vous semblez avoir un appel en cours, voulez-vous vraiment partir ?", "You seem to be uploading files, are you sure you want to quit?": "Vous semblez être en train d'envoyer des fichiers, voulez-vous vraiment partir ?", "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Vous ne pourrez pas annuler cette modification car vous promouvez l’utilisateur au même rang que le vôtre.", @@ -377,7 +377,7 @@ "%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (rang %(powerLevelNumber)s)", "(could not connect media)": "(impossible de se connecter au média)", "(no answer)": "(pas de réponse)", - "(unknown failure: %(reason)s)": "(erreur inconnue : %(reason)s)", + "(unknown failure: %(reason)s)": "(erreur inconnue : %(reason)s)", "Your browser does not support the required cryptography extensions": "Votre navigateur ne supporte pas les extensions cryptographiques nécessaires", "Not a valid %(brand)s keyfile": "Fichier de clé %(brand)s non valide", "Authentication check failed: incorrect password?": "Erreur d’authentification : mot de passe incorrect ?", @@ -388,13 +388,13 @@ "Add a widget": "Ajouter un widget", "Allow": "Autoriser", "Delete widget": "Supprimer le widget", - "Define the power level of a user": "Définir le rang d'un utilisateur", + "Define the power level of a user": "Définir le rang d’un utilisateur", "Edit": "Modifier", "Enable automatic language detection for syntax highlighting": "Activer la détection automatique de la langue pour la correction orthographique", "To get started, please pick a username!": "Pour commencer, choisissez un nom d'utilisateur !", "Unable to create widget.": "Impossible de créer le widget.", - "You are not in this room.": "Vous n'êtes pas dans ce salon.", - "You do not have permission to do that in this room.": "Vous n'avez pas la permission d'effectuer cette action dans ce salon.", + "You are not in this room.": "Vous n’êtes pas dans ce salon.", + "You do not have permission to do that in this room.": "Vous n’avez pas l’autorisation d’effectuer cette action dans ce salon.", "Example": "Exemple", "Create": "Créer", "Featured Rooms:": "Salons mis en avant :", @@ -411,21 +411,21 @@ "Copied!": "Copié !", "Failed to copy": "Échec de la copie", "%(widgetName)s widget modified by %(senderName)s": "Widget %(widgetName)s modifié par %(senderName)s", - "Who would you like to add to this community?": "Qui souhaitez-vous ajouter à cette communauté ?", - "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Attention : toute personne ajoutée à une communauté sera visible par tous ceux connaissant l'identifiant de la communauté", - "Invite new community members": "Inviter de nouveaux membres dans cette communauté", - "Which rooms would you like to add to this community?": "Quels salons souhaitez-vous ajouter à cette communauté ?", + "Who would you like to add to this community?": "Qui souhaitez-vous ajouter à cette communauté ?", + "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Attention : toute personne ajoutée à une communauté sera visible par tous ceux connaissant l’identifiant de la communauté", + "Invite new community members": "Inviter de nouvelles personnes dans cette communauté", + "Which rooms would you like to add to this community?": "Quels salons souhaitez-vous ajouter à cette communauté ?", "Add rooms to the community": "Ajouter des salons à la communauté", "Add to community": "Ajouter à la communauté", - "Failed to invite the following users to %(groupId)s:": "Échec de l'invitation des utilisateurs à %(groupId)s :", - "Failed to invite users to community": "Échec de l'invitation d'utilisateurs à la communauté", - "Failed to invite users to %(groupId)s": "Échec de l'invitation d'utilisateurs à %(groupId)s", - "Failed to add the following rooms to %(groupId)s:": "Échec de l'ajout des salons suivants à %(groupId)s :", + "Failed to invite the following users to %(groupId)s:": "Échec de l’invitation des utilisateurs suivants à %(groupId)s :", + "Failed to invite users to community": "Échec de l’invitation des utilisateurs à la communauté", + "Failed to invite users to %(groupId)s": "Échec de l’invitation des utilisateurs à %(groupId)s", + "Failed to add the following rooms to %(groupId)s:": "Échec de l’ajout des salons suivants à %(groupId)s :", "Ignored user": "Utilisateur ignoré", - "You are now ignoring %(userId)s": "Dorénavant vous ignorez %(userId)s", - "Unignored user": "Utilisateur n'étant plus ignoré", - "You are no longer ignoring %(userId)s": "Vous n'ignorez plus %(userId)s", - "Invite to Community": "Inviter dans la Communauté", + "You are now ignoring %(userId)s": "Vous ignorez désormais %(userId)s", + "Unignored user": "L’utilisateur n’est plus ignoré", + "You are no longer ignoring %(userId)s": "Vous n’ignorez plus %(userId)s", + "Invite to Community": "Inviter dans la communauté", "Communities": "Communautés", "Message Pinning": "Épingler un message", "Mention": "Mentionner", @@ -573,7 +573,7 @@ "Mirror local video feed": "Inverser horizontalement la vidéo locale (effet miroir)", "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "Un e-mail a été envoyé à %(emailAddress)s. Après avoir suivi le lien présent dans celui-ci, cliquez ci-dessous.", "Ignores a user, hiding their messages from you": "Ignore un utilisateur, en masquant ses messages", - "Stops ignoring a user, showing their messages going forward": "N'ignore plus un utilisateur, en affichant ses messages à partir de maintenant", + "Stops ignoring a user, showing their messages going forward": "Arrête d’ignorer un utilisateur, en affichant ses messages à partir de maintenant", "The visibility of '%(roomName)s' in %(groupId)s could not be updated.": "La visibilité de \"%(roomName)s\" dans %(groupId)s n'a pas pu être mise à jour.", "Visibility in Room List": "Visibilité dans la liste des salons", "Visible to everyone": "Visible pour tout le monde", @@ -609,7 +609,7 @@ "Display your community flair in rooms configured to show it.": "Sélectionnez les badges dans les paramètres de chaque salon pour les afficher.", "expand": "développer", "collapse": "réduire", - "Call Failed": "L'appel a échoué", + "Call Failed": "L’appel a échoué", "Send": "Envoyer", "Old cryptography data detected": "Anciennes données de chiffrement détectées", "Data from an older version of %(brand)s has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "Nous avons détecté des données d'une ancienne version de %(brand)s. Le chiffrement de bout en bout n'aura pas fonctionné correctement sur l'ancienne version. Les messages chiffrés échangés récemment dans l'ancienne version ne sont peut-être pas déchiffrables dans cette version. Les échanges de message avec cette version peuvent aussi échouer. Si vous rencontrez des problèmes, déconnectez-vous puis reconnectez-vous. Pour conserver l'historique des messages, exportez puis réimportez vos clés de chiffrement.", @@ -625,13 +625,13 @@ "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Le respect de votre vie privée est important pour nous, donc nous ne collectons aucune donnée personnelle ou permettant de vous identifier pour nos statistiques.", "Learn more about how we use analytics.": "En savoir plus sur notre utilisation des statistiques.", "The information being sent to us to help make %(brand)s better includes:": "Les informations qui nous sont envoyées et qui nous aident à améliorer %(brand)s comportent :", - "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Si la page contient des informations permettant de vous identifier, comme un salon, un identifiant d'utilisateur ou de groupe, ces données sont enlevées avant qu'elle ne soit envoyée au serveur.", + "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Si la page contient des informations permettant de vous identifier, comme un salon, un identifiant d’utilisateur ou de groupe, ces données sont enlevées avant qu’elle ne soit envoyée au serveur.", "The platform you're on": "La plateforme que vous utilisez", "The version of %(brand)s": "La version de %(brand)s", "Your language of choice": "La langue que vous avez choisie", - "Which officially provided instance you are using, if any": "L'instance officielle que vous utilisez, si vous en utilisez une", - "Whether or not you're using the Richtext mode of the Rich Text Editor": "Si vous utilisez le mode « texte enrichi » de l'éditeur de texte enrichi", - "Your homeserver's URL": "L'URL de votre serveur d'accueil", + "Which officially provided instance you are using, if any": "L’instance officielle que vous utilisez, si vous en utilisez une", + "Whether or not you're using the Richtext mode of the Rich Text Editor": "Si vous utilisez le mode « texte enrichi » de l’éditeur de texte enrichi", + "Your homeserver's URL": "L’URL de votre serveur d’accueil", "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s %(day)s %(monthName)s %(fullYear)s", "This room is not public. You will not be able to rejoin without an invite.": "Ce salon n'est pas public. Vous ne pourrez pas y revenir sans invitation.", "Community IDs cannot be empty.": "Les identifiants de communauté ne peuvent pas être vides.", @@ -648,7 +648,7 @@ "Code": "Code", "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "Si vous avez signalé un bug via GitHub, les journaux de débogage peuvent nous aider à identifier le problème. Les journaux de débogage contiennent des données d'utilisation de l'application dont votre nom d'utilisateur, les identifiants ou alias des salons ou groupes que vous avez visité et les noms d'utilisateur des autres participants. Ils ne contiennent pas les messages.", "Submit debug logs": "Envoyer les journaux de débogage", - "Opens the Developer Tools dialog": "Ouvre la fenêtre des Outils de développeur", + "Opens the Developer Tools dialog": "Ouvre la fenêtre des outils de développeur", "Unable to join community": "Impossible de rejoindre la communauté", "Unable to leave community": "Impossible de quitter la communauté", "Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Les changements effectués au nom et à l'avatar de votre communauté peuvent prendre jusqu'à 30 minutes avant d'être vus par d'autres utilisateurs.", @@ -784,7 +784,7 @@ "Preparing to send logs": "Préparation d'envoi des journaux", "Missing roomId.": "Identifiant de salon manquant.", "Popout widget": "Détacher le widget", - "Every page you use in the app": "Toutes les pages que vous utilisez dans l'application", + "Every page you use in the app": "Toutes les pages que vous utilisez dans l’application", "e.g. ": "par ex. ", "Your device resolution": "La résolution de votre appareil", "Always show encryption icons": "Toujours afficher les icônes de chiffrement", @@ -832,8 +832,8 @@ "Demote yourself?": "Vous rétrograder ?", "Demote": "Rétrograder", "This event could not be displayed": "Cet événement n'a pas pu être affiché", - "Permission Required": "Permission requise", - "You do not have permission to start a conference call in this room": "Vous n'avez pas la permission de lancer un appel en téléconférence dans ce salon", + "Permission Required": "Autorisation requise", + "You do not have permission to start a conference call in this room": "Vous n’avez pas l’autorisation de lancer un appel en téléconférence dans ce salon", "A call is currently being placed!": "Un appel est en cours !", "Failed to remove widget": "Échec de la suppression du widget", "An error ocurred whilst trying to remove the widget from the room": "Une erreur est survenue lors de la suppression du widget du salon", @@ -862,8 +862,8 @@ "Upgrade this room to version %(version)s": "Mettre à niveau ce salon vers la version %(version)s", "Forces the current outbound group session in an encrypted room to be discarded": "Force la session de groupe sortante actuelle dans un salon chiffré à être rejetée", "Unable to connect to Homeserver. Retrying...": "Impossible de se connecter au serveur d'accueil. Reconnexion...", - "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s a défini l'adresse principale pour ce salon comme %(address)s.", - "%(senderName)s removed the main address for this room.": "%(senderName)s a supprimé l'adresse principale de ce salon.", + "%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s a défini l’adresse principale pour ce salon comme %(address)s.", + "%(senderName)s removed the main address for this room.": "%(senderName)s a supprimé l’adresse principale de ce salon.", "%(brand)s now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "%(brand)s utilise maintenant 3 à 5 fois moins de mémoire, en ne chargeant les informations des autres utilisateurs que quand elles sont nécessaires. Veuillez patienter pendant que l'on se resynchronise avec le serveur !", "Updating %(brand)s": "Mise à jour de %(brand)s", "Before submitting logs, you must create a GitHub issue to describe your problem.": "Avant de soumettre vos journaux, vous devez créer une « issue » sur GitHub pour décrire votre problème.", @@ -934,7 +934,7 @@ "Common names and surnames are easy to guess": "Les noms et prénoms répandus sont faciles à deviner", "Use a longer keyboard pattern with more turns": "Utilisez un schéma plus long et avec plus de variations", "Failed to load group members": "Échec du chargement des membres du groupe", - "Failed to invite users to the room:": "Échec de l'invitation d'utilisateurs dans le salon :", + "Failed to invite users to the room:": "Échec de l’invitation d'utilisateurs dans le salon :", "There was an error joining the room": "Une erreur est survenue en rejoignant le salon", "You do not have permission to invite people to this room.": "Vous n'avez pas la permission d'envoyer des invitations dans ce salon.", "User %(user_id)s does not exist": "L'utilisateur %(user_id)s n'existe pas", @@ -968,14 +968,14 @@ "Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Impossible de trouver les profils pour les identifiants Matrix listés ci-dessous. Voulez-vous quand même les inviter ?", "Invite anyway and never warn me again": "Inviter quand même et ne plus me prévenir", "Invite anyway": "Inviter quand même", - "Whether or not you're logged in (we don't record your username)": "Si vous êtes connecté ou pas (votre nom d'utilisateur n'est pas enregistré)", + "Whether or not you're logged in (we don't record your username)": "Si vous êtes connecté ou pas (votre nom d'utilisateur n’est pas enregistré)", "Upgrades a room to a new version": "Met à niveau un salon vers une nouvelle version", "Sets the room name": "Définit le nom du salon", "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s a mis à niveau ce salon.", "%(displayName)s is typing …": "%(displayName)s est en train d'écrire…", - "%(names)s and %(count)s others are typing …|other": "%(names)s et %(count)s autres sont en train d'écrire…", - "%(names)s and %(count)s others are typing …|one": "%(names)s et un autre sont en train d'écrire…", - "%(names)s and %(lastPerson)s are typing …": "%(names)s et %(lastPerson)s sont en train d'écrire…", + "%(names)s and %(count)s others are typing …|other": "%(names)s et %(count)s autres sont en train d’écrire…", + "%(names)s and %(count)s others are typing …|one": "%(names)s et un autre sont en train d’écrire…", + "%(names)s and %(lastPerson)s are typing …": "%(names)s et %(lastPerson)s sont en train d’écrire…", "Enable Emoji suggestions while typing": "Activer la suggestion d’émojis lors de la saisie", "Render simple counters in room header": "Afficher des compteurs simples dans l’en-tête des salons", "Show a placeholder for removed messages": "Afficher les messages supprimés", @@ -1084,7 +1084,7 @@ "A new recovery passphrase and key for Secure Messages have been detected.": "Un nouveau mot de passe et une nouvelle clé de récupération pour les messages sécurisés ont été détectés.", "Recovery Method Removed": "Méthode de récupération supprimée", "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Si vous n'avez pas supprimé la méthode de récupération, un attaquant peut être en train d'essayer d'accéder à votre compte. Modifiez le mot de passe de votre compte et configurez une nouvelle méthode de récupération dans les réglages.", - "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Le fichier \"%(fileName)s\" dépasse la taille limite autorisée par ce serveur pour les téléchargements", + "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Le fichier « %(fileName)s » dépasse la taille limite autorisée par ce serveur pour les envois", "Gets or sets the room topic": "Récupère ou définit le sujet du salon", "This room has no topic.": "Ce salon n'a pas de sujet.", "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s a rendu le salon public à tous ceux qui en connaissent le lien.", @@ -1092,7 +1092,7 @@ "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s a changé la règle d’adhésion en %(rule)s", "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s a autorisé les visiteurs à rejoindre le salon.", "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s a empêché les visiteurs de rejoindre le salon.", - "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s a changé l'accès des visiteurs en %(rule)s", + "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s a changé l’accès des visiteurs en %(rule)s", "Group & filter rooms by custom tags (refresh to apply changes)": "Grouper et filtrer les salons grâce à des étiquettes personnalisées (actualiser pour appliquer les changements)", "Verify this user by confirming the following emoji appear on their screen.": "Vérifier cet utilisateur en confirmant que les émojis suivant apparaissent sur son écran.", "Unable to find a supported verification method.": "Impossible de trouver une méthode de vérification prise en charge.", @@ -1206,7 +1206,7 @@ "Create your Matrix account on %(serverName)s": "Créez votre compte Matrix sur %(serverName)s", "Could not load user profile": "Impossible de charger le profil de l’utilisateur", "Your Matrix account on %(serverName)s": "Votre compte Matrix sur %(serverName)s", - "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Ajoute ¯\\_(ツ)_/¯ devant un message en texte brut", + "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Ajoute ¯\\_(ツ)_/¯ en préfixe du message", "User %(userId)s is already in the room": "L’utilisateur %(userId)s est déjà membre du salon", "The user must be unbanned before they can be invited.": "Le bannissement de l’utilisateur doit être révoqué avant de pouvoir l’inviter.", "Upgrade to your own domain": "Mettre à niveau vers votre propre domaine", @@ -1452,7 +1452,7 @@ "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.": "Si vous ne voulez pas utiliser pour découvrir et être découvrable par les contacts que vous connaissez, saisissez un autre serveur d’identité ci-dessous.", "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "L’utilisation d’un serveur d’identité est optionnelle. Si vous ne choisissez pas d’utiliser un serveur d’identité, les autres utilisateurs ne pourront pas vous découvrir et vous ne pourrez pas en inviter par e-mail ou par téléphone.", "Do not use an identity server": "Ne pas utiliser de serveur d’identité", - "You do not have the required permissions to use this command.": "Vous n’avez pas les permissions nécessaires pour utiliser cette commande.", + "You do not have the required permissions to use this command.": "Vous n’avez pas les autorisations nécessaires pour utiliser cette commande.", "Upgrade the room": "Mettre à niveau le salon", "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Renseignez un e-mail pour la récupération de compte. Utilisez un e-mail ou un téléphone pour être éventuellement découvrable par des contacts existants.", "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Renseignez un e-mail pour la récupération de compte. Utilisez un e-mail pour être éventuellement découvrable par des contacts existants.", @@ -1580,7 +1580,7 @@ "Unread messages.": "Messages non lus.", "Show tray icon and minimize window to it on close": "Afficher l’icône dans la barre d’état et minimiser la fenêtre lors de la fermeture", "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Cette action nécessite l’accès au serveur d’identité par défaut afin de valider une adresse e-mail ou un numéro de téléphone, mais le serveur n’a aucune condition de service.", - "Trust": "Confiance", + "Trust": "Faire confiance", "Message Actions": "Actions de message", "%(name)s (%(userId)s)": "%(name)s (%(userId)s)", "You verified %(name)s": "Vous avez vérifié %(name)s", @@ -1665,9 +1665,9 @@ "Verification Request": "Demande de vérification", "Match system theme": "S’adapter au thème du système", "%(senderName)s placed a voice call.": "%(senderName)s a passé un appel vocal.", - "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s a passé un appel vocal. (pas pris en charge par ce navigateur)", + "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s a passé un appel vocal. (non pris en charge par ce navigateur)", "%(senderName)s placed a video call.": "%(senderName)s a passé un appel vidéo.", - "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s a passé un appel vidéo. (pas pris en charge par ce navigateur)", + "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s a passé un appel vidéo. (non pris en charge par ce navigateur)", "Clear notifications": "Vider les notifications", "Customise your experience with experimental labs features. Learn more.": "Personnalisez votre expérience avec des fonctionnalités expérimentales du labo. En savoir plus.", "Error upgrading room": "Erreur lors de la mise à niveau du salon", @@ -1838,7 +1838,7 @@ "Unknown (user, session) pair:": "Paire (utilisateur, session) inconnue :", "Session already verified!": "Session déjà vérifiée !", "WARNING: Session already verified, but keys do NOT MATCH!": "ATTENTION : La session a déjà été vérifiée mais les clés NE CORRESPONDENT PAS !", - "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "ATTENTION : ÉCHEC DE LA VÉRIFICATION DE CLÉ ! La clé de signature pour %(userId)s et la session %(deviceId)s est « %(fprint)s » que ne correspond pas à la clé fournie « %(fingerprint)s ». Cela pourrait signifier que vos communications sont interceptées !", + "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "ATTENTION : ÉCHEC DE LA VÉRIFICATION DE CLÉ ! La clé de signature pour %(userId)s et la session %(deviceId)s est « %(fprint)s  ce qui ne correspond pas à la clé fournie « %(fingerprint)s ». Cela pourrait signifier que vos communications sont interceptées !", "The signing key you provided matches the signing key you received from %(userId)s's session %(deviceId)s. Session marked as verified.": "La clé de signature que vous avez fournie correspond à celle que vous avez reçue de la session %(deviceId)s de %(userId)s. Session marquée comme vérifiée.", "Never send encrypted messages to unverified sessions from this session": "Ne jamais envoyer de messages chiffrés aux sessions non vérifiées depuis cette session", "Never send encrypted messages to unverified sessions in this room from this session": "Ne jamais envoyer des messages chiffrés aux sessions non vérifiées dans ce salon depuis cette session", @@ -1940,7 +1940,7 @@ "%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s (%(userId)s) s’est connecté à une nouvelle session sans la vérifier :", "Ask this user to verify their session, or manually verify it below.": "Demandez à cet utilisateur de vérifier sa session, ou vérifiez-la manuellement ci-dessous.", "Verify by scanning": "Vérifier en scannant", - "Whether you're using %(brand)s on a device where touch is the primary input mechanism": "Si vous utilisez %(brand)s sur un appareil où le toucher est le mécanisme primaire de saisie", + "Whether you're using %(brand)s on a device where touch is the primary input mechanism": "Si vous utilisez %(brand)s sur un appareil où le tactile est le mode principal de saisie", "Whether you're using %(brand)s as an installed Progressive Web App": "Si vous utilisez %(brand)s en tant qu’application web progressive (PWA)", "Your user agent": "Votre agent utilisateur", "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.": "La session que vous essayez de vérifier ne prend pas en charge les codes QR ou la vérification d’émojis, qui sont les méthodes prises en charge par %(brand)s. Essayez avec un autre client.", @@ -1960,7 +1960,7 @@ "The internet connection either session is using": "La connection internet de l'une des sessions", "We recommend you change your password and recovery key in Settings immediately": "Nous vous recommandons de changer votre mot de passe et la clé de récupération dans Paramètres dès que possible", "Sign In or Create Account": "Se connecter ou créer un compte", - "Use your account or create a new one to continue.": "Utilisez votre compte ou créez un nouveau compte pour continuer.", + "Use your account or create a new one to continue.": "Utilisez votre compte ou créez en un pour continuer.", "Create Account": "Créer un compte", "Order rooms by name": "Trier les salons par nom", "Show rooms with unread notifications first": "Afficher en premier les salons avec des notifications non lues", @@ -2378,7 +2378,7 @@ "%(brand)s Desktop": "%(brand)s Desktop", "%(brand)s iOS": "%(brand)s iOS", "%(brand)s X for Android": "%(brand)s X pour Android", - "Are you sure you want to cancel entering passphrase?": "Souhaitez-vous vraiment annuler l'entrée de la phrase de passe ?", + "Are you sure you want to cancel entering passphrase?": "Souhaitez-vous vraiment annuler la saisie de la phrase de passe ?", "Unexpected server error trying to leave the room": "Erreur de serveur inattendue en essayant de quitter le salon", "Error leaving room": "Erreur en essayant de quitter le salon", "The person who invited you already left the room.": "La personne vous ayant invité a déjà quitté le salon.", @@ -2400,18 +2400,18 @@ "The operation could not be completed": "L'opération n'a pas pu être terminée", "Failed to save your profile": "Erreur lors de l'enregistrement du profile", "Unknown App": "Application inconnue", - "%(senderName)s declined the call.": "%(senderName)s a refusé l'appel.", + "%(senderName)s declined the call.": "%(senderName)s a refusé l’appel.", "(an error occurred)": "(une erreur est survenue)", "(connection failed)": "(échec de connexion)", - "%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s a changé les paramètres d'accès du serveur pour ce salon.", - "%(senderDisplayName)s set the server ACLs for this room.": "%(senderDisplayName)s a défini les paramètres d'accès du serveur pour ce salon.", - "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Ajoute ( ͡° ͜ʖ ͡°) devant un message en texte brut", + "%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s a changé les paramètres d’accès du serveur pour ce salon.", + "%(senderDisplayName)s set the server ACLs for this room.": "%(senderDisplayName)s a défini les paramètres d’accès du serveur pour ce salon.", + "Prepends ( ͡° ͜ʖ ͡°) to a plain-text message": "Ajoute ( ͡° ͜ʖ ͡°) en préfixe du message", "This will end the conference for everyone. Continue?": "Ceci arrêtera la téléconférence pour tout le monde. Continuer ?", "End conference": "Finir la téléconférence", - "The call was answered on another device.": "L'appel a été répondu sur un autre appareil.", + "The call was answered on another device.": "L’appel a été répondu sur un autre appareil.", "Answered Elsewhere": "Répondu autre-part", - "The call could not be established": "L'appel n'a pas pu être établi", - "The other party declined the call.": "L'autre personne a décliné l'appel.", + "The call could not be established": "L’appel n’a pas pu être établi", + "The other party declined the call.": "Le correspondant a décliné l’appel.", "Call Declined": "Appel rejeté", "Ignored attempt to disable encryption": "Essai de désactiver le chiffrement ignoré", "Add users and servers you want to ignore here. Use asterisks to have %(brand)s match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Ajoutez les utilisateurs et les serveurs que vous voulez ignorer ici. Utilisez des astérisques pour que %(brand)s comprenne tous les caractères. Par exemple, @bot:* va ignorer tous les utilisateurs ayant le nom 'bot' sur n'importe quel serveur.", @@ -2428,8 +2428,8 @@ "Offline encrypted messaging using dehydrated devices": "Messagerie hors-ligne chiffrée utilisant des appareils déshydratés", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Prototype de communautés v2. Requiert un serveur d'accueil compatible. Très expérimental - à utiliser avec précaution.", "Safeguard against losing access to encrypted messages & data": "Sécurité contre la perte d'accès aux messages & données chiffrées", - "(their device couldn't start the camera / microphone)": "(son appareil ne peut pas démarrer la caméra / le microphone)", - "🎉 All servers are banned from participating! This room can no longer be used.": "🎉 Tous les serveurs sont interdits de participation ! Ce salon ne peut plus être utilisé.", + "(their device couldn't start the camera / microphone)": "(leur appareil ne peut pas démarrer la caméra/le microphone)", + "🎉 All servers are banned from participating! This room can no longer be used.": "🎉 Tous les serveurs ont été bannis ! Ce salon ne peut plus être utilisé.", "What's the name of your community or team?": "Quel est le nom de votre communauté ou équipe ?", "You can change this later if needed.": "Vous pouvez modifier ceci après si besoin.", "Use this when referencing your community to others. The community ID cannot be changed.": "Utilisez ceci lorsque vous faites référence à votre communauté aux autres. L'identifiant de la communauté ne peut pas être modifié.", @@ -2570,12 +2570,12 @@ "Afghanistan": "Afghanistan", "United States": "États-Unis", "United Kingdom": "Royaume-Uni", - "You've reached the maximum number of simultaneous calls.": "Vous avez atteint le nombre maximum d'appels en simultané.", - "No other application is using the webcam": "Aucune autre application n'est en train d'utiliser la caméra", - "A microphone and webcam are plugged in and set up correctly": "Un microphone et une caméra sont branchées et bien configurées", - "Unable to access webcam / microphone": "Impossible d'accéder à la caméra ou microphone", - "Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "La fonction a échoué car le microphone n'a pas pu être accédé. Vérifiez qu'un microphone est branché et bien configuré.", - "Unable to access microphone": "Impossible d'accéder au microphone", + "You've reached the maximum number of simultaneous calls.": "Vous avez atteint le nombre maximum d’appels en simultané.", + "No other application is using the webcam": "Aucune autre application n’est en train d’utiliser la caméra", + "A microphone and webcam are plugged in and set up correctly": "Un microphone et une caméra sont branchées et bien configurés", + "Unable to access webcam / microphone": "Impossible d’accéder à la caméra ou au microphone", + "Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "La fonction a échoué faute de pouvoir accéder au microphone. Vérifiez qu’un microphone est branché et bien configuré.", + "Unable to access microphone": "Impossible d’accéder au microphone", "Belgium": "Belgique", "Belarus": "Biélorussie", "Barbados": "Barbade", @@ -2591,15 +2591,15 @@ "Antigua & Barbuda": "Antigue-et-Barbude", "Antarctica": "Antarctique", "Anguilla": "Anguilla", - "Angola": "République d'Angola", + "Angola": "République d’Angola", "Andorra": "Andorre", "American Samoa": "Samoa américaines", "Invite someone using their name, email address, username (like ) or share this room.": "Invitez quelqu'un via leur nom, e-mail ou nom d'utilisateur (p. ex. ) ou partagez ce salon.", "Start a conversation with someone using their name, email address or username (like ).": "Commencer une conversation avec quelqu'un via leur nom, e-mail ou nom d'utilisateur (comme par exemple ).", - "Too Many Calls": "Trop d'appels", - "Permission is granted to use the webcam": "Permission accordée pour l'utilisation de la webcam", - "Call failed because webcam or microphone could not be accessed. Check that:": "La fonction a échoué car la webcam ou le microphone ne pouvait pas être accédé. Vérifiez que :", - "Send stickers to this room as you": "Envoyer des stickers dans ce salon en tant que vous", + "Too Many Calls": "Trop d’appels", + "Permission is granted to use the webcam": "L’autorisation d’accéder à la caméra a été accordée", + "Call failed because webcam or microphone could not be accessed. Check that:": "La fonction a échoué faute de pouvoir accéder à la caméra ou au microphone. Vérifiez que :", + "Send stickers to this room as you": "Envoyer des stickers dans ce salon en tant que vous-même", "Zambia": "Zambie", "Yemen": "Yémen", "Western Sahara": "Sahara occidental", @@ -2685,7 +2685,7 @@ "North Korea": "Corée du Nord", "Norfolk Island": "Île Norfolk", "Niue": "Niue", - "Nigeria": "Nigéria", + "Nigeria": "Nigeria", "Niger": "Niger", "Nicaragua": "Nicaragua", "New Zealand": "Nouvelle-Zélande", @@ -2703,7 +2703,7 @@ "Monaco": "Monaco", "Moldova": "Moldavie", "Micronesia": "États fédérés de Micronésie", - "Mexico": "Mexico", + "Mexico": "Mexique", "Mayotte": "Mayotte", "Mauritius": "République de Maurice", "Mauritania": "Mauritanie", @@ -2783,13 +2783,13 @@ "Equatorial Guinea": "Guinée équatoriale", "El Salvador": "Le Salvador", "Egypt": "Égypte", - "Ecuador": "République de l'Équateur", + "Ecuador": "République de l’Équateur", "Dominican Republic": "République dominicaine", "Dominica": "Dominique", "Djibouti": "Djibouti", "Denmark": "Danemark", - "Côte d’Ivoire": "Côte d’Ivoire (Terre d'Éburnie)", - "Czech Republic": "La République tchèque", + "Côte d’Ivoire": "Côte d’Ivoire", + "Czech Republic": "République tchèque", "Cyprus": "Chypre", "Curaçao": "Curaçao", "Cuba": "Cuba", @@ -2799,7 +2799,7 @@ "Congo - Kinshasa": "République démocratique du Congo", "Congo - Brazzaville": "République du Congo", "Comoros": "Comores", - "Colombia": "Colombia", + "Colombia": "Colombie", "Cocos (Keeling) Islands": "îles Cocos", "Christmas Island": "île Christmas", "China": "Chine", @@ -2812,12 +2812,12 @@ "Canada": "Canada", "Cameroon": "Cameroun", "Cambodia": "Cambodge", - "Burundi": "La république du Burundi", + "Burundi": "République du Burundi", "Burkina Faso": "Burkina Faso", "Bulgaria": "Bulgarie", "Brunei": "Brunéi", "British Virgin Islands": "Îles Vierges britanniques", - "British Indian Ocean Territory": "Territoire britannique de l'océan Indien", + "British Indian Ocean Territory": "Territoire britannique de l’océan Indien", "Brazil": "Brésil", "Bouvet Island": "Île Bouvet", "Botswana": "Botswana", @@ -2825,14 +2825,14 @@ "Bolivia": "Bolivie", "Bhutan": "Bhoutan", "Bermuda": "Bermudes", - "with state key %(stateKey)s": "avec la ou les clés d'état %(stateKey)s", - "with an empty state key": "avec une clé d'état vide", - "See when anyone posts a sticker to your active room": "Voir quand n'importe qui envoye un sticker dans le salon actuel", + "with state key %(stateKey)s": "avec la ou les clés d’état %(stateKey)s", + "with an empty state key": "avec une clé d’état vide", + "See when anyone posts a sticker to your active room": "Voir quand n’importe qui envoie un sticker dans le salon actuel", "See when a sticker is posted in this room": "Voir quand un sticker est envoyé dans ce salon", - "See when the avatar changes in your active room": "Voir quand l'avatar change dans le salon actuel", - "Change the avatar of your active room": "Changer l'avatar du salon actuel", - "See when the avatar changes in this room": "Voir quand l'avatar change dans ce salon", - "Change the avatar of this room": "Changer l'avatar de ce salon", + "See when the avatar changes in your active room": "Voir quand l’avatar change dans le salon actuel", + "Change the avatar of your active room": "Changer l’avatar du salon actuel", + "See when the avatar changes in this room": "Voir quand l’avatar change dans ce salon", + "Change the avatar of this room": "Changer l’avatar de ce salon", "Send stickers into your active room": "Envoyer des stickers dans le salon actuel", "See when the topic changes in this room": "Voir quand le sujet change dans ce salon", "See when the topic changes in your active room": "Voir quand le sujet change dans le salon actuel", @@ -2842,35 +2842,35 @@ "Change the topic of your active room": "Changer le sujet dans le salon actuel", "Change the topic of this room": "Changer le sujet de ce salon", "Send stickers into this room": "Envoyer des stickers dans ce salon", - "Remain on your screen when viewing another room, when running": "Reste sur votre écran quand vous regardez un autre salon lors de l'appel", - "Takes the call in the current room off hold": "Reprends l'appel en cours dans ce salon", - "Places the call in the current room on hold": "Met l'appel en pause dans ce salon", - "Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message": "Ajoute \"(╯°□°)╯︵ ┻━┻\" en préfixe du message", - "Prepends ┬──┬ ノ( ゜-゜ノ) to a plain-text message": "Ajoute \"┬──┬ ノ( ゜-゜ノ)\" en préfixe du message", + "Remain on your screen when viewing another room, when running": "Reste sur votre écran quand vous regardez un autre salon lors de l’appel", + "Takes the call in the current room off hold": "Reprend l’appel en cours dans ce salon", + "Places the call in the current room on hold": "Met l’appel en pause dans ce salon", + "Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message": "Ajoute (╯°□°)╯︵ ┻━┻ en préfixe du message", + "Prepends ┬──┬ ノ( ゜-゜ノ) to a plain-text message": "Ajoute ┬──┬ ノ( ゜-゜ノ) en préfixe du message", "Effects": "Effets", "Zimbabwe": "Zimbabwe", "Send images as you in your active room": "Envoie des images sous votre nom dans le salon actuel", "Send images as you in this room": "Envoie des images sous votre nom dans ce salon", - "See emotes posted to your active room": "Voir les émoticônes envoyées dans le salon actuel", - "See emotes posted to this room": "Voir les émoticônes envoyées dans ce salon", - "Send emotes as you in your active room": "Envoyer des émoticônes sous votre nom dans le salon actuel", - "Send emotes as you in this room": "Envoyer des émoticônes sous votre nom dans ce salon", - "See videos posted to your active room": "Voir les vidéos publiées dans votre salon actuel", + "See emotes posted to your active room": "Voir les réactions envoyées dans le salon actuel", + "See emotes posted to this room": "Voir les réactions envoyées dans ce salon", + "Send emotes as you in your active room": "Envoyer des réactions sous votre nom dans le salon actuel", + "Send emotes as you in this room": "Envoyer des réactions sous votre nom dans ce salon", + "See videos posted to your active room": "Voir les vidéos publiées dans votre salon actif", "See videos posted to this room": "Voir les vidéos publiées dans ce salon", - "Send videos as you in your active room": "Envoie des vidéos en tant que vous dans votre salon actuel", + "Send videos as you in your active room": "Envoie des vidéos en tant que vous-même dans votre salon actuel", "Send videos as you in this room": "Envoie des vidéos en tant que vous dans ce salon", "See images posted to this room": "Voir les images publiées dans ce salon", - "See images posted to your active room": "Voir les images publiées dans votre salon actuel", - "See messages posted to your active room": "Voir les messages publiés dans votre salon actuel", + "See images posted to your active room": "Voir les images publiées dans votre salon actif", + "See messages posted to your active room": "Voir les messages publiés dans votre salon actif", "See messages posted to this room": "Voir les messages publiés dans ce salon", - "Send messages as you in your active room": "Envoie des messages en tant que vous dans votre salon actuel", - "Send messages as you in this room": "Envoie des messages en tant que vous dans ce salon", + "Send messages as you in your active room": "Envoie des messages en tant que vous-même dans votre salon actif", + "Send messages as you in this room": "Envoie des messages en tant que vous-même dans ce salon", "The %(capability)s capability": "La capacité %(capability)s", "See %(eventType)s events posted to your active room": "Voir les événements %(eventType)s publiés dans votre salon actuel", - "Send %(eventType)s events as you in your active room": "Envoie des événements %(eventType)s en tant que vous dans votre salon actuel", + "Send %(eventType)s events as you in your active room": "Envoie des événements %(eventType)s en tant que vous-même dans votre salon actuel", "See %(eventType)s events posted to this room": "Voir les événements %(eventType)s publiés dans ce salon", - "Send %(eventType)s events as you in this room": "Envoie des événements %(eventType)s en tant que vous dans ce salon", - "Send stickers to your active room as you": "Envoie des stickers en tant que vous dans le salon actuel", + "Send %(eventType)s events as you in this room": "Envoie des événements %(eventType)s en tant que vous-même dans ce salon", + "Send stickers to your active room as you": "Envoie des stickers en tant que vous-même dans le salon actuel", "Continue with %(ssoButtons)s": "Continuer avec %(ssoButtons)s", "About homeservers": "À propos des serveurs d'accueils", "Learn more": "En savoir plus", @@ -2930,12 +2930,12 @@ "Don't miss a reply": "Ne ratez pas une réponse", "See %(msgtype)s messages posted to your active room": "Voir les messages de type %(msgtype)s publiés dans le salon actuel", "See %(msgtype)s messages posted to this room": "Voir les messages de type %(msgtype)s publiés dans ce salon", - "Send %(msgtype)s messages as you in this room": "Envoie des messages de type%(msgtype)s en tant que vous dans ce salon", - "Send %(msgtype)s messages as you in your active room": "Envoie des messages de type %(msgtype)s en tant que vous dans votre salon actuel", + "Send %(msgtype)s messages as you in this room": "Envoie des messages de type%(msgtype)s en tant que vous-même dans ce salon", + "Send %(msgtype)s messages as you in your active room": "Envoie des messages de type %(msgtype)s en tant que vous-même dans votre salon actif", "See general files posted to your active room": "Voir les fichiers postés dans votre salon actuel", "See general files posted to this room": "Voir les fichiers postés dans ce salon", - "Send general files as you in your active room": "Envoie des fichiers en tant que vous dans votre salon actuel", - "Send general files as you in this room": "Envoie des fichiers en tant que vous dans ce salon", + "Send general files as you in your active room": "Envoyer des fichiers en tant que vous-même dans votre salon actif", + "Send general files as you in this room": "Envoyer des fichiers en tant que vous-même dans ce salon", "Search (must be enabled)": "Recherche (si activée)", "This session has detected that your Security Phrase and key for Secure Messages have been removed.": "Cette session a détecté que votre phrase de passe et clé de sécurité pour les messages sécurisés ont été supprimées.", "A new Security Phrase and key for Secure Messages have been detected.": "Une nouvelle phrase de passe et clé pour les messages sécurisés ont été détectées.", @@ -3035,10 +3035,10 @@ "See when the name changes in your active room": "Suivre les changements de nom dans le salon actif", "Change which room, message, or user you're viewing": "Changer le salon, message, ou la personne que vous visualisez", "Change which room you're viewing": "Changer le salon que vous visualisez", - "Remain on your screen while running": "Restez sur votre écran pendant l’exécution", + "Remain on your screen while running": "Reste sur votre écran pendant l’exécution", "%(senderName)s has updated the widget layout": "%(senderName)s a mis à jour la disposition du widget", - "Converts the DM to a room": "Transformer le message privé en salon", - "Converts the room to a DM": "Transformer le salon en message privé", + "Converts the DM to a room": "Transforme le message privé en salon", + "Converts the room to a DM": "Transforme le salon en message privé", "Your homeserver rejected your log in attempt. This could be due to things just taking too long. Please try again. If this continues, please contact your homeserver administrator.": "Votre serveur d’accueil a rejeté la demande de connexion. Ceci pourrait être dû à une connexion qui prend trop de temps. Si cela persiste, merci de contacter l’administrateur de votre serveur d’accueil.", "Your homeserver was unreachable and was not able to log you in. Please try again. If this continues, please contact your homeserver administrator.": "Votre serveur d’accueil n’est pas accessible, nous n’avons pas pu vous connecter. Merci de réessayer. Si cela persiste, merci de contacter l’administrateur de votre serveur d’accueil.", "Try again": "Réessayez", From 83d10b827b0f75decc8959adb7c833fee5406afe Mon Sep 17 00:00:00 2001 From: Jean-Luc KABORE-TURQUIN Date: Thu, 25 Feb 2021 08:15:23 +0000 Subject: [PATCH 095/420] Translated using Weblate (French) Currently translated at 99.2% (2758 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index 304599cd44..3d024f6e18 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -77,7 +77,7 @@ "Email": "E-mail", "Failed to unban": "Échec de la révocation du bannissement", "Failed to verify email address: make sure you clicked the link in the email": "La vérification de l’adresse e-mail a échoué : vérifiez que vous avez bien cliqué sur le lien dans l’e-mail", - "Failure to create room": "Échec de la création du salon", + "Failure to create room": "Échec de création du salon", "Favourites": "Favoris", "Fill screen": "Plein écran", "Filter room members": "Filtrer les membres du salon", From f75d98493d0fba3883cf355746d63e2ee6df6bba Mon Sep 17 00:00:00 2001 From: Szimszon Date: Wed, 24 Feb 2021 21:44:12 +0000 Subject: [PATCH 096/420] Translated using Weblate (Hungarian) Currently translated at 100.0% (2780 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/hu/ --- src/i18n/strings/hu.json | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json index 398dd04ec2..d0f8cc3b69 100644 --- a/src/i18n/strings/hu.json +++ b/src/i18n/strings/hu.json @@ -3060,5 +3060,27 @@ "Failed to connect to your homeserver. Please close this dialog and try again.": "A matrix szerverhez való csatlakozás nem sikerült. Zárja be ezt az ablakot és próbálja újra.", "Abort": "Megszakítás", "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Biztos benne, hogy meg kívánja szakítani a gazdagép létrehozásának a folyamatát? A folyamat nem folytatható.", - "Confirm abort of host creation": "Erősítse meg a gazdagép készítés megszakítását" + "Confirm abort of host creation": "Erősítse meg a gazdagép készítés megszakítását", + "Upgrade to %(hostSignupBrand)s": "Frissítés erre: %(hostSignupBrand)s", + "Edit Values": "Értékek szerkesztése", + "Values at explicit levels in this room:": "Egyedi szinthez tartozó értékek ebben a szobában:", + "Values at explicit levels:": "Egyedi szinthez tartozó értékek:", + "Value in this room:": "Érték ebben a szobában:", + "Value:": "Érték:", + "Save setting values": "Beállított értékek mentése", + "Values at explicit levels in this room": "Egyedi szinthez tartozó értékek ebben a szobában", + "Values at explicit levels": "Egyedi szinthez tartozó értékek", + "Settable at room": "Szobára beállítható", + "Settable at global": "Általánosan beállítható", + "Level": "Szint", + "Setting definition:": "Beállítás leírása:", + "This UI does NOT check the types of the values. Use at your own risk.": "Ez a felület nem ellenőrzi az érték típusát. Csak saját felelősségére használja.", + "Caution:": "Figyelmeztetés:", + "Setting:": "Beállítás:", + "Value in this room": "Érték ebben a szobában", + "Value": "Érték", + "Setting ID": "Beállítás azon.", + "Failed to save settings": "A beállítások elmentése nem sikerült", + "Settings Explorer": "Beállítás Böngésző", + "Show chat effects (animations when receiving e.g. confetti)": "Csevegés effektek megjelenítése (mint a konfetti animáció)" } From 32ac7d7c8b98e26872bef1b707d05187d89405f4 Mon Sep 17 00:00:00 2001 From: jelv Date: Thu, 25 Feb 2021 13:56:38 +0000 Subject: [PATCH 097/420] Translated using Weblate (Dutch) Currently translated at 100.0% (2780 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/nl/ --- src/i18n/strings/nl.json | 44 ++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json index 101d997d9c..0d80e520c8 100644 --- a/src/i18n/strings/nl.json +++ b/src/i18n/strings/nl.json @@ -689,7 +689,7 @@ "Messages in one-to-one chats": "Berichten in één-op-één gesprekken", "Unavailable": "Niet beschikbaar", "View Decrypted Source": "Ontsleutelde bron bekijken", - "Failed to update keywords": "Bijwerken van trefwoorden is mislukt", + "Failed to update keywords": "Updaten van trefwoorden is mislukt", "remove %(name)s from the directory.": "verwijder %(name)s uit de catalogus.", "Notifications on the following keywords follow rules which can’t be displayed here:": "Meldingen op de volgende trefwoorden volgen regels die hier niet getoond kunnen worden:", "Please set a password!": "Stel een wachtwoord in!", @@ -740,7 +740,7 @@ "What's new?": "Wat is er nieuw?", "Notify me for anything else": "Stuur een melding voor al het andere", "When I'm invited to a room": "Wanneer ik uitgenodigd word in een gesprek", - "Can't update user notification settings": "Kan de meldingsinstellingen van de gebruiker niet bijwerken", + "Can't update user notification settings": "Kan de meldingsinstellingen van de gebruiker niet updaten", "Notify for all other messages/rooms": "Stuur een melding voor alle andere berichten/gesprekken", "Unable to look up room ID from server": "Kon de gesprek-ID niet van de server ophalen", "Couldn't find a matching Matrix room": "Kon geen bijbehorend Matrix-gesprek vinden", @@ -852,7 +852,7 @@ "Gets or sets the room topic": "Verkrijgt het onderwerp van het gesprek of stelt het in", "This room has no topic.": "Dit gesprek heeft geen onderwerp.", "Sets the room name": "Stelt de gespreksnaam in", - "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s heeft dit gesprek bijgewerkt.", + "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s heeft dit gesprek geüpgraded.", "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s heeft het gesprek toegankelijk gemaakt voor iedereen die de koppeling kent.", "%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s heeft het gesprek enkel op uitnodiging toegankelijk gemaakt.", "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s heeft de toegangsregel veranderd naar ‘%(rule)s’", @@ -1240,7 +1240,7 @@ "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s heeft de uitnodiging aan %(targetDisplayName)s toe te treden tot het gesprek ingetrokken.", "Upgrade this room to the recommended room version": "Werk dit gesprek bij tot de aanbevolen versie", "This room is running room version , which this homeserver has marked as unstable.": "Dit gesprek draait op groepsgespreksversie , die door deze homeserver als onstabiel is gemarkeerd.", - "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Bijwerken zal de huidige versie van dit gesprek sluiten, en onder dezelfde naam een bijgewerkte versie starten.", + "Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "Upgraden zal de huidige versie van dit gesprek sluiten, en onder dezelfde naam een geüpgraded versie starten.", "Failed to revoke invite": "Intrekken van uitnodiging is mislukt", "Could not revoke the invite. The server may be experiencing a temporary problem or you do not have sufficient permissions to revoke the invite.": "Kon de uitnodiging niet intrekken. De server ondervindt mogelijk een tijdelijk probleem, of u heeft niet het recht de uitnodiging in te trekken.", "Revoke invite": "Uitnodiging intrekken", @@ -1289,7 +1289,7 @@ "Unexpected error resolving homeserver configuration": "Onverwachte fout bij het controleren van de homeserverconfiguratie", "The user's homeserver does not support the version of the room.": "De homeserver van de gebruiker biedt geen ondersteuning voor de gespreksversie.", "Show hidden events in timeline": "Verborgen gebeurtenissen op de tijdslijn weergeven", - "When rooms are upgraded": "Wanneer gesprekken bijgewerkt worden", + "When rooms are upgraded": "Wanneer gesprekken geüpgraded worden", "this room": "dit gesprek", "View older messages in %(roomName)s.": "Bekijk oudere berichten in %(roomName)s.", "Joining room …": "Deelnemen aan gesprek…", @@ -1316,7 +1316,7 @@ "This room doesn't exist. Are you sure you're at the right place?": "Dit gesprek bestaat niet. Weet u zeker dat u zich op de juiste plaats bevindt?", "Try again later, or ask a room admin to check if you have access.": "Probeer het later opnieuw, of vraag een gespreksbeheerder om te controleren of u wel toegang heeft.", "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please submit a bug report.": "De foutcode %(errcode)s is weergegeven bij het toetreden van het gesprek. Als u meent dat u dit bericht foutief te zien krijgt, gelieve dan een foutmelding in te dienen.", - "This room has already been upgraded.": "Dit gesprek is reeds bijgewerkt.", + "This room has already been upgraded.": "Dit gesprek is reeds geüpgraded.", "reacted with %(shortName)s": "heeft gereageerd met %(shortName)s", "edited": "bewerkt", "Rotate Left": "Links draaien", @@ -1557,7 +1557,7 @@ "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Dit vergt validatie van een e-mailadres of telefoonnummer middels de standaardidentiteitsserver , maar die server heeft geen gebruiksvoorwaarden.", "Trust": "Vertrouwen", "Custom (%(level)s)": "Aangepast (%(level)s)", - "Error upgrading room": "Bijwerken van gesprek mislukt", + "Error upgrading room": "Upgraden van gesprek mislukt", "Double check that your server supports the room version chosen and try again.": "Ga nogmaals na dat de server de gekozen gespreksversie ondersteunt, en probeer het dan opnieuw.", "Verifies a user, session, and pubkey tuple": "Verifieert een combinatie van gebruiker+sessie+publieke sleutel", "Unknown (user, session) pair:": "Onbekende combinatie gebruiker+sessie:", @@ -1618,7 +1618,7 @@ "Lock": "Hangslot", "Verify yourself & others to keep your chats safe": "Verifieer uzelf en anderen om uw gesprekken veilig te houden", "Other users may not trust it": "Mogelijk wantrouwen anderen het", - "Upgrade": "Bijwerken", + "Upgrade": "Upgraden", "Verify": "Verifiëren", "Later": "Later", "Review": "Controle", @@ -2377,7 +2377,7 @@ "Use between %(min)s pt and %(max)s pt": "Gebruik een getal tussen %(min)s pt en %(max)s pt", "Custom font size can only be between %(min)s pt and %(max)s pt": "Aangepaste lettergrootte kan alleen een getal tussen %(min)s pt en %(max)s pt zijn", "Size must be a number": "Grootte moet een getal zijn", - "New version available. Update now.": "Nieuwe versie beschikbaar. Nu bijwerken.", + "New version available. Update now.": "Nieuwe versie beschikbaar. Nu updaten.", "not ready": "Niet gereed", "ready": "Gereed", "unexpected type": "Onverwacht type", @@ -2445,7 +2445,7 @@ "The person who invited you already left the room, or their server is offline.": "De persoon door wie u ben uitgenodigd heeft het gesprek al verlaten, of hun server is offline.", "The person who invited you already left the room.": "De persoon door wie u ben uitgenodigd, heeft het gesprek reeds verlaten.", "New version of %(brand)s is available": "Nieuwe versie van %(brand)s is beschikbaar", - "Update %(brand)s": "%(brand)s bijwerken", + "Update %(brand)s": "%(brand)s updaten", "%(senderName)s has updated the widget layout": "%(senderName)s heeft de widget-indeling bijgewerkt", "%(senderName)s declined the call.": "%(senderName)s heeft de oproep afgewezen.", "(an error occurred)": "(een fout is opgetreden)", @@ -2950,5 +2950,27 @@ "Create a Group Chat": "Maak een groepsgesprek aan", "Send a Direct Message": "Start een direct gesprek", "Welcome to %(appName)s": "Welkom bij %(appName)s", - "Add a topic to help people know what it is about.": "Stel een gespreksonderwerp in zodat mensen weten waar het over gaat." + "Add a topic to help people know what it is about.": "Stel een gespreksonderwerp in zodat mensen weten waar het over gaat.", + "Upgrade to %(hostSignupBrand)s": "Upgrade naar %(hostSignupBrand)s", + "Edit Values": "Waarde wijzigen", + "Values at explicit levels in this room:": "Waarde op expliciete niveaus in dit gesprek:", + "Values at explicit levels:": "Waardes op expliciete niveaus:", + "Value in this room:": "Waarde in dit gesprek:", + "Value:": "Waarde:", + "Save setting values": "Instelling waardes opslaan", + "Values at explicit levels in this room": "Waardes op expliciete niveaus in dit gesprek", + "Values at explicit levels": "Waardes op expliciete niveaus", + "Settable at room": "Instelbaar op gesprek", + "Settable at global": "Instelbaar op globaal", + "Level": "Niveau", + "Setting definition:": "Instelling definitie:", + "This UI does NOT check the types of the values. Use at your own risk.": "De UI heeft GEEN controle op het type van de waardes. Gebruik op eigen risico.", + "Caution:": "Opgelet:", + "Setting:": "Instelling:", + "Value in this room": "Waarde van dit gesprek", + "Value": "Waarde", + "Setting ID": "Instellingen-ID", + "Failed to save settings": "Kan geen instellingen opslaan", + "Settings Explorer": "Instellingen Ontdekken", + "Show chat effects (animations when receiving e.g. confetti)": "Effecten tonen (animaties bij ontvangst bijv. confetti)" } From abbe51660438ad2baa92d88097a2c1f926ab71b9 Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Fri, 26 Feb 2021 17:32:14 +0000 Subject: [PATCH 098/420] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2780 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/pt_BR/ --- src/i18n/strings/pt_BR.json | 38 ++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/pt_BR.json b/src/i18n/strings/pt_BR.json index b32fbf5094..ef748c5fd8 100644 --- a/src/i18n/strings/pt_BR.json +++ b/src/i18n/strings/pt_BR.json @@ -2983,5 +2983,41 @@ "Converts the DM to a room": "Converte a conversa para uma sala", "Converts the room to a DM": "Converte a sala para uma conversa", "Try again": "Tente novamente", - "We couldn't log you in": "Não foi possível fazer login" + "We couldn't log you in": "Não foi possível fazer login", + "%(hostSignupBrand)s Setup": "Configuração do %(hostSignupBrand)s", + "Continuing temporarily allows the %(hostSignupBrand)s setup process to access your account to fetch verified email addresses. This data is not stored.": "Ao continuar, você permite que o processo de configuração do %(hostSignupBrand)s acesse a sua conta para obter endereços de e-mail verificados, temporariamente. Esses dados não são armazenados.", + "Settable at room": "Definido em cada sala", + "Settable at global": "Definido globalmente", + "Settings Explorer": "Explorador de configurações", + "Level": "Nível", + "Setting definition:": "Definição da configuração:", + "Setting ID": "ID da configuração", + "Your homeserver rejected your log in attempt. This could be due to things just taking too long. Please try again. If this continues, please contact your homeserver administrator.": "Seu servidor local recusou a sua tentativa de login. Isso pode ocorrer quando a conexão de internet estiver demorando muito. Por favor, tente novamente. Se o problema continuar, entre em contato com o administrador do seu servidor local.", + "Your homeserver was unreachable and was not able to log you in. Please try again. If this continues, please contact your homeserver administrator.": "O seu servidor local está inacessível e não foi possível fazer o seu login. Tente novamente. Se o problema continuar, entre em contato com o administrador do seu servidor local.", + "We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "Anteriormente, pedimos ao seu navegador para lembrar qual servidor local você usa para fazer login, mas infelizmente o navegador se esqueceu disso. Vá para a página de login e tente novamente.", + "Upgrade to %(hostSignupBrand)s": "Atualizar para %(hostSignupBrand)s", + "Minimize dialog": "Minimizar a janela", + "Maximize dialog": "Maximizar a janela", + "You should know": "Você deveria saber", + "Cookie Policy": "Política de cookies", + "Failed to connect to your homeserver. Please close this dialog and try again.": "Falha ao se conectar ao seu servidor local. Feche esta caixa de diálogo e tente novamente.", + "Abort": "Cancelar", + "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Tem certeza de que deseja cancelar a criação do host? O processo não pode ser continuado.", + "Confirm abort of host creation": "Confirmar o cancelamento da criação do host", + "Edit Values": "Editar valores", + "Values at explicit levels in this room:": "Valores em níveis explícitos nessa sala:", + "Values at explicit levels:": "Valores em níveis explícitos:", + "Value in this room:": "Valor nessa sala:", + "Value:": "Valor:", + "Save setting values": "Salvar valores de configuração", + "Values at explicit levels in this room": "Valores em níveis explícitos nessa sala", + "Values at explicit levels": "Valores em níveis explícitos", + "This UI does NOT check the types of the values. Use at your own risk.": "Esta interface de usuário NÃO verifica os tipos de valores. Use por sua conta e risco.", + "Caution:": "Atenção:", + "Setting:": "Configuração:", + "Value in this room": "Valor nessa sala", + "Value": "Valor", + "Failed to save settings": "Falha ao salvar as configurações", + "Show chat effects (animations when receiving e.g. confetti)": "Mostrar efeitos na conversa (por exemplo: animações ao receber confetes)", + "Element Web is experimental on mobile. For a better experience and the latest features, use our free native app.": "O Element Web é experimental para dispositivos móveis. Para uma melhor experiência e os recursos mais recentes, use nosso aplicativo gratuito." } From 3540bec046cc648f97699967439d81b1c5ed2027 Mon Sep 17 00:00:00 2001 From: rkfg Date: Sat, 27 Feb 2021 11:02:52 +0000 Subject: [PATCH 099/420] Translated using Weblate (Russian) Currently translated at 99.6% (2769 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 65aeaf82d9..8310676e69 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -1591,7 +1591,7 @@ "Error upgrading room": "Ошибка обновления комнаты", "Match system theme": "Тема системы", "Show tray icon and minimize window to it on close": "Показать иконку в трее и сворачивать окно при закрытии", - "Show typing notifications": "Показывать уведомления о наборе", + "Show typing notifications": "Показывать уведомления о наборе текста", "Delete %(count)s sessions|other": "Удалить %(count)s сессий", "Enable desktop notifications for this session": "Включить уведомления для рабочего стола для этой сессии", "Enable audible notifications for this session": "Включить звуковые уведомления для этой сессии", @@ -3031,7 +3031,7 @@ "Privacy Policy": "Политика конфиденциальности", "Cookie Policy": "Политика использования файлов cookie", "Learn more in our , and .": "Дополнительную информацию можно найти на страницах , и .", - "Continuing temporarily allows the %(hostSignupBrand)s setup process to access your account to fetch verified email addresses. This data is not stored.": "Продолжая процесс настройки %(hostSignupBrand)s, вы предоставите доступ к вашей учётной записи для получения проверенных адресов электронной почты. Эти данные не сохраняются.", + "Continuing temporarily allows the %(hostSignupBrand)s setup process to access your account to fetch verified email addresses. This data is not stored.": "Продолжая процесс настройки %(hostSignupBrand)s, вы предоставите временный доступ к вашей учётной записи для получения проверенных адресов электронной почты. Эти данные не сохраняются.", "Failed to connect to your homeserver. Please close this dialog and try again.": "Не удалось подключиться к домашнему серверу. Закройте это диалоговое окно и попробуйте ещё раз.", "Abort": "Отмена", "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Вы уверены, что хотите прервать создание хоста? Процесс не может быть продолжен.", @@ -3072,5 +3072,7 @@ "Value in this room": "Значение в этой комнате", "Value": "Значение", "Failed to save settings": "Не удалось сохранить настройки", - "Show chat effects (animations when receiving e.g. confetti)": "Показать эффекты чата (анимация при получении, например, конфетти)" + "Show chat effects (animations when receiving e.g. confetti)": "Показать эффекты чата (анимация при получении, например, конфетти)", + "Caution:": "Предупреждение:", + "Settings Explorer": "Обзор настроек" } From 8d68cbdb142ab0f9f513d1b3583490824cbb49be Mon Sep 17 00:00:00 2001 From: A-l-exa-n-d-r Date: Sat, 27 Feb 2021 08:05:08 +0000 Subject: [PATCH 100/420] Translated using Weblate (Russian) Currently translated at 99.6% (2769 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/ru/ --- src/i18n/strings/ru.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json index 8310676e69..7cbcb08167 100644 --- a/src/i18n/strings/ru.json +++ b/src/i18n/strings/ru.json @@ -3068,7 +3068,7 @@ "Settable at room": "Устанавливается для комнаты", "Settable at global": "Устанавливается на глобальном уровне", "Level": "Уровень", - "This UI does NOT check the types of the values. Use at your own risk.": "Этот пользовательский интерфейс НЕ проверяет типы значений. Используйте на свой риск.", + "This UI does NOT check the types of the values. Use at your own risk.": "Этот пользовательский интерфейс НЕ проверяет типы значений. Используйте на свой страх и риск.", "Value in this room": "Значение в этой комнате", "Value": "Значение", "Failed to save settings": "Не удалось сохранить настройки", From b6d4e3fe9a247835d87337282ebc2eba7af26a20 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Fri, 26 Feb 2021 07:13:31 +0000 Subject: [PATCH 101/420] Translated using Weblate (Swedish) Currently translated at 100.0% (2780 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sv/ --- src/i18n/strings/sv.json | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json index 916de9ecce..c8a5a41339 100644 --- a/src/i18n/strings/sv.json +++ b/src/i18n/strings/sv.json @@ -2996,5 +2996,27 @@ "Failed to connect to your homeserver. Please close this dialog and try again.": "Misslyckades att ansluta till din hemserver. Vänligen stäng den här dialogrutan och försök igen.", "Abort": "Avbryt", "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Är du säker på att du vill avbryta skapande av värden? Processen kan inte fortsättas.", - "Confirm abort of host creation": "Bekräfta avbrytning av värdskapande" + "Confirm abort of host creation": "Bekräfta avbrytning av värdskapande", + "Upgrade to %(hostSignupBrand)s": "Uppgradera till %(hostSignupBrand)s", + "Edit Values": "Redigera värden", + "Values at explicit levels in this room:": "Värden vid explicita nivåer i det här rummet:", + "Values at explicit levels:": "Värden vid explicita nivåer:", + "Value in this room:": "Värde i det här rummet:", + "Value:": "Värde:", + "Save setting values": "Spara inställningsvärden", + "Values at explicit levels in this room": "Värden vid explicita nivåer i det här rummet", + "Values at explicit levels": "Värden vid explicita nivåer", + "Settable at room": "Inställningsbar per rum", + "Settable at global": "Inställningsbar globalt", + "Level": "Nivå", + "Setting definition:": "Inställningsdefinition:", + "This UI does NOT check the types of the values. Use at your own risk.": "Det här UI:t kontrollerar inte typer för värden. Använd på egen risk.", + "Caution:": "Varning:", + "Setting:": "Inställning:", + "Value in this room": "Värde i det här rummet", + "Value": "Värde", + "Setting ID": "Inställnings-ID", + "Failed to save settings": "Misslyckades att spara inställningar", + "Settings Explorer": "Inställningsutforskare", + "Show chat effects (animations when receiving e.g. confetti)": "Visa chatteffekter (animeringar när du tar emot t.ex. konfetti)" } From ad38f87287b13eb2ef835781287c16f26169a204 Mon Sep 17 00:00:00 2001 From: linsui Date: Fri, 26 Feb 2021 14:45:43 +0000 Subject: [PATCH 102/420] Translated using Weblate (Chinese (Simplified)) Currently translated at 80.7% (2245 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/zh_Hans/ --- src/i18n/strings/zh_Hans.json | 75 ++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json index 167fea70e1..12fb5e2877 100644 --- a/src/i18n/strings/zh_Hans.json +++ b/src/i18n/strings/zh_Hans.json @@ -2409,5 +2409,78 @@ "The call was answered on another device.": "在另一台设备上应答了该通话。", "The call could not be established": "无法建立通话", "The other party declined the call.": "对方拒绝了通话。", - "Call Declined": "通话被拒绝" + "Call Declined": "通话被拒绝", + "(connection failed)": "(连接失败)", + "🎉 All servers are banned from participating! This room can no longer be used.": "🎉 所有服务器都已禁止参与!此聊天室不再可用。", + "%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s 为此聊天室更改了服务器 ACL。", + "%(senderDisplayName)s set the server ACLs for this room.": "%(senderDisplayName)s 为此聊天室设置了服务器 ACL。", + "Hong Kong": "香港", + "Cook Islands": "库克群岛", + "Congo - Kinshasa": "刚果金", + "Congo - Brazzaville": "刚果布拉柴维尔", + "Comoros": "科摩罗", + "Colombia": "哥伦比亚", + "Cocos (Keeling) Islands": "科科斯基林群岛", + "Christmas Island": "圣诞岛", + "China": "中国", + "Chile": "智利", + "Chad": "乍得", + "Central African Republic": "中非共和国", + "Cayman Islands": "开曼群岛(英)", + "Caribbean Netherlands": "荷兰加勒比区", + "Cape Verde": "佛得角", + "Canada": "加拿大", + "Cameroon": "喀麦隆", + "Cambodia": "柬埔寨", + "Burundi": "布隆迪", + "Burkina Faso": "布基纳法索", + "Bulgaria": "保加利亚", + "Brunei": "文莱", + "British Virgin Islands": "英属维尔京群岛", + "British Indian Ocean Territory": "英属印度洋领地", + "Brazil": "巴西", + "Bouvet Island": "布维岛", + "Botswana": "博茨瓦纳", + "Bosnia": "波斯尼亚", + "Bolivia": "玻利维亚", + "Armenia": "亚美尼亚", + "Bhutan": "不丹", + "Bermuda": "百慕大群岛", + "Benin": "贝宁", + "Belize": "伯利兹", + "Belgium": "比利时", + "Belarus": "白俄罗斯", + "Barbados": "巴巴多斯", + "Bangladesh": "孟加拉国", + "Bahrain": "巴林", + "Bahamas": "巴哈马", + "Azerbaijan": "阿塞拜疆", + "Austria": "奥地利", + "Australia": "澳大利亚", + "Aruba": "阿鲁巴岛", + "Argentina": "阿根廷", + "Antigua & Barbuda": "安提瓜和巴布达", + "Antarctica": "南极洲", + "Anguilla": "安圭拉", + "Angola": "安哥拉", + "Andorra": "安道尔", + "American Samoa": "美属萨摩亚", + "Algeria": "阿尔及利亚", + "Albania": "阿尔巴尼亚", + "Åland Islands": "奥兰群岛", + "Afghanistan": "阿富汗", + "United States": "美国", + "United Kingdom": "英国", + "Your homeserver rejected your log in attempt. This could be due to things just taking too long. Please try again. If this continues, please contact your homeserver administrator.": "您的主服务器已拒绝您的登入尝试。请重试。如果此情况持续发生,请联系您的主服务器管理员。", + "Your homeserver was unreachable and was not able to log you in. Please try again. If this continues, please contact your homeserver administrator.": "您的主服务器不可达,无法使您登入。请重试。如果此情况持续发生,请联系您的主服务器管理员。", + "Try again": "重试", + "We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "我们已要求浏览器记住您使用的主服务器,但不幸的是您的浏览器已忘记。请前往登录页面重试。", + "We couldn't log you in": "我们无法使您登入", + "This will end the conference for everyone. Continue?": "这将结束所有人的会议。是否继续?", + "End conference": "结束会议", + "You've reached the maximum number of simultaneous calls.": "您已达到并行呼叫最大数量。", + "Too Many Calls": "太多呼叫", + "Call failed because webcam or microphone could not be accessed. Check that:": "通话失败,因为无法访问网络摄像头或麦克风。请检查:", + "Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.": "呼叫失败,因为无法访问任何麦克风。 检查是否已插入麦克风并正确设置。", + "Answered Elsewhere": "在其他地方已回答" } From c4940d164381449f857a5c88cc9abbc62939a6aa Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 25 Feb 2021 01:41:18 +0000 Subject: [PATCH 103/420] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2780 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/zh_Hant/ --- src/i18n/strings/zh_Hant.json | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json index b11ff2274f..64dab74139 100644 --- a/src/i18n/strings/zh_Hant.json +++ b/src/i18n/strings/zh_Hant.json @@ -3068,5 +3068,27 @@ "Failed to connect to your homeserver. Please close this dialog and try again.": "無法連線到您的家伺服器。請關閉對話框並再試一次。", "Abort": "中止", "Are you sure you wish to abort creation of the host? The process cannot be continued.": "您確定您想要中止主機建立嗎?流程將無法繼續。", - "Confirm abort of host creation": "確認中止主機建立" + "Confirm abort of host creation": "確認中止主機建立", + "Upgrade to %(hostSignupBrand)s": "升級至 %(hostSignupBrand)s", + "Edit Values": "編輯值", + "Values at explicit levels in this room:": "此聊天室中明確等級的值:", + "Values at explicit levels:": "明確等級的值:", + "Value in this room:": "此聊天室中的值:", + "Value:": "值:", + "Save setting values": "儲存設定值", + "Values at explicit levels in this room": "此聊天室中明確等級的值", + "Values at explicit levels": "明確等級的值", + "Settable at room": "聊天室設定", + "Settable at global": "全域設定", + "Level": "等級", + "Setting definition:": "設定定義:", + "This UI does NOT check the types of the values. Use at your own risk.": "此使用者介面不會檢查值的類型。使用風險自負。", + "Caution:": "警告:", + "Setting:": "設定:", + "Value in this room": "此聊天室中的值", + "Value": "值", + "Setting ID": "設定 ID", + "Failed to save settings": "儲存設定失敗", + "Settings Explorer": "設定瀏覽程式", + "Show chat effects (animations when receiving e.g. confetti)": "顯示聊天效果(當收到如五彩紙屑時顯示動畫)" } From 66cf2075fe9457f5190eb43723efc6cefd327285 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Fri, 26 Feb 2021 08:30:59 +0000 Subject: [PATCH 104/420] Translated using Weblate (Czech) Currently translated at 100.0% (2780 of 2780 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 | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json index 3ee9c73399..47a99ab670 100644 --- a/src/i18n/strings/cs.json +++ b/src/i18n/strings/cs.json @@ -231,7 +231,7 @@ "You do not have permission to do that in this room.": "V této místnosti nemáte na toto právo.", "You cannot place a call with yourself.": "Nemůžete volat sami sobě.", "You cannot place VoIP calls in this browser.": "V tomto prohlížeči nelze vytáčet VoIP hovory.", - "You do not have permission to post to this room": "Na přispívání do této místnosti nemáte právo", + "You do not have permission to post to this room": "Nemáte oprávnění zveřejňovat příspěvky v této místnosti", "Online": "Online", "Offline": "Offline", "Check for update": "Zkontrolovat aktualizace", @@ -1409,7 +1409,7 @@ "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.": "Odeslat pozvánku pomocí serveru identit. Použít výchozí (%(defaultIdentityServerName)s) nebo přenastavit Nastavení.", "Use an identity server to invite by email. Manage in Settings.": "Odeslat pozvánku pomocí serveru identit. Přenastavit v Nastavení.", "Close dialog": "Zavřít dialog", - "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Napište nám prosím co se pokazilo a nebo nám napište issue na GitHub, kde popíšete problém.", + "Please tell us what went wrong or, better, create a GitHub issue that describes the problem.": "Dejte nám vědět, prosím, co se pokazilo nebo vytvořte issue na GitHubu, kde problém popište.", "Removing…": "Odstaňování…", "Clear all data": "Smazat všechna data", "Please enter a name for the room": "Zadejte prosím název místnosti", @@ -1529,7 +1529,7 @@ "Flags": "Vlajky", "Quick Reactions": "Rychlé reakce", "Cancel search": "Zrušit hledání", - "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.", + "Please create a new issue on GitHub so that we can investigate this bug.": "Vytvořte prosím novou 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", @@ -2981,5 +2981,27 @@ "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" + "Confirm abort of host creation": "Potvrďte přerušení vytváření hostitele", + "Upgrade to %(hostSignupBrand)s": "Aktualizovat na %(hostSignupBrand)s", + "Edit Values": "Upravit hodnoty", + "Values at explicit levels in this room:": "Hodnoty na explicitních úrovních v této místnosti:", + "Values at explicit levels:": "Hodnoty na explicitních úrovních:", + "Value in this room:": "Hodnota v této místnosti:", + "Value:": "Hodnota:", + "Save setting values": "Uložit hodnoty nastavení", + "Values at explicit levels in this room": "Hodnoty na explicitních úrovních v této místnosti", + "Values at explicit levels": "Hodnoty na explicitních úrovních", + "Settable at room": "Nastavitelné v místnosti", + "Settable at global": "Nastavitelné na globální úrovni", + "Level": "Úroveň", + "Setting definition:": "Definice nastavení:", + "This UI does NOT check the types of the values. Use at your own risk.": "Toto uživatelské rozhraní NEKONTROLUJE typy hodnot. Použití na vlastní nebezpečí.", + "Caution:": "Pozor:", + "Setting:": "Nastavení:", + "Value in this room": "Hodnota v této místnosti", + "Value": "Hodnota", + "Setting ID": "ID nastavení", + "Failed to save settings": "Nastavení se nepodařilo uložit", + "Settings Explorer": "Průzkumník nastavení", + "Show chat effects (animations when receiving e.g. confetti)": "Zobrazit efekty chatu (animace např. při přijetí konfet)" } From b6a5b08a86cb466d7e11982c1f0b5a91c53bb5ae Mon Sep 17 00:00:00 2001 From: XoseM Date: Thu, 25 Feb 2021 06:09:06 +0000 Subject: [PATCH 105/420] Translated using Weblate (Galician) Currently translated at 100.0% (2780 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/gl/ --- src/i18n/strings/gl.json | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/gl.json b/src/i18n/strings/gl.json index b309f12245..98a984d860 100644 --- a/src/i18n/strings/gl.json +++ b/src/i18n/strings/gl.json @@ -3065,5 +3065,27 @@ "Failed to connect to your homeserver. Please close this dialog and try again.": "Fallou a conexión co teu servidor de inicio. Pecha esta información e inténtao outra vez.", "Abort": "Abortar", "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Tes a certeza de querer cancelar a creación do servidor? O proceso non pode ser completado.", - "Confirm abort of host creation": "Corfirma que cancelas a creación do servidor" + "Confirm abort of host creation": "Corfirma que cancelas a creación do servidor", + "Upgrade to %(hostSignupBrand)s": "Actualizar a %(hostSignupBrand)s", + "Edit Values": "Editar valores", + "Values at explicit levels in this room:": "Valores a niveis explícitos nesta sala:", + "Values at explicit levels:": "Valores a niveis explícitos:", + "Value in this room:": "Valor nesta sala:", + "Value:": "Valor:", + "Save setting values": "Gardar valores configurados", + "Values at explicit levels in this room": "Valores a niveis explícitos nesta sala", + "Values at explicit levels": "Valores e niveis explícitos", + "Settable at room": "Configurable na sala", + "Settable at global": "Configurable como global", + "Level": "Nivel", + "Setting definition:": "Definición do axuste:", + "This UI does NOT check the types of the values. Use at your own risk.": "Esta IU non comproba os tipos dos valores. Usa baixo a túa responsabilidade.", + "Caution:": "Aviso:", + "Setting:": "Axuste:", + "Value in this room": "Valor nesta sala", + "Value": "Valor", + "Setting ID": "ID do axuste", + "Failed to save settings": "Non se gardaron os axustes", + "Settings Explorer": "Navegar nos axustes", + "Show chat effects (animations when receiving e.g. confetti)": "Mostrar efectos no chat (animacións na recepción, ex. confetti)" } From 73421597b63b7f3f266f7fcf94bb4030c70b9a12 Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Fri, 26 Feb 2021 17:16:44 +0000 Subject: [PATCH 106/420] Translated using Weblate (Albanian) Currently translated at 99.6% (2769 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/sq/ --- src/i18n/strings/sq.json | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json index e8e47ac81b..dd58154b5a 100644 --- a/src/i18n/strings/sq.json +++ b/src/i18n/strings/sq.json @@ -2990,7 +2990,7 @@ "If you've forgotten your Security Key you can ": "Nëse keni harruar Kyçin tuaj të Sigurisë, mund të .", "Your Security Key has been copied to your clipboard, paste it to:": "Kyçi juaj i Sigurisë është kopjuar te e papastra juaj, ngjiteni te:", "Confirm your Security Phrase": "Ripohoni Frazën tuaj të Sigurisë", - "Secure your backup with a Security Phrase": "Sigurojeni kopjeruajtjen tuaj me një Frazë Sigurie.", + "Secure your backup with a Security Phrase": "Sigurojeni kopjeruajtjen tuaj me një Frazë Sigurie", "Repeat your Security Phrase...": "Përsëritni Frazën tuaj të Sigurisë…", "Please enter your Security Phrase a second time to confirm.": "Ju lutemi, që të ripohohet, rijepeni Frazën tuaj të Sigurisë.", "This session has detected that your Security Phrase and key for Secure Messages have been removed.": "Ky sesion ka pikasur se Fraza e Sigurisë dhe kyçi juaj për Mesazhe të Sigurt janë hequr.", @@ -3056,5 +3056,25 @@ "Failed to connect to your homeserver. Please close this dialog and try again.": "S’u arrit të lidhej me shërbyesin tuaj Home. Ju lutemi, mbylleni këtë dialog dhe riprovoni.", "Abort": "Ndërprite", "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Jeni i sigurt se doni të ndërpritet krijimi i strehës? Procesi s’mund të vazhdohet.", - "Confirm abort of host creation": "Ripohoni ndërprerjen e krijimit të strehës" + "Confirm abort of host creation": "Ripohoni ndërprerjen e krijimit të strehës", + "Upgrade to %(hostSignupBrand)s": "Përmirësojeni me %(hostSignupBrand)s", + "Edit Values": "Përpunoni Vlera", + "Values at explicit levels in this room:": "Vlera në nivele shprehimisht në këtë dhomë:", + "Values at explicit levels:": "Vlera në nivele shprehimisht:", + "Value in this room:": "Vlerë në këtë dhomë:", + "Value:": "Vlerë:", + "Save setting values": "Ruaj vlera rregullimi", + "Values at explicit levels in this room": "Vlera në nivele shprehimisht në këtë dhomë", + "Values at explicit levels": "Vlera në nivele shprehimisht", + "Settable at room": "I caktueshëm për dhomën", + "Settable at global": "I caktueshëm te të përgjithshmet", + "Level": "Nivel", + "Setting definition:": "Përkufizim rregullimi:", + "This UI does NOT check the types of the values. Use at your own risk.": "Kjo UI NUK kontrollon llojet e vlerave. Përdorini me përgjegjësinë tuaj.", + "Setting:": "Rregullim:", + "Value in this room": "Vlerë në këtë dhomë", + "Value": "Vlerë", + "Failed to save settings": "S’u arrit të ruhen rregullimet", + "Settings Explorer": "Eksplorues Rregullimesh", + "Show chat effects (animations when receiving e.g. confetti)": "Shfaq efekte fjalosjeje (animacione kur merren bonbone, për shembull)" } From 55bfe21df0ed8827b7dca40cd580ac91cf202f82 Mon Sep 17 00:00:00 2001 From: Trendyne Date: Thu, 25 Feb 2021 14:25:53 +0000 Subject: [PATCH 107/420] Translated using Weblate (Icelandic) Currently translated at 14.8% (413 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/is/ --- src/i18n/strings/is.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/is.json b/src/i18n/strings/is.json index 6f561331f6..ddb3bbc66d 100644 --- a/src/i18n/strings/is.json +++ b/src/i18n/strings/is.json @@ -458,5 +458,7 @@ "Passphrases must match": "Lykilfrasar verða að stemma", "Passphrase must not be empty": "Lykilfrasi má ekki vera auður", "Create Account": "Stofna Reikning", - "Please install Chrome, Firefox, or Safari for the best experience.": "vinsamlegast setja upp Chrome, Firefox, eða Safari fyrir besta reynsluna." + "Please install Chrome, Firefox, or Safari for the best experience.": "vinsamlegast setja upp Chrome, Firefox, eða Safari fyrir besta reynsluna.", + "Explore rooms": "Kanna herbergi", + "Sign In": "Skrá inn" } From 36ef0be05b028a4e97afac0499491a20f9ae7ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Thu, 25 Feb 2021 16:43:43 +0000 Subject: [PATCH 108/420] Translated using Weblate (Estonian) Currently translated at 100.0% (2780 of 2780 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/et/ --- src/i18n/strings/et.json | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json index 7769cf61c8..eb895f5f26 100644 --- a/src/i18n/strings/et.json +++ b/src/i18n/strings/et.json @@ -3066,5 +3066,27 @@ "Privacy Policy": "Privaatsuspoliitika", "Cookie Policy": "Küpsiste kasutamine", "Learn more in our , and .": "Lisateavet leiad , ja lehtedelt.", - "Failed to connect to your homeserver. Please close this dialog and try again.": "Ei õnnestunud ühendada koduserveriga. Palun sulge see aken ja proovi uuesti." + "Failed to connect to your homeserver. Please close this dialog and try again.": "Ei õnnestunud ühendada koduserveriga. Palun sulge see aken ja proovi uuesti.", + "Upgrade to %(hostSignupBrand)s": "Kui soovid, siis võta kasutusele %(hostSignupBrand)s", + "Edit Values": "Muuda väärtusi", + "Values at explicit levels in this room:": "Väärtused konkreetsel tasemel selles jututoas:", + "Values at explicit levels:": "Väärtused konkreetsel tasemel:", + "Value in this room:": "Väärtus selles jututoas:", + "Value:": "Väärtus:", + "Save setting values": "Salvesta seadistuste väärtused", + "Values at explicit levels in this room": "Väärtused konkreetsel tasemel selles jututoas", + "Values at explicit levels": "Väärtused konkreetsel tasemel", + "Settable at room": "Seadistatav jututoa-kohaselt", + "Settable at global": "Seadistatav üldiselt", + "Level": "Tase", + "Setting definition:": "Seadistuse määratlus:", + "This UI does NOT check the types of the values. Use at your own risk.": "See kasutajaliides ei oska kontrollida väärtuste tüüpi ja vormingut. Muudatusi teed omal vastutusel.", + "Caution:": "Hoiatus:", + "Setting:": "Seadistus:", + "Value in this room": "Väärtus selles jututoas", + "Value": "Väärtus", + "Setting ID": "Seadistuse tunnus", + "Failed to save settings": "Seadistuste salvestamine ei õnnestunud", + "Settings Explorer": "Seadistuste haldur", + "Show chat effects (animations when receiving e.g. confetti)": "Näita vestluses edevat graafikat (näiteks kui keegi on saatnud serpentiine)" } From d679708058befe021f8fba23594180aa76ec4554 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 1 Mar 2021 15:40:46 +0000 Subject: [PATCH 109/420] Fix styling of disabled options in an Iconized menu --- res/css/views/context_menus/_IconizedContextMenu.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/css/views/context_menus/_IconizedContextMenu.scss b/res/css/views/context_menus/_IconizedContextMenu.scss index d911ac6dfe..204435995f 100644 --- a/res/css/views/context_menus/_IconizedContextMenu.scss +++ b/res/css/views/context_menus/_IconizedContextMenu.scss @@ -75,6 +75,11 @@ limitations under the License. background-color: $menu-selected-color; } + &.mx_AccessibleButton_disabled { + opacity: 0.5; + cursor: not-allowed; + } + img, .mx_IconizedContextMenu_icon { // icons width: 16px; min-width: 16px; From ce648633266c53d80148a498b98617f88629a282 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 1 Mar 2021 15:53:16 +0000 Subject: [PATCH 110/420] Annotate User Menu handle with currently selected space --- res/css/structures/_UserMenu.scss | 1 + src/components/structures/UserMenu.tsx | 40 ++++++++++++++++++++------ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index 2a4453df70..3badb0850c 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -72,6 +72,7 @@ limitations under the License. position: relative; // to make default avatars work margin-right: 8px; height: 32px; // to remove the unknown 4px gap the browser puts below it + padding: 3px 0; // to align with and without using doubleName .mx_UserMenu_userAvatar { border-radius: 32px; // should match avatar size diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 5ed6a00d74..b31a5f4b8e 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -15,13 +15,18 @@ limitations under the License. */ import React, { createRef } from "react"; +import { Room } from "matrix-js-sdk/src/models/room"; +import classNames from "classnames"; +import * as fbEmitter from "fbemitter"; + import { MatrixClientPeg } from "../../MatrixClientPeg"; import defaultDispatcher from "../../dispatcher/dispatcher"; +import dis from "../../dispatcher/dispatcher"; import { ActionPayload } from "../../dispatcher/payloads"; import { Action } from "../../dispatcher/actions"; import { _t } from "../../languageHandler"; import { ContextMenuButton } from "./ContextMenu"; -import {USER_NOTIFICATIONS_TAB, USER_SECURITY_TAB} from "../views/dialogs/UserSettingsDialog"; +import { USER_NOTIFICATIONS_TAB, USER_SECURITY_TAB } from "../views/dialogs/UserSettingsDialog"; import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload"; import FeedbackDialog from "../views/dialogs/FeedbackDialog"; import Modal from "../../Modal"; @@ -30,11 +35,10 @@ import SettingsStore from "../../settings/SettingsStore"; import {getCustomTheme} from "../../theme"; import AccessibleButton, {ButtonEvent} from "../views/elements/AccessibleButton"; import SdkConfig from "../../SdkConfig"; -import {getHomePageUrl} from "../../utils/pages"; +import { getHomePageUrl } from "../../utils/pages"; import { OwnProfileStore } from "../../stores/OwnProfileStore"; import { UPDATE_EVENT } from "../../stores/AsyncStore"; import BaseAvatar from '../views/avatars/BaseAvatar'; -import classNames from "classnames"; import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; import { SettingLevel } from "../../settings/SettingLevel"; import IconizedContextMenu, { @@ -42,16 +46,16 @@ import IconizedContextMenu, { IconizedContextMenuOptionList, } from "../views/context_menus/IconizedContextMenu"; import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore"; -import * as fbEmitter from "fbemitter"; import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore"; import { showCommunityInviteDialog } from "../../RoomInvite"; -import dis from "../../dispatcher/dispatcher"; import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; import ErrorDialog from "../views/dialogs/ErrorDialog"; import EditCommunityPrototypeDialog from "../views/dialogs/EditCommunityPrototypeDialog"; -import {UIFeature} from "../../settings/UIFeature"; +import { UIFeature } from "../../settings/UIFeature"; import HostSignupAction from "./HostSignupAction"; -import {IHostSignupConfig} from "../views/dialogs/HostSignupDialogTypes"; +import { IHostSignupConfig } from "../views/dialogs/HostSignupDialogTypes"; +import SpaceStore, { UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore"; +import RoomName from "../views/elements/RoomName"; interface IProps { isMinimized: boolean; @@ -62,6 +66,7 @@ type PartialDOMRect = Pick; interface IState { contextMenuPosition: PartialDOMRect; isDarkTheme: boolean; + selectedSpace?: Room; } export default class UserMenu extends React.Component { @@ -79,6 +84,9 @@ export default class UserMenu extends React.Component { }; OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); + if (SettingsStore.getValue("feature_spaces")) { + SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate); + } } private get hasHomePage(): boolean { @@ -96,6 +104,9 @@ export default class UserMenu extends React.Component { if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef); OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate); this.tagStoreRef.remove(); + if (SettingsStore.getValue("feature_spaces")) { + SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate); + } } private onTagStoreUpdate = () => { @@ -120,6 +131,10 @@ export default class UserMenu extends React.Component { this.forceUpdate(); }; + private onSelectedSpaceUpdate = async (selectedSpace?: Room) => { + this.setState({ selectedSpace }); + }; + private onThemeChanged = () => { this.setState({isDarkTheme: this.isUserOnDarkTheme()}); }; @@ -517,7 +532,16 @@ export default class UserMenu extends React.Component { {/* masked image in CSS */} ); - if (prototypeCommunityName) { + if (this.state.selectedSpace) { + name = ( +
+ {displayName} + + {(roomName) => {roomName}} + +
+ ); + } else if (prototypeCommunityName) { name = (
{prototypeCommunityName} From 70f1303544c65d46ffb7233259ba33b2ac3d2fa5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 1 Mar 2021 16:48:35 +0000 Subject: [PATCH 111/420] Fix invite svg viewbox --- res/img/element-icons/room/invite.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/img/element-icons/room/invite.svg b/res/img/element-icons/room/invite.svg index 655f9f118a..d2ecb837b2 100644 --- a/res/img/element-icons/room/invite.svg +++ b/res/img/element-icons/room/invite.svg @@ -1,3 +1,3 @@ - + From ea61b18c1877173cd206734c2926fca04447f0c1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 1 Mar 2021 17:02:02 +0000 Subject: [PATCH 112/420] Iterate Space Panel alignments --- res/css/structures/_SpacePanel.scss | 99 +++++++++++-------- src/components/views/spaces/SpacePanel.tsx | 21 ++-- .../views/spaces/SpaceTreeLevel.tsx | 15 ++- 3 files changed, 81 insertions(+), 54 deletions(-) diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index 8de85f95ef..324757648f 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -2,8 +2,6 @@ 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 @@ -18,8 +16,12 @@ limitations under the License. $topLevelHeight: 32px; $nestedHeight: 24px; -$gutterSize: 21px; +$gutterSize: 17px; $activeStripeSize: 4px; +$activeBorderTransparentGap: 2px; + +$activeBackgroundColor: $roomtile-selected-bg-color; +$activeBorderColor: $secondary-fg-color; .mx_SpacePanel { flex: 0 0 auto; @@ -68,11 +70,14 @@ $activeStripeSize: 4px; cursor: pointer; } - .mx_SpaceItem { - position: relative; - } - .mx_SpaceItem.collapsed { + .mx_SpaceButton { + .mx_NotificationBadge { + right: -4px; + top: -4px; + } + } + & > .mx_SpaceButton > .mx_SpaceButton_toggleCollapse { transform: rotate(-90deg); } @@ -84,20 +89,35 @@ $activeStripeSize: 4px; .mx_SpaceItem:not(.hasSubSpaces) > .mx_SpaceButton { margin-left: $gutterSize; - - &.mx_SpaceButton_active { - &::before { - left: -$gutterSize; - } - } } .mx_SpaceButton { border-radius: 8px; position: relative; - margin-bottom: 16px; + margin-bottom: 2px; display: flex; align-items: center; + padding: 4px; + + &.mx_SpaceButton_active { + &:not(.mx_SpaceButton_narrow) .mx_SpaceButton_selectionWrapper { + background-color: $activeBackgroundColor; + border-radius: 8px; + } + + &.mx_SpaceButton_narrow { + .mx_BaseAvatar, .mx_SpaceButton_avatarPlaceholder { + border: 2px $activeBorderColor solid; + border-radius: 11px; + } + } + } + + .mx_SpaceButton_selectionWrapper { + display: flex; + flex: 1; + align-items: center; + } .mx_SpaceButton_name { flex: 1; @@ -107,7 +127,7 @@ $activeStripeSize: 4px; max-width: 150px; text-overflow: ellipsis; overflow: hidden; - + padding-right: 8px; font-size: $font-14px; line-height: $font-18px; } @@ -123,24 +143,12 @@ $activeStripeSize: 4px; mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); } - &.mx_SpaceButton_active { - &::before { - position: absolute; - content: ''; - width: $activeStripeSize; - top: 0; - left: 0; - bottom: 0; - background-color: $accent-color; - border-radius: 0 4px 4px 0; - } - } - - .mx_SpaceButton_avatarPlaceholder { + .mx_SpaceButton_icon { width: $topLevelHeight; min-width: $topLevelHeight; height: $topLevelHeight; border-radius: 8px; + position: relative; &::before { position: absolute; @@ -155,7 +163,7 @@ $activeStripeSize: 4px; } } - &.mx_SpaceButton_home .mx_SpaceButton_avatarPlaceholder { + &.mx_SpaceButton_home .mx_SpaceButton_icon { background-color: #ffffff; &::before { @@ -164,19 +172,28 @@ $activeStripeSize: 4px; } } - &.mx_SpaceButton_newCancel .mx_SpaceButton_avatarPlaceholder { - background-color: $icon-button-color; + .mx_SpaceButton_avatarPlaceholder { + border: $activeBorderTransparentGap transparent solid; + padding: $activeBorderTransparentGap; + } - &::before { - transform: rotate(45deg); + .mx_BaseAvatar { + /* moving the border-radius to this element from _image + element so we can add a border to it without the initials being displaced */ + overflow: hidden; + border: 2px transparent solid; + padding: $activeBorderTransparentGap; + + .mx_BaseAvatar_initial { + top: $activeBorderTransparentGap; + left: $activeBorderTransparentGap; + } + + .mx_BaseAvatar_image { + border-radius: 8px; } } - .mx_BaseAvatar_image { - border-radius: 8px; - } - } - .mx_SpacePanel_badgeContainer { height: 16px; // don't set width so that it takes no space when there is no badge to show @@ -201,8 +218,8 @@ $activeStripeSize: 4px; .mx_SpaceButton { .mx_SpacePanel_badgeContainer { position: absolute; - right: -8px; - top: -4px; + right: 0px; + top: 2px; } } } diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index bc9cd5c9fd..760181e0e0 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -56,9 +56,10 @@ const SpaceButton: React.FC = ({ }) => { const classes = classNames("mx_SpaceButton", className, { mx_SpaceButton_active: selected, + mx_SpaceButton_narrow: isNarrow, }); - let avatar =
; + let avatar =
; if (space) { avatar = ; } @@ -74,18 +75,22 @@ const SpaceButton: React.FC = ({ if (isNarrow) { button = ( - { avatar } - { notifBadge } - { children } +
+ { avatar } + { notifBadge } + { children } +
); } else { button = ( - { avatar } - { tooltip } - { notifBadge } - { children } +
+ { avatar } + { tooltip } + { notifBadge } + { children } +
); } diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index 14fe68ff66..f94798433f 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -95,6 +95,7 @@ export class SpaceItem extends React.PureComponent { const classes = classNames("mx_SpaceButton", { mx_SpaceButton_active: isActive, mx_SpaceButton_hasMenuOpen: !!this.state.contextMenuPosition, + mx_SpaceButton_narrow: isNarrow, }); const notificationState = SpaceStore.instance.getNotificationState(space.roomId); const childItems = childSpaces && !collapsed ? { role="treeitem" > { toggleCollapseButton } - - { notifBadge } +
+ + { notifBadge } +
); } else { @@ -142,9 +145,11 @@ export class SpaceItem extends React.PureComponent { role="treeitem" > { toggleCollapseButton } - - { space.name } - { notifBadge } +
+ + { space.name } + { notifBadge } +
); } From 89386b9b2ea5d9fc2ef1ff07d0becdc0ad9fc1ff Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 1 Mar 2021 17:06:56 +0000 Subject: [PATCH 113/420] fix missing closing brace --- res/css/structures/_SpacePanel.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index 324757648f..563c5eba58 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -193,6 +193,7 @@ $activeBorderColor: $secondary-fg-color; border-radius: 8px; } } + } .mx_SpacePanel_badgeContainer { height: 16px; From d731e82fba3819143144d3d64ebb71309284c4df Mon Sep 17 00:00:00 2001 From: Jaiwanth Date: Mon, 1 Mar 2021 22:44:48 +0530 Subject: [PATCH 114/420] Fixed edit for markdown images Signed-off-by: Jaiwanth --- src/editor/deserialize.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts index 6336b4c46b..bc1dd74c7d 100644 --- a/src/editor/deserialize.ts +++ b/src/editor/deserialize.ts @@ -60,6 +60,11 @@ function parseLink(a: HTMLAnchorElement, partCreator: PartCreator) { } } +function parseImage(img: HTMLImageElement, partCreator: PartCreator) { + const { src } = img; + return partCreator.plain(`![${img.alt.replace(/[[\\\]]/g, c => "\\" + c)}](${src})`); +} + function parseCodeBlock(n: HTMLElement, partCreator: PartCreator) { const parts = []; let language = ""; @@ -102,6 +107,8 @@ function parseElement(n: HTMLElement, partCreator: PartCreator, lastNode: HTMLEl return parseHeader(n, partCreator); case "A": return parseLink(n, partCreator); + case "IMG": + return parseImage(n, partCreator); case "BR": return partCreator.newline(); case "EM": From 483d56320c8810ae88dbfd934be3c5d3eea8dee1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 1 Mar 2021 17:27:09 +0000 Subject: [PATCH 115/420] Beginning of space creation UX from space panel --- res/css/_components.scss | 2 + res/css/structures/_SpacePanel.scss | 19 ++ res/css/views/spaces/_SpaceBasicSettings.scss | 86 +++++++++ res/css/views/spaces/_SpaceCreateMenu.scss | 138 ++++++++++++++ res/img/element-icons/lock.svg | 3 + res/img/element-icons/plus.svg | 3 + src/components/structures/ContextMenu.tsx | 3 +- .../views/spaces/SpaceBasicSettings.tsx | 120 ++++++++++++ .../views/spaces/SpaceCreateMenu.tsx | 175 ++++++++++++++++++ src/components/views/spaces/SpacePanel.tsx | 21 +++ src/createRoom.ts | 4 +- src/i18n/strings/en_EN.json | 22 ++- 12 files changed, 588 insertions(+), 8 deletions(-) create mode 100644 res/css/views/spaces/_SpaceBasicSettings.scss create mode 100644 res/css/views/spaces/_SpaceCreateMenu.scss create mode 100644 res/img/element-icons/lock.svg create mode 100644 res/img/element-icons/plus.svg create mode 100644 src/components/views/spaces/SpaceBasicSettings.tsx create mode 100644 src/components/views/spaces/SpaceCreateMenu.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 29b5262826..8d6597aefa 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -233,6 +233,8 @@ @import "./views/settings/tabs/user/_PreferencesUserSettingsTab.scss"; @import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss"; @import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss"; +@import "./views/spaces/_SpaceBasicSettings.scss"; +@import "./views/spaces/_SpaceCreateMenu.scss"; @import "./views/terms/_InlineTermsAgreement.scss"; @import "./views/toasts/_AnalyticsToast.scss"; @import "./views/toasts/_NonUrgentEchoFailureToast.scss"; diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index 563c5eba58..24d2243912 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -177,6 +177,25 @@ $activeBorderColor: $secondary-fg-color; padding: $activeBorderTransparentGap; } + &.mx_SpaceButton_new .mx_SpaceButton_icon { + background-color: $accent-color; + transition: all .1s ease-in-out; // TODO transition + + &::before { + background-color: #ffffff; + mask-image: url('$(res)/img/element-icons/plus.svg'); + transition: all .2s ease-in-out; // TODO transition + } + } + + &.mx_SpaceButton_newCancel .mx_SpaceButton_icon { + background-color: $icon-button-color; + + &::before { + transform: rotate(45deg); + } + } + .mx_BaseAvatar { /* moving the border-radius to this element from _image element so we can add a border to it without the initials being displaced */ diff --git a/res/css/views/spaces/_SpaceBasicSettings.scss b/res/css/views/spaces/_SpaceBasicSettings.scss new file mode 100644 index 0000000000..204ccab2b7 --- /dev/null +++ b/res/css/views/spaces/_SpaceBasicSettings.scss @@ -0,0 +1,86 @@ +/* +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. +*/ + +.mx_SpaceBasicSettings { + .mx_Field { + margin: 32px 0; + } + + .mx_SpaceBasicSettings_avatarContainer { + display: flex; + margin-top: 24px; + + .mx_SpaceBasicSettings_avatar { + position: relative; + height: 80px; + width: 80px; + background-color: $tertiary-fg-color; + border-radius: 16px; + } + + img.mx_SpaceBasicSettings_avatar { + width: 80px; + height: 80px; + object-fit: cover; + border-radius: 16px; + } + + // only show it when the button is a div and not an img (has avatar) + div.mx_SpaceBasicSettings_avatar { + cursor: pointer; + + &::before { + content: ""; + position: absolute; + height: 80px; + width: 80px; + top: 0; + left: 0; + background-color: #ffffff; // white icon fill + mask-repeat: no-repeat; + mask-position: center; + mask-size: 20px; + mask-image: url('$(res)/img/element-icons/camera.svg'); + } + } + + > input[type="file"] { + display: none; + } + + > .mx_AccessibleButton_kind_link { + display: inline-block; + padding: 0; + margin: auto 16px; + color: #368bd6; + } + + > .mx_SpaceBasicSettings_avatar_remove { + color: $notice-primary-color; + } + } + + .mx_FormButton { + padding: 8px 22px; + margin-left: auto; + display: block; + width: min-content; + } + + .mx_AccessibleButton_disabled { + cursor: not-allowed; + } +} diff --git a/res/css/views/spaces/_SpaceCreateMenu.scss b/res/css/views/spaces/_SpaceCreateMenu.scss new file mode 100644 index 0000000000..2a11ec9f23 --- /dev/null +++ b/res/css/views/spaces/_SpaceCreateMenu.scss @@ -0,0 +1,138 @@ +/* +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. +*/ + +// TODO: the space panel currently does not have a fixed width, +// just the headers at each level have a max-width of 150px +// so this will look slightly off for now. We should probably use css grid for the whole main layout... +$spacePanelWidth: 200px; + +.mx_SpaceCreateMenu_wrapper { + // background blur everything except SpacePanel + .mx_ContextualMenu_background { + background-color: $dialog-backdrop-color; + opacity: 0.6; + left: $spacePanelWidth; + } + + .mx_ContextualMenu { + padding: 24px; + width: 480px; + box-sizing: border-box; + background-color: $primary-bg-color; + + > div { + > h2 { + font-weight: $font-semi-bold; + font-size: $font-18px; + margin-top: 4px; + } + + > p { + font-size: $font-15px; + color: $secondary-fg-color; + margin: 0; + } + } + + .mx_SpaceCreateMenuType { + position: relative; + padding: 16px 32px 16px 72px; + width: 432px; + box-sizing: border-box; + border-radius: 8px; + border: 1px solid $input-darker-bg-color; + font-size: $font-15px; + margin: 20px 0; + + > h3 { + font-weight: $font-semi-bold; + margin: 0 0 4px; + } + + > span { + color: $secondary-fg-color; + } + + &::before { + position: absolute; + content: ''; + width: 32px; + height: 32px; + top: 24px; + left: 20px; + mask-position: center; + mask-repeat: no-repeat; + mask-size: 32px; + background-color: $tertiary-fg-color; + } + + &:hover { + border-color: $accent-color; + + &::before { + background-color: $accent-color; + } + + > span { + color: $primary-fg-color; + } + } + } + + .mx_SpaceCreateMenuType_public::before { + mask-image: url('$(res)/img/globe.svg'); + mask-size: 26px; + } + .mx_SpaceCreateMenuType_private::before { + mask-image: url('$(res)/img/element-icons/lock.svg'); + } + + .mx_SpaceCreateMenu_back { + width: 28px; + height: 28px; + position: relative; + background-color: $theme-button-bg-color; + border-radius: 14px; + margin-bottom: 12px; + + &::before { + content: ""; + position: absolute; + height: 28px; + width: 28px; + top: 0; + left: 0; + background-color: $muted-fg-color; + transform: rotate(90deg); + mask-repeat: no-repeat; + mask-position: 2px 3px; + mask-size: 24px; + mask-image: url('$(res)/img/feather-customised/chevron-down.svg'); + } + } + + .mx_FormButton { + padding: 8px 22px; + margin-left: auto; + display: block; + width: min-content; + } + + .mx_AccessibleButton_disabled { + cursor: not-allowed; + } + } +} diff --git a/res/img/element-icons/lock.svg b/res/img/element-icons/lock.svg new file mode 100644 index 0000000000..06fe52a391 --- /dev/null +++ b/res/img/element-icons/lock.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/plus.svg b/res/img/element-icons/plus.svg new file mode 100644 index 0000000000..ea1972237d --- /dev/null +++ b/res/img/element-icons/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index b5e5966d91..726ff547ff 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -76,6 +76,7 @@ export interface IProps extends IPosition { hasBackground?: boolean; // whether this context menu should be focus managed. If false it must handle itself managed?: boolean; + wrapperClassName?: string; // Function to be called on menu close onFinished(); @@ -365,7 +366,7 @@ export class ContextMenu extends React.PureComponent { return (
{ + const avatarUploadRef = useRef(); + const [avatar, setAvatarDataUrl] = useState(avatarUrl); // avatar data url cache + + let avatarSection; + if (avatarDisabled) { + if (avatar) { + avatarSection = ; + } else { + avatarSection =
; + } + } else { + if (avatar) { + avatarSection = + avatarUploadRef.current?.click()} + element="img" + src={avatar} + alt="" + /> + { + avatarUploadRef.current.value = ""; + setAvatarDataUrl(undefined); + setAvatar(undefined); + }} kind="link" className="mx_SpaceBasicSettings_avatar_remove"> + { _t("Delete") } + + ; + } else { + avatarSection = +
avatarUploadRef.current?.click()} /> + avatarUploadRef.current?.click()} kind="link"> + { _t("Upload") } + + ; + } + } + + return
+
+ { avatarSection } + { + if (!e.target.files?.length) return; + const file = e.target.files[0]; + setAvatar(file); + const reader = new FileReader(); + reader.onload = (ev) => { + setAvatarDataUrl(ev.target.result as string); + }; + reader.readAsDataURL(file); + }} accept="image/*" /> +
+ + setName(ev.target.value)} + disabled={nameDisabled} + /> + + setTopic(ev.target.value)} + rows={3} + disabled={topicDisabled} + /> +
; +}; + +export default SpaceBasicSettings; diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx new file mode 100644 index 0000000000..9d0543a6c5 --- /dev/null +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -0,0 +1,175 @@ +/* +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 React, {useContext, useState} from "react"; +import classNames from "classnames"; +import {EventType, RoomType, RoomCreateTypeField} from "matrix-js-sdk/src/@types/event"; + +import {_t} from "../../../languageHandler"; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; +import {ChevronFace, ContextMenu} from "../../structures/ContextMenu"; +import FormButton from "../elements/FormButton"; +import createRoom, {IStateEvent, Preset} from "../../../createRoom"; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import SpaceBasicSettings from "./SpaceBasicSettings"; +import AccessibleButton from "../elements/AccessibleButton"; +import FocusLock from "react-focus-lock"; + +const SpaceCreateMenuType = ({ title, description, className, onClick }) => { + return ( + +

{ title }

+ { description } +
+ ); +}; + +enum Visibility { + Public, + Private, +} + +const SpaceCreateMenu = ({ onFinished }) => { + const cli = useContext(MatrixClientContext); + const [visibility, setVisibility] = useState(null); + const [name, setName] = useState(""); + const [avatar, setAvatar] = useState(null); + const [topic, setTopic] = useState(""); + const [busy, setBusy] = useState(false); + + const onSpaceCreateClick = async () => { + if (busy) return; + setBusy(true); + const initialState: IStateEvent[] = [ + { + type: EventType.RoomHistoryVisibility, + content: { + "history_visibility": visibility === Visibility.Public ? "world_readable" : "invited", + }, + }, + ]; + if (avatar) { + const url = await cli.uploadContent(avatar); + + initialState.push({ + type: EventType.RoomAvatar, + content: { url }, + }); + } + if (topic) { + initialState.push({ + type: EventType.RoomTopic, + content: { topic }, + }); + } + + try { + await createRoom({ + createOpts: { + preset: visibility === Visibility.Public ? Preset.PublicChat : Preset.PrivateChat, + name, + creation_content: { + // Based on MSC1840 + [RoomCreateTypeField]: RoomType.Space, + }, + initial_state: initialState, + power_level_content_override: { + // Only allow Admins to write to the timeline to prevent hidden sync spam + events_default: 100, + }, + }, + spinner: false, + encryption: false, + andView: true, + inlineErrors: true, + }); + + onFinished(); + } catch (e) { + console.error(e); + } + }; + + let body; + if (visibility === null) { + body = +

{ _t("Create a space") }

+

{ _t("Organise rooms into spaces, for just you or anyone") }

+ + setVisibility(Visibility.Public)} + /> + setVisibility(Visibility.Private)} + /> + + {/*

{ _t("Looking to join an existing space?") }

*/} +
; + } else { + body = + setVisibility(null)} + title={_t("Go back")} + /> + +

+ { + visibility === Visibility.Public + ? _t("Personalise your public space") + : _t("Personalise your private space") + } +

+

+ { + _t("Give it a photo, name and description to help you identify it.") + } { + _t("You can change these at any point.") + } +

+ + + + +
; + } + + return + + { body } + + ; +} + +export default SpaceCreateMenu; diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 760181e0e0..48e2c86b2c 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -20,6 +20,8 @@ import {Room} from "matrix-js-sdk/src/models/room"; import {_t} from "../../../languageHandler"; import RoomAvatar from "../avatars/RoomAvatar"; +import {useContextMenu} from "../../structures/ContextMenu"; +import SpaceCreateMenu from "./SpaceCreateMenu"; import {SpaceItem} from "./SpaceTreeLevel"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import {useEventEmitter} from "../../../hooks/useEventEmitter"; @@ -112,9 +114,21 @@ const useSpaces = (): [Room[], Room | null] => { }; const SpacePanel = () => { + // We don't need the handle as we position the menu in a constant location + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu(); const [spaces, activeSpace] = useSpaces(); const [isPanelCollapsed, setPanelCollapsed] = useState(true); + const newClasses = classNames("mx_SpaceButton_new", { + mx_SpaceButton_newCancel: menuDisplayed, + }); + + let contextMenu = null; + if (menuDisplayed) { + contextMenu = ; + } + const onKeyDown = (ev: React.KeyboardEvent) => { let handled = true; @@ -203,12 +217,19 @@ const SpacePanel = () => { onExpand={() => setPanelCollapsed(false)} />) }
+ setPanelCollapsed(!isPanelCollapsed)} title={expandCollapseButtonTitle} /> + { contextMenu } )} diff --git a/src/createRoom.ts b/src/createRoom.ts index 9e3960cdb7..e773c51290 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -41,7 +41,7 @@ enum Visibility { Private = "private", } -enum Preset { +export enum Preset { PrivateChat = "private_chat", TrustedPrivateChat = "trusted_private_chat", PublicChat = "public_chat", @@ -54,7 +54,7 @@ interface Invite3PID { address: string; } -interface IStateEvent { +export interface IStateEvent { type: string; state_key?: string; // defaults to an empty string content: object; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 38460a5f6e..1b29e65b40 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -978,11 +978,27 @@ "From %(deviceName)s (%(deviceId)s)": "From %(deviceName)s (%(deviceId)s)", "Decline (%(counter)s)": "Decline (%(counter)s)", "Accept to continue:": "Accept to continue:", + "Delete": "Delete", + "Upload": "Upload", + "Name": "Name", + "Description": "Description", + "Create a space": "Create a space", + "Organise rooms into spaces, for just you or anyone": "Organise rooms into spaces, for just you or anyone", + "Public": "Public", + "Open space for anyone, best for communities": "Open space for anyone, best for communities", + "Private": "Private", + "Invite only space, best for yourself or teams": "Invite only space, best for yourself or teams", + "Go back": "Go back", + "Personalise your public space": "Personalise your public space", + "Personalise your private space": "Personalise your private space", + "Give it a photo, name and description to help you identify it.": "Give it a photo, name and description to help you identify it.", + "You can change these at any point.": "You can change these at any point.", + "Creating...": "Creating...", + "Create": "Create", "Expand space panel": "Expand space panel", "Collapse space panel": "Collapse space panel", "Home": "Home", "Remove": "Remove", - "Upload": "Upload", "This bridge was provisioned by .": "This bridge was provisioned by .", "This bridge is managed by .": "This bridge is managed by .", "Workspace: ": "Workspace: ", @@ -1136,7 +1152,6 @@ "Disconnect anyway": "Disconnect anyway", "You are still sharing your personal data on the identity server .": "You are still sharing your personal data on the identity server .", "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.": "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.", - "Go back": "Go back", "Identity Server (%(server)s)": "Identity Server (%(server)s)", "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.", "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.": "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.", @@ -2011,7 +2026,6 @@ "You can change this later if needed.": "You can change this later if needed.", "What's the name of your community or team?": "What's the name of your community or team?", "Enter name": "Enter name", - "Create": "Create", "Add image (optional)": "Add image (optional)", "An image will help people identify your community.": "An image will help people identify your community.", "Community IDs cannot be empty.": "Community IDs cannot be empty.", @@ -2033,7 +2047,6 @@ "Create a public room": "Create a public room", "Create a private room": "Create a private room", "Create a room in %(communityName)s": "Create a room in %(communityName)s", - "Name": "Name", "Topic (optional)": "Topic (optional)", "Make this room public": "Make this room public", "Block anyone not part of %(serverName)s from ever joining this room.": "Block anyone not part of %(serverName)s from ever joining this room.", @@ -2456,7 +2469,6 @@ "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!": "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!", "Long Description (HTML)": "Long Description (HTML)", "Upload avatar": "Upload avatar", - "Description": "Description", "Community %(groupId)s not found": "Community %(groupId)s not found", "This homeserver does not support communities": "This homeserver does not support communities", "Failed to load %(groupId)s": "Failed to load %(groupId)s", From c8fe3f76765108869e85a4b0140461210c0f5eff Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 1 Mar 2021 17:54:53 +0000 Subject: [PATCH 116/420] Pass room creation opts for new rooms into RoomView --- src/components/structures/LoggedInView.tsx | 3 +++ src/components/structures/MatrixChat.tsx | 6 +++++- src/components/structures/RoomView.tsx | 3 +++ src/createRoom.ts | 6 +++++- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index c01214f3f4..1694b4bcf5 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -55,6 +55,7 @@ import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; import Modal from "../../Modal"; import { ICollapseConfig } from "../../resizer/distributors/collapse"; import HostSignupContainer from '../views/host_signup/HostSignupContainer'; +import { IOpts } from "../../createRoom"; // We need to fetch each pinned message individually (if we don't already have it) // so each pinned message may trigger a request. Limit the number per room for sanity. @@ -91,6 +92,7 @@ interface IProps { currentGroupId?: string; currentGroupIsNew?: boolean; justRegistered?: boolean; + roomJustCreatedOpts?: IOpts; } interface IUsageLimit { @@ -619,6 +621,7 @@ class LoggedInView extends React.Component { viaServers={this.props.viaServers} key={this.props.currentRoomId || 'roomview'} resizeNotifier={this.props.resizeNotifier} + justCreatedOpts={this.props.roomJustCreatedOpts} />; break; diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 5045e44182..8e3d3e6b5f 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -48,7 +48,7 @@ import * as Lifecycle from '../../Lifecycle'; import '../../stores/LifecycleStore'; import PageTypes from '../../PageTypes'; -import createRoom from "../../createRoom"; +import createRoom, {IOpts} from "../../createRoom"; import {_t, _td, getCurrentLanguage} from '../../languageHandler'; import SettingsStore from "../../settings/SettingsStore"; import ThemeController from "../../settings/controllers/ThemeController"; @@ -144,6 +144,8 @@ interface IRoomInfo { oob_data?: object; via_servers?: string[]; threepid_invite?: IThreepidInvite; + + justCreatedOpts?: IOpts; } /* eslint-enable camelcase */ @@ -201,6 +203,7 @@ interface IState { viaServers?: string[]; pendingInitialSync?: boolean; justRegistered?: boolean; + roomJustCreatedOpts?: IOpts; } export default class MatrixChat extends React.PureComponent { @@ -922,6 +925,7 @@ export default class MatrixChat extends React.PureComponent { roomOobData: roomInfo.oob_data, viaServers: roomInfo.via_servers, ready: true, + roomJustCreatedOpts: roomInfo.justCreatedOpts, }, () => { this.notifyNewScreen('room/' + presentedId, replaceLast); }); diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 6c8560f42c..933514754c 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -80,6 +80,8 @@ import { showToast as showNotificationsToast } from "../../toasts/DesktopNotific import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore"; import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutStore"; import { objectHasDiff } from "../../utils/objects"; +import SpaceRoomView from "./SpaceRoomView"; +import { IOpts } from "../../createRoom"; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -114,6 +116,7 @@ interface IProps { autoJoin?: boolean; resizeNotifier: ResizeNotifier; + justCreatedOpts?: IOpts; // Called with the credentials of a registered user (if they were a ROU that transitioned to PWLU) onRegistered?(credentials: IMatrixClientCreds): void; diff --git a/src/createRoom.ts b/src/createRoom.ts index e773c51290..00a970eedc 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -75,7 +75,7 @@ interface ICreateOpts { power_level_content_override?: object; } -interface IOpts { +export interface IOpts { dmUserId?: string; createOpts?: ICreateOpts; spinner?: boolean; @@ -197,6 +197,9 @@ export default function createRoom(opts: IOpts): Promise { // room has been created, so we race here with the client knowing that // the room exists, causing things like // https://github.com/vector-im/vector-web/issues/1813 + // Even if we were to block on the echo, servers tend to split the room + // state over multiple syncs so we can't atomically know when we have the + // entire thing. if (opts.andView) { dis.dispatch({ action: 'view_room', @@ -206,6 +209,7 @@ export default function createRoom(opts: IOpts): Promise { // so we are expecting the room to come down the sync // stream, if it hasn't already. joining: true, + justCreatedOpts: opts, }); } CountlyAnalytics.instance.trackRoomCreate(startTime, roomId); From c10512fd569021089c34a8ea2b6083d0996f02b3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 1 Mar 2021 18:10:17 +0000 Subject: [PATCH 117/420] Initial SpaceRoomView work --- res/css/_components.scss | 2 + res/css/structures/_SpaceRoomView.scss | 244 +++++++++ res/css/views/spaces/_SpacePublicShare.scss | 60 +++ res/img/element-icons/link.svg | 3 + res/themes/dark/css/_dark.scss | 1 + res/themes/legacy-dark/css/_legacy-dark.scss | 1 + .../legacy-light/css/_legacy-light.scss | 1 + res/themes/light/css/_light.scss | 1 + src/RoomInvite.js | 9 +- src/components/structures/RightPanel.js | 23 +- src/components/structures/RoomView.tsx | 20 +- src/components/structures/SpaceRoomView.tsx | 503 ++++++++++++++++++ src/components/views/dialogs/InviteDialog.tsx | 66 ++- .../views/spaces/SpacePublicShare.tsx | 65 +++ .../payloads/SetRightPanelPhasePayload.ts | 2 + src/hooks/useStateArray.ts | 29 + src/i18n/strings/en_EN.json | 41 +- src/stores/RightPanelStorePhases.ts | 12 + src/utils/space.ts | 28 + 19 files changed, 1066 insertions(+), 45 deletions(-) create mode 100644 res/css/structures/_SpaceRoomView.scss create mode 100644 res/css/views/spaces/_SpacePublicShare.scss create mode 100644 res/img/element-icons/link.svg create mode 100644 src/components/structures/SpaceRoomView.tsx create mode 100644 src/components/views/spaces/SpacePublicShare.tsx create mode 100644 src/hooks/useStateArray.ts create mode 100644 src/utils/space.ts diff --git a/res/css/_components.scss b/res/css/_components.scss index 8d6597aefa..ca66aa60ec 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -28,6 +28,7 @@ @import "./structures/_ScrollPanel.scss"; @import "./structures/_SearchBox.scss"; @import "./structures/_SpacePanel.scss"; +@import "./structures/_SpaceRoomView.scss"; @import "./structures/_TabbedView.scss"; @import "./structures/_ToastContainer.scss"; @import "./structures/_UploadBar.scss"; @@ -235,6 +236,7 @@ @import "./views/settings/tabs/user/_VoiceUserSettingsTab.scss"; @import "./views/spaces/_SpaceBasicSettings.scss"; @import "./views/spaces/_SpaceCreateMenu.scss"; +@import "./views/spaces/_SpacePublicShare.scss"; @import "./views/terms/_InlineTermsAgreement.scss"; @import "./views/toasts/_AnalyticsToast.scss"; @import "./views/toasts/_NonUrgentEchoFailureToast.scss"; diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss new file mode 100644 index 0000000000..559f405e59 --- /dev/null +++ b/res/css/structures/_SpaceRoomView.scss @@ -0,0 +1,244 @@ +/* +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. +*/ + +$SpaceRoomViewInnerWidth: 428px; + +.mx_SpaceRoomView { + .mx_MainSplit > div:first-child { + padding: 80px 60px; + flex-grow: 1; + + h1 { + margin: 0; + font-size: $font-24px; + font-weight: $font-semi-bold; + color: $primary-fg-color; + width: max-content; + } + + .mx_SpaceRoomView_description { + font-size: $font-15px; + color: $secondary-fg-color; + margin-top: 12px; + margin-bottom: 24px; + } + + .mx_SpaceRoomView_buttons { + display: block; + margin-top: 44px; + width: $SpaceRoomViewInnerWidth; + text-align: right; // button alignment right + + .mx_FormButton { + padding: 8px 22px; + margin-left: 16px; + } + } + + .mx_Field { + max-width: $SpaceRoomViewInnerWidth; + + & + .mx_Field { + margin-top: 28px; + } + } + + .mx_SpaceRoomView_errorText { + font-weight: $font-semi-bold; + font-size: $font-12px; + line-height: $font-15px; + color: $notice-primary-color; + margin-bottom: 28px; + } + + .mx_AccessibleButton_disabled { + cursor: not-allowed; + } + } + + .mx_SpaceRoomView_landing { + overflow-y: auto; + + > .mx_BaseAvatar_image, + > .mx_BaseAvatar > .mx_BaseAvatar_image { + border-radius: 12px; + } + + .mx_SpaceRoomView_landing_name { + margin: 24px 0 16px; + font-size: $font-15px; + color: $secondary-fg-color; + + > span { + display: inline-block; + } + + .mx_SpaceRoomView_landing_nameRow { + margin-top: 12px; + + > h1 { + display: inline-block; + } + } + + .mx_SpaceRoomView_landing_inviter { + .mx_BaseAvatar { + margin-right: 4px; + vertical-align: middle; + } + } + + .mx_SpaceRoomView_landing_memberCount { + position: relative; + margin-left: 24px; + padding: 0 0 0 28px; + line-height: $font-24px; + vertical-align: text-bottom; + + &::before { + position: absolute; + content: ''; + width: 24px; + height: 24px; + top: 0; + left: 0; + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + background-color: $accent-color; + mask-image: url('$(res)/img/element-icons/community-members.svg'); + } + } + } + + .mx_SpaceRoomView_landing_topic { + font-size: $font-15px; + } + + .mx_SpaceRoomView_landing_joinButtons { + margin-top: 24px; + + .mx_FormButton { + padding: 8px 22px; + } + } + } + + .mx_SpaceRoomView_privateScope { + .mx_RadioButton { + width: $SpaceRoomViewInnerWidth; + border-radius: 8px; + border: 1px solid $space-button-outline-color; + padding: 16px 16px 16px 72px; + margin-top: 36px; + cursor: pointer; + box-sizing: border-box; + position: relative; + + > div:first-of-type { + // hide radio dot + display: none; + } + + .mx_RadioButton_content { + margin: 0; + + > h3 { + margin: 0 0 4px; + font-size: $font-15px; + font-weight: $font-semi-bold; + line-height: $font-18px; + } + + > div { + color: $secondary-fg-color; + font-size: $font-15px; + line-height: $font-24px; + } + } + + &::before { + content: ""; + position: absolute; + height: 32px; + width: 32px; + top: 24px; + left: 20px; + background-color: $secondary-fg-color; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + } + } + + .mx_RadioButton_checked { + border-color: $accent-color; + + .mx_RadioButton_content { + > div { + color: $primary-fg-color; + } + } + + &::before { + background-color: $accent-color; + } + } + + .mx_SpaceRoomView_privateScope_justMeButton::before { + mask-image: url('$(res)/img/element-icons/room/members.svg'); + } + + .mx_SpaceRoomView_privateScope_meAndMyTeammatesButton::before { + mask-image: url('$(res)/img/element-icons/community-members.svg'); + } + } + + .mx_SpaceRoomView_inviteTeammates { + .mx_SpaceRoomView_inviteTeammates_buttons { + color: $secondary-fg-color; + margin-top: 28px; + + .mx_AccessibleButton { + position: relative; + display: inline-block; + padding-left: 32px; + line-height: 24px; // to center icons + + &::before { + content: ""; + position: absolute; + height: 24px; + width: 24px; + top: 0; + left: 0; + background-color: $secondary-fg-color; + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + } + + & + .mx_AccessibleButton { + margin-left: 32px; + } + } + + .mx_SpaceRoomView_inviteTeammates_inviteDialogButton::before { + mask-image: url('$(res)/img/element-icons/room/invite.svg'); + } + } + } +} diff --git a/res/css/views/spaces/_SpacePublicShare.scss b/res/css/views/spaces/_SpacePublicShare.scss new file mode 100644 index 0000000000..9ba0549ae3 --- /dev/null +++ b/res/css/views/spaces/_SpacePublicShare.scss @@ -0,0 +1,60 @@ +/* +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. +*/ + +.mx_SpacePublicShare { + .mx_AccessibleButton { + border: 1px solid $space-button-outline-color; + box-sizing: border-box; + border-radius: 8px; + padding: 12px 24px 12px 52px; + margin-top: 16px; + width: $SpaceRoomViewInnerWidth; + font-size: $font-15px; + line-height: $font-24px; + position: relative; + display: flex; + + > span { + color: #368bd6; + margin-left: auto; + } + + &:hover { + background-color: rgba(141, 151, 165, 0.1); + } + + &::before { + content: ""; + position: absolute; + width: 30px; + height: 30px; + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + background: $muted-fg-color; + left: 12px; + top: 9px; + } + + &.mx_SpacePublicShare_shareButton::before { + mask-image: url('$(res)/img/element-icons/link.svg'); + } + + &.mx_SpacePublicShare_inviteButton::before { + mask-image: url('$(res)/img/element-icons/room/invite.svg'); + } + } +} diff --git a/res/img/element-icons/link.svg b/res/img/element-icons/link.svg new file mode 100644 index 0000000000..ab3d54b838 --- /dev/null +++ b/res/img/element-icons/link.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index a878aa3cdd..0de5e69782 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -123,6 +123,7 @@ $roomsublist-divider-color: $primary-fg-color; $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%); $groupFilterPanel-divider-color: $roomlist-header-color; +$space-button-outline-color: rgba(141, 151, 165, 0.2); $roomtile-preview-color: $secondary-fg-color; $roomtile-default-badge-bg-color: #61708b; diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 3e3c299af9..8c5f20178b 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -120,6 +120,7 @@ $roomsublist-divider-color: $primary-fg-color; $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%); $groupFilterPanel-divider-color: $roomlist-header-color; +$space-button-outline-color: rgba(141, 151, 165, 0.2); $roomtile-preview-color: #9e9e9e; $roomtile-default-badge-bg-color: #61708b; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index a740ba155c..3ba10a68ea 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -187,6 +187,7 @@ $roomsublist-divider-color: $primary-fg-color; $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%); $groupFilterPanel-divider-color: $roomlist-header-color; +$space-button-outline-color: #E3E8F0; $roomtile-preview-color: #9e9e9e; $roomtile-default-badge-bg-color: #61708b; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 1c89d83c01..76bf2ddc21 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -181,6 +181,7 @@ $roomsublist-divider-color: $primary-fg-color; $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%); $groupFilterPanel-divider-color: $roomlist-header-color; +$space-button-outline-color: #E3E8F0; $roomtile-preview-color: $secondary-fg-color; $roomtile-default-badge-bg-color: #61708b; diff --git a/src/RoomInvite.js b/src/RoomInvite.js index 06d3fb04e8..728ae11e79 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -22,7 +22,7 @@ import MultiInviter from './utils/MultiInviter'; import Modal from './Modal'; import * as sdk from './'; import { _t } from './languageHandler'; -import {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog"; +import InviteDialog, {KIND_DM, KIND_INVITE, KIND_SPACE_INVITE} from "./components/views/dialogs/InviteDialog"; import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog"; import {CommunityPrototypeStore} from "./stores/CommunityPrototypeStore"; @@ -75,6 +75,13 @@ export function showCommunityInviteDialog(communityId) { } } +export const showSpaceInviteDialog = (roomId) => { + Modal.createTrackedDialog("Invite Users", "Space", InviteDialog, { + kind: KIND_SPACE_INVITE, + roomId, + }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); +}; + /** * Checks if the given MatrixEvent is a valid 3rd party user invite. * @param {MatrixEvent} event The event to check diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js index d66049d3a5..3d9df2e927 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.js @@ -24,7 +24,11 @@ import dis from '../../dispatcher/dispatcher'; import RateLimitedFunc from '../../ratelimitedfunc'; import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker'; import GroupStore from '../../stores/GroupStore'; -import {RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPanelStorePhases"; +import { + RightPanelPhases, + RIGHT_PANEL_PHASES_NO_ARGS, + RIGHT_PANEL_SPACE_PHASES, +} from "../../stores/RightPanelStorePhases"; import RightPanelStore from "../../stores/RightPanelStore"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import {Action} from "../../dispatcher/actions"; @@ -79,6 +83,8 @@ export default class RightPanel extends React.Component { return RightPanelPhases.GroupMemberList; } return rps.groupPanelPhase; + } else if (this.props.room?.isSpaceRoom() && !RIGHT_PANEL_SPACE_PHASES.includes(rps.roomPanelPhase)) { + return RightPanelPhases.SpaceMemberList; } else if (userForPanel) { // XXX FIXME AAAAAARGH: What is going on with this class!? It takes some of its state // from its props and some from a store, except if the contents of the store changes @@ -99,9 +105,8 @@ export default class RightPanel extends React.Component { return rps.roomPanelPhase; } return RightPanelPhases.RoomMemberInfo; - } else { - return rps.roomPanelPhase; } + return rps.roomPanelPhase; } componentDidMount() { @@ -181,6 +186,7 @@ export default class RightPanel extends React.Component { verificationRequest: payload.verificationRequest, verificationRequestPromise: payload.verificationRequestPromise, widgetId: payload.widgetId, + space: payload.space, }); } } @@ -232,6 +238,13 @@ export default class RightPanel extends React.Component { panel = ; } break; + case RightPanelPhases.SpaceMemberList: + panel = ; + break; case RightPanelPhases.GroupMemberList: if (this.props.groupId) { @@ -244,10 +257,11 @@ export default class RightPanel extends React.Component { break; case RightPanelPhases.RoomMemberInfo: + case RightPanelPhases.SpaceMemberInfo: case RightPanelPhases.EncryptionPanel: panel = ; break; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 933514754c..1961779d0e 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1400,7 +1400,7 @@ export default class RoomView extends React.Component { }); }; - private onRejectButtonClicked = ev => { + private onRejectButtonClicked = () => { this.setState({ rejecting: true, }); @@ -1460,7 +1460,7 @@ export default class RoomView extends React.Component { } }; - private onRejectThreepidInviteButtonClicked = ev => { + private onRejectThreepidInviteButtonClicked = () => { // We can reject 3pid invites in the same way that we accept them, // using /leave rather than /join. In the short term though, we // just ignore them. @@ -1723,7 +1723,7 @@ export default class RoomView extends React.Component { } const myMembership = this.state.room.getMyMembership(); - if (myMembership == 'invite') { + if (myMembership === "invite" && !this.state.room.isSpaceRoom()) { // SpaceRoomView handles invites itself if (this.state.joining || this.state.rejecting) { return ( @@ -1852,7 +1852,7 @@ export default class RoomView extends React.Component { room={this.state.room} /> ); - if (!this.state.canPeek) { + if (!this.state.canPeek && !this.state.room?.isSpaceRoom()) { return (
{ previewBar } @@ -1874,6 +1874,18 @@ export default class RoomView extends React.Component { ); } + if (this.state.room?.isSpaceRoom()) { + return ; + } + const auxPanel = ( { + const members = useRoomMembers(room); + const count = members.length; + + if (children) return children(count); + return count; +}; + +const useMyRoomMembership = (room: Room) => { + const [membership, setMembership] = useState(room.getMyMembership()); + useEventEmitter(room, "Room.myMembership", () => { + setMembership(room.getMyMembership()); + }); + return membership; +}; + +const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => { + const cli = useContext(MatrixClientContext); + const myMembership = useMyRoomMembership(space); + const joinRule = space.getJoinRule(); + const userId = cli.getUserId(); + + let joinButtons; + if (myMembership === "invite") { + joinButtons =
+ + + {_t("Decline")} + +
; + } else if (myMembership !== "join" && joinRule === "public") { + joinButtons =
+ +
; + } + + return
+ +
+ + {(name) => { + const tags = { name: () =>
+

{ name }

+ + {(count) => count > 0 ? ( + { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.RoomMemberList, + refireParams: { space }, + }); + }} + > + { _t("%(count)s members", { count }) } + + ) : null} + +
}; + if (myMembership === "invite") { + const inviteSender = space.getMember(userId)?.events.member?.getSender(); + const inviter = inviteSender && space.getMember(inviteSender); + + if (inviteSender) { + return _t(" invited you to ", {}, { + name: tags.name, + inviter: () => inviter + ? + + { inviter.name } + + : + { inviteSender } + , + }) as JSX.Element; + } else { + return _t("You have been invited to ", {}, tags) as JSX.Element; + } + } else if (shouldShowSpaceSettings(cli, space)) { + if (space.getJoinRule() === "public") { + return _t("Your public space ", {}, tags) as JSX.Element; + } else { + return _t("Your private space ", {}, tags) as JSX.Element; + } + } + return _t("Welcome to ", {}, tags) as JSX.Element; + }} +
+
+
+ +
+ { joinButtons } +
; +}; + +const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => { + const [busy, setBusy] = useState(false); + const [error, setError] = useState(""); + const numFields = 3; + const placeholders = [_t("General"), _t("Random"), _t("Support")]; + // TODO vary default prefills for "Just Me" spaces + const [roomNames, setRoomName] = useStateArray(numFields, [_t("General"), _t("Random"), ""]); + const fields = new Array(numFields).fill(0).map((_, i) => { + const name = "roomName" + i; + return setRoomName(i, ev.target.value)} + />; + }); + + const onNextClick = async () => { + setError(""); + setBusy(true); + try { + await Promise.all(roomNames.map(name => name.trim()).filter(Boolean).map(name => { + return createRoom({ + createOpts: { + preset: space.getJoinRule() === "public" ? Preset.PublicChat : Preset.PrivateChat, + name, + }, + spinner: false, + encryption: false, + andView: false, + inlineErrors: true, + parentSpace: space, + }); + })); + onFinished(); + } catch (e) { + console.error("Failed to create initial space rooms", e); + setError(_t("Failed to create initial space rooms")); + } + setBusy(false); + }; + + let onClick = onFinished; + let buttonLabel = _t("Skip for now"); + if (roomNames.some(name => name.trim())) { + onClick = onNextClick; + buttonLabel = busy ? _t("Creating rooms...") : _t("Next") + } + + return
+

{ title }

+
{ description }
+ + { error &&
{ error }
} + { fields } + +
+ +
+
; +}; + +const SpaceSetupPublicShare = ({ space, onFinished }) => { + return
+

{ _t("Share your public space") }

+
{ _t("At the moment only you can see it.") }
+ + + +
+ +
+
; +}; + +const SpaceSetupPrivateScope = ({ onFinished }) => { + const [option, setOption] = useState(null); + + return
+

{ _t("Who are you working with?") }

+
{ _t("Ensure the right people have access to the space.") }
+ + +

{ _t("Just Me") }

+
{ _t("A private space just for you") }
+ , + }, { + value: "meAndMyTeammates", + className: "mx_SpaceRoomView_privateScope_meAndMyTeammatesButton", + label: +

{ _t("Me and my teammates") }

+
{ _t("A private space for you and your teammates") }
+
, + }, + ]} + /> + +
+ onFinished(option !== "justMe")} /> +
+
; +}; + +const validateEmailRules = withValidation({ + rules: [{ + key: "email", + test: ({ value }) => !value || Email.looksValid(value), + invalid: () => _t("Doesn't look like a valid email address"), + }], +}); + +const SpaceSetupPrivateInvite = ({ space, onFinished }) => { + const [busy, setBusy] = useState(false); + const [error, setError] = useState(""); + const numFields = 3; + const fieldRefs: RefObject[] = [useRef(), useRef(), useRef()]; + const [emailAddresses, setEmailAddress] = useStateArray(numFields, ""); + const fields = new Array(numFields).fill(0).map((_, i) => { + const name = "emailAddress" + i; + return setEmailAddress(i, ev.target.value)} + ref={fieldRefs[i]} + onValidate={validateEmailRules} + />; + }); + + const onNextClick = async () => { + setError(""); + for (let i = 0; i < fieldRefs.length; i++) { + const fieldRef = fieldRefs[i]; + const valid = await fieldRef.current.validate({ allowEmpty: true }); + + if (valid === false) { // true/null are allowed + fieldRef.current.focus(); + fieldRef.current.validate({ allowEmpty: true, focused: true }); + return; + } + } + + setBusy(true); + const targetIds = emailAddresses.map(name => name.trim()).filter(Boolean); + try { + const result = await inviteMultipleToRoom(space.roomId, targetIds); + + const failedUsers = Object.keys(result.states).filter(a => result.states[a] === "error"); + if (failedUsers.length > 0) { + console.log("Failed to invite users to space: ", result); + setError(_t("Failed to invite the following users to your space: %(csvUsers)s", { + csvUsers: failedUsers.join(", "), + })); + } else { + onFinished(); + } + } catch (err) { + console.error("Failed to invite users to space: ", err); + setError(_t("We couldn't invite those users. Please check the users you want to invite and try again.")); + } + setBusy(false); + }; + + return
+

{ _t("Invite your teammates") }

+
{ _t("Ensure the right people have access to the space.") }
+ + { error &&
{ error }
} + { fields } + +
+ showSpaceInviteDialog(space.roomId)} + > + { _t("Invite by username") } + +
+ +
+ {_t("Skip for now")} + +
+
; +}; + +export default class SpaceRoomView extends React.PureComponent { + static contextType = MatrixClientContext; + + private readonly creator: string; + private readonly dispatcherRef: string; + private readonly rightPanelStoreToken: EventSubscription; + + constructor(props, context) { + super(props, context); + + let phase = Phase.Landing; + + this.creator = this.props.space.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender(); + const showSetup = this.props.justCreatedOpts && this.context.getUserId() === this.creator; + + if (showSetup) { + phase = this.props.justCreatedOpts.createOpts.preset === Preset.PublicChat + ? Phase.PublicCreateRooms : Phase.PrivateScope; + } + + this.state = { + phase, + showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, + }; + + this.dispatcherRef = defaultDispatcher.register(this.onAction); + this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate); + } + + componentWillUnmount() { + defaultDispatcher.unregister(this.dispatcherRef); + this.rightPanelStoreToken.remove(); + } + + private onRightPanelStoreUpdate = () => { + this.setState({ + showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, + }); + }; + + private onAction = (payload: ActionPayload) => { + if (payload.action !== Action.ViewUser && payload.action !== "view_3pid_invite") return; + + if (payload.action === Action.ViewUser && payload.member) { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.SpaceMemberInfo, + refireParams: { + space: this.props.space, + member: payload.member, + }, + }); + } else if (payload.action === "view_3pid_invite" && payload.event) { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.Space3pidMemberInfo, + refireParams: { + space: this.props.space, + event: payload.event, + }, + }); + } else { + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.SpaceMemberList, + refireParams: { space: this.props.space }, + }); + } + }; + + private renderBody() { + switch (this.state.phase) { + case Phase.Landing: + return ; + + case Phase.PublicCreateRooms: + return this.setState({ phase: Phase.PublicShare })} + />; + case Phase.PublicShare: + return this.setState({ phase: Phase.Landing })} + />; + + case Phase.PrivateScope: + return { + this.setState({ phase: invite ? Phase.PrivateInvite : Phase.PrivateCreateRooms }); + }} + />; + case Phase.PrivateInvite: + return this.setState({ phase: Phase.PrivateCreateRooms })} + />; + case Phase.PrivateCreateRooms: + return this.setState({ phase: Phase.Landing })} + />; + } + } + + render() { + const rightPanel = this.state.showRightPanel && this.state.phase === Phase.Landing + ? + : null; + + return
+ + + { this.renderBody() } + + +
; + } +} diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index 5b936e822c..9bc5b6476f 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React, {createRef} from 'react'; -import {_t} from "../../../languageHandler"; +import {_t, _td} from "../../../languageHandler"; import * as sdk from "../../../index"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {makeRoomPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks"; @@ -48,6 +48,7 @@ import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; export const KIND_DM = "dm"; export const KIND_INVITE = "invite"; +export const KIND_SPACE_INVITE = "space_invite"; export const KIND_CALL_TRANSFER = "call_transfer"; const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first @@ -309,7 +310,7 @@ interface IInviteDialogProps { // not provided. kind: string, - // The room ID this dialog is for. Only required for KIND_INVITE. + // The room ID this dialog is for. Only required for KIND_INVITE and KIND_SPACE_INVITE. roomId: string, // The call to transfer. Only required for KIND_CALL_TRANSFER. @@ -348,8 +349,8 @@ export default class InviteDialog extends React.PureComponent) or " + - "share this room.", - {}, - { - userId: () => - {userId}, - a: (sub) => - - {sub} - , - }, - ); - } else { - helpText = _t( - "Invite someone using their name, username (like ) or share this room.", - {}, - { - userId: () => - {userId}, - a: (sub) => - - {sub} - , - }, - ); + let helpTextUntranslated; + if (this.props.kind === KIND_INVITE) { + if (identityServersEnabled) { + helpTextUntranslated = _td("Invite someone using their name, email address, username " + + "(like ) or share this room."); + } else { + helpTextUntranslated = _td("Invite someone using their name, username " + + "(like ) or share this room."); + } + } else { // KIND_SPACE_INVITE + if (identityServersEnabled) { + helpTextUntranslated = _td("Invite someone using their name, email address, username " + + "(like ) or share this space."); + } else { + helpTextUntranslated = _td("Invite someone using their name, username " + + "(like ) or share this space."); + } } + helpText = _t(helpTextUntranslated, {}, { + userId: () => + {userId}, + a: (sub) => + {sub}, + }); + buttonText = _t("Invite"); goButtonFn = this._inviteUsers; } else if (this.props.kind === KIND_CALL_TRANSFER) { diff --git a/src/components/views/spaces/SpacePublicShare.tsx b/src/components/views/spaces/SpacePublicShare.tsx new file mode 100644 index 0000000000..064d1640a2 --- /dev/null +++ b/src/components/views/spaces/SpacePublicShare.tsx @@ -0,0 +1,65 @@ +/* +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 React, {useState} from "react"; +import {Room} from "matrix-js-sdk/src/models/room"; + +import {_t} from "../../../languageHandler"; +import AccessibleButton from "../elements/AccessibleButton"; +import {copyPlaintext} from "../../../utils/strings"; +import {sleep} from "../../../utils/promise"; +import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; +import {showSpaceInviteDialog} from "../../../RoomInvite"; + +interface IProps { + space: Room; + onFinished(): void; +} + +const SpacePublicShare = ({ space, onFinished }: IProps) => { + const [copiedText, setCopiedText] = useState(_t("Click to copy")); + + return
+ { + const permalinkCreator = new RoomPermalinkCreator(space); + permalinkCreator.load(); + const success = await copyPlaintext(permalinkCreator.forRoom()); + const text = success ? _t("Copied!") : _t("Failed to copy"); + setCopiedText(text); + await sleep(10); + if (copiedText === text) { // if the text hasn't changed by another click then clear it after some time + setCopiedText(_t("Click to copy")); + } + }} + > + { _t("Share invite link") } + { copiedText } + + { + showSpaceInviteDialog(space.roomId); + onFinished(); + }} + > + { _t("Invite by email or username") } + +
; +}; + +export default SpacePublicShare; diff --git a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts index 4126e8a669..430fad6145 100644 --- a/src/dispatcher/payloads/SetRightPanelPhasePayload.ts +++ b/src/dispatcher/payloads/SetRightPanelPhasePayload.ts @@ -15,6 +15,7 @@ limitations under the License. */ import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; +import { Room } from "matrix-js-sdk/src/models/room"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; import { ActionPayload } from "../payloads"; @@ -35,4 +36,5 @@ export interface SetRightPanelPhaseRefireParams { // XXX: The type for event should 'view_3pid_invite' action's payload event?: any; widgetId?: string; + space?: Room; } diff --git a/src/hooks/useStateArray.ts b/src/hooks/useStateArray.ts new file mode 100644 index 0000000000..e8ff6efff0 --- /dev/null +++ b/src/hooks/useStateArray.ts @@ -0,0 +1,29 @@ +/* +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 {useState} from "react"; + +// Hook to simplify managing state of arrays of a common type +export const useStateArray = (initialSize: number, initialState: T | T[]): [T[], (i: number, v: T) => void] => { + const [data, setData] = useState(() => { + return Array.isArray(initialState) ? initialState : new Array(initialSize).fill(initialState); + }); + return [data, (index: number, value: T) => setData(data => { + const copy = [...data]; + copy[index] = value; + return copy; + })] +}; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1b29e65b40..ae12b195a0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -998,6 +998,11 @@ "Expand space panel": "Expand space panel", "Collapse space panel": "Collapse space panel", "Home": "Home", + "Click to copy": "Click to copy", + "Copied!": "Copied!", + "Failed to copy": "Failed to copy", + "Share invite link": "Share invite link", + "Invite by email or username": "Invite by email or username", "Remove": "Remove", "This bridge was provisioned by .": "This bridge was provisioned by .", "This bridge is managed by .": "This bridge is managed by .", @@ -1814,8 +1819,6 @@ "%(senderDisplayName)s changed the room avatar to ": "%(senderDisplayName)s changed the room avatar to ", "Click here to see older messages.": "Click here to see older messages.", "This room is a continuation of another conversation.": "This room is a continuation of another conversation.", - "Copied!": "Copied!", - "Failed to copy": "Failed to copy", "Add an Integration": "Add an Integration", "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?", "Edited at %(date)s": "Edited at %(date)s", @@ -2164,8 +2167,11 @@ "Start a conversation with someone using their name or username (like ).": "Start a conversation with someone using their name or username (like ).", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here", "Go": "Go", + "Invite to this space": "Invite to this space", "Invite someone using their name, email address, username (like ) or share this room.": "Invite someone using their name, email address, username (like ) or share this room.", "Invite someone using their name, username (like ) or share this room.": "Invite someone using their name, username (like ) or share this room.", + "Invite someone using their name, email address, username (like ) or share this space.": "Invite someone using their name, email address, username (like ) or share this space.", + "Invite someone using their name, username (like ) or share this space.": "Invite someone using their name, username (like ) or share this space.", "Transfer": "Transfer", "a new master key signature": "a new master key signature", "a new cross-signing key signature": "a new cross-signing key signature", @@ -2550,6 +2556,37 @@ "Failed to reject invite": "Failed to reject invite", "You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.", "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", + "Accept Invite": "Accept Invite", + "%(count)s members|other": "%(count)s members", + "%(count)s members|one": "%(count)s member", + " invited you to ": " invited you to ", + "You have been invited to ": "You have been invited to ", + "Your public space ": "Your public space ", + "Your private space ": "Your private space ", + "Welcome to ": "Welcome to ", + "Random": "Random", + "Support": "Support", + "Room name": "Room name", + "Failed to create initial space rooms": "Failed to create initial space rooms", + "Skip for now": "Skip for now", + "Creating rooms...": "Creating rooms...", + "Share your public space": "Share your public space", + "At the moment only you can see it.": "At the moment only you can see it.", + "Finish": "Finish", + "Who are you working with?": "Who are you working with?", + "Ensure the right people have access to the space.": "Ensure the right people have access to the space.", + "Just Me": "Just Me", + "A private space just for you": "A private space just for you", + "Me and my teammates": "Me and my teammates", + "A private space for you and your teammates": "A private space for you and your teammates", + "Failed to invite the following users to your space: %(csvUsers)s": "Failed to invite the following users to your space: %(csvUsers)s", + "Invite your teammates": "Invite your teammates", + "Invite by username": "Invite by username", + "Inviting...": "Inviting...", + "What discussions do you want to have?": "What discussions do you want to have?", + "We'll create rooms for each topic.": "We'll create rooms for each topic.", + "What projects are you working on?": "What projects are you working on?", + "We'll create rooms for each of them. You can add existing rooms after setup.": "We'll create rooms for each of them. You can add existing rooms after setup.", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", "Tried to load a specific point in this room's timeline, but was unable to find it.": "Tried to load a specific point in this room's timeline, but was unable to find it.", "Failed to load timeline position": "Failed to load timeline position", diff --git a/src/stores/RightPanelStorePhases.ts b/src/stores/RightPanelStorePhases.ts index 11b51dfc2d..aea78c7460 100644 --- a/src/stores/RightPanelStorePhases.ts +++ b/src/stores/RightPanelStorePhases.ts @@ -31,6 +31,11 @@ export enum RightPanelPhases { GroupRoomList = 'GroupRoomList', GroupRoomInfo = 'GroupRoomInfo', GroupMemberInfo = 'GroupMemberInfo', + + // Space stuff + SpaceMemberList = "SpaceMemberList", + SpaceMemberInfo = "SpaceMemberInfo", + Space3pidMemberInfo = "Space3pidMemberInfo", } // These are the phases that are safe to persist (the ones that don't require additional @@ -43,3 +48,10 @@ export const RIGHT_PANEL_PHASES_NO_ARGS = [ RightPanelPhases.GroupMemberList, RightPanelPhases.GroupRoomList, ]; + +// Subset of phases visible in the Space View +export const RIGHT_PANEL_SPACE_PHASES = [ + RightPanelPhases.SpaceMemberList, + RightPanelPhases.Space3pidMemberInfo, + RightPanelPhases.SpaceMemberInfo, +]; diff --git a/src/utils/space.ts b/src/utils/space.ts new file mode 100644 index 0000000000..85faedf5d6 --- /dev/null +++ b/src/utils/space.ts @@ -0,0 +1,28 @@ +/* +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 {Room} from "matrix-js-sdk/src/models/room"; +import {MatrixClient} from "matrix-js-sdk/src/client"; +import {EventType} from "matrix-js-sdk/src/@types/event"; + +export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => { + const userId = cli.getUserId(); + return space.getMyMembership() === "join" + && (space.currentState.maySendStateEvent(EventType.RoomAvatar, userId) + || space.currentState.maySendStateEvent(EventType.RoomName, userId) + || space.currentState.maySendStateEvent(EventType.RoomTopic, userId) + || space.currentState.maySendStateEvent(EventType.RoomJoinRules, userId)); +}; From 1a7a0e619d72eb3c0ec3f3626ebd802195a27a07 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 1 Mar 2021 19:05:50 +0000 Subject: [PATCH 118/420] extend createRoom for creating rooms in a space --- src/createRoom.ts | 17 +++++++++++++++++ src/utils/permalinks/Permalinks.js | 16 ++++++++++++++-- src/utils/space.ts | 11 +++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/createRoom.ts b/src/createRoom.ts index 00a970eedc..a5343076ac 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -17,6 +17,7 @@ limitations under the License. import { MatrixClient } from "matrix-js-sdk/src/client"; import { Room } from "matrix-js-sdk/src/models/room"; +import { EventType } from "matrix-js-sdk/src/@types/event"; import { MatrixClientPeg } from './MatrixClientPeg'; import Modal from './Modal'; @@ -31,6 +32,8 @@ import GroupStore from "./stores/GroupStore"; import CountlyAnalytics from "./CountlyAnalytics"; import { isJoinedOrNearlyJoined } from "./utils/membership"; import { VIRTUAL_ROOM_EVENT_TYPE } from "./CallHandler"; +import SpaceStore from "./stores/SpaceStore"; +import { makeSpaceParentEvent } from "./utils/space"; // we define a number of interfaces which take their names from the js-sdk /* eslint-disable camelcase */ @@ -84,6 +87,7 @@ export interface IOpts { inlineErrors?: boolean; andView?: boolean; associatedWithCommunity?: string; + parentSpace?: Room; } /** @@ -175,6 +179,16 @@ export default function createRoom(opts: IOpts): Promise { }); } + if (opts.parentSpace) { + opts.createOpts.initial_state.push(makeSpaceParentEvent(opts.parentSpace, true)); + opts.createOpts.initial_state.push({ + type: EventType.RoomHistoryVisibility, + content: { + "history_visibility": opts.createOpts.preset === Preset.PublicChat ? "world_readable" : "invited", + }, + }); + } + let modal; if (opts.spinner) modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner'); @@ -189,6 +203,9 @@ export default function createRoom(opts: IOpts): Promise { return Promise.resolve(); } }).then(() => { + if (opts.parentSpace) { + return SpaceStore.instance.addRoomToSpace(opts.parentSpace, roomId, [client.getDomain()], true); + } if (opts.associatedWithCommunity) { return GroupStore.addRoomToGroup(opts.associatedWithCommunity, roomId, false); } diff --git a/src/utils/permalinks/Permalinks.js b/src/utils/permalinks/Permalinks.js index 086abc669d..bcf4d87136 100644 --- a/src/utils/permalinks/Permalinks.js +++ b/src/utils/permalinks/Permalinks.js @@ -14,9 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {MatrixClientPeg} from "../../MatrixClientPeg"; import isIp from "is-ip"; -import * as utils from 'matrix-js-sdk/src/utils'; +import * as utils from "matrix-js-sdk/src/utils"; +import {Room} from "matrix-js-sdk/src/models/room"; + +import {MatrixClientPeg} from "../../MatrixClientPeg"; import SpecPermalinkConstructor, {baseUrl as matrixtoBaseUrl} from "./SpecPermalinkConstructor"; import PermalinkConstructor, {PermalinkParts} from "./PermalinkConstructor"; import ElementPermalinkConstructor from "./ElementPermalinkConstructor"; @@ -121,6 +123,10 @@ export class RoomPermalinkCreator { this._started = false; } + get serverCandidates() { + return this._serverCandidates; + } + isStarted() { return this._started; } @@ -451,3 +457,9 @@ function isHostnameIpAddress(hostname) { return isIp(hostname); } + +export const calculateRoomVia = (room: Room) => { + const permalinkCreator = new RoomPermalinkCreator(room); + permalinkCreator.load(); + return permalinkCreator.serverCandidates; +}; diff --git a/src/utils/space.ts b/src/utils/space.ts index 85faedf5d6..98801cabd0 100644 --- a/src/utils/space.ts +++ b/src/utils/space.ts @@ -18,6 +18,8 @@ import {Room} from "matrix-js-sdk/src/models/room"; import {MatrixClient} from "matrix-js-sdk/src/client"; import {EventType} from "matrix-js-sdk/src/@types/event"; +import {calculateRoomVia} from "../utils/permalinks/Permalinks"; + export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => { const userId = cli.getUserId(); return space.getMyMembership() === "join" @@ -26,3 +28,12 @@ export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => { || space.currentState.maySendStateEvent(EventType.RoomTopic, userId) || space.currentState.maySendStateEvent(EventType.RoomJoinRules, userId)); }; + +export const makeSpaceParentEvent = (room: Room, canonical = false) => ({ + type: EventType.SpaceParent, + content: { + "via": calculateRoomVia(room), + "canonical": canonical, + }, + state_key: room.roomId, +}); From 9cec3828650490018e674bb4d79ebe97768ce3d4 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Sat, 27 Feb 2021 22:46:38 -0700 Subject: [PATCH 119/420] Change sending->sent state to match new designs For https://github.com/vector-im/element-web/issues/16424 --- res/css/views/rooms/_EventTile.scss | 39 ++++++++++++------- res/img/element-icons/circle-sending.svg | 3 ++ res/img/element-icons/circle-sent.svg | 4 ++ res/themes/dark/css/_dark.scss | 3 -- res/themes/legacy-dark/css/_legacy-dark.scss | 3 -- .../legacy-light/css/_legacy-light.scss | 2 - res/themes/light/css/_light.scss | 2 - .../views/messages/EditHistoryMessage.js | 1 + src/components/views/rooms/EventTile.js | 21 +++++++++- 9 files changed, 54 insertions(+), 24 deletions(-) create mode 100644 res/img/element-icons/circle-sending.svg create mode 100644 res/img/element-icons/circle-sent.svg diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 5841cf2853..028d9a7556 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -213,23 +213,36 @@ $left-gutter: 64px; color: $accent-fg-color; } -.mx_EventTile_encrypting { - color: $event-encrypting-color !important; -} - -.mx_EventTile_sending { - color: $event-sending-color; -} - -.mx_EventTile_sending .mx_UserPill, -.mx_EventTile_sending .mx_RoomPill { - opacity: 0.5; -} - .mx_EventTile_notSent { color: $event-notsent-color; } +.mx_EventTile_receiptSent, +.mx_EventTile_receiptSending { + // We don't use `position: relative` on the element because then it won't line + // up with the other read receipts + + &::before { + background-color: $tertiary-fg-color; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 14px; + width: 14px; + height: 14px; + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + } +} +.mx_EventTile_receiptSent::before { + mask-image: url('$(res)/img/element-icons/circle-sent.svg'); +} +.mx_EventTile_receiptSending::before { + mask-image: url('$(res)/img/element-icons/circle-sending.svg'); +} + .mx_EventTile_contextual { opacity: 0.4; } diff --git a/res/img/element-icons/circle-sending.svg b/res/img/element-icons/circle-sending.svg new file mode 100644 index 0000000000..2d15a0f716 --- /dev/null +++ b/res/img/element-icons/circle-sending.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/element-icons/circle-sent.svg b/res/img/element-icons/circle-sent.svg new file mode 100644 index 0000000000..04a00ceff7 --- /dev/null +++ b/res/img/element-icons/circle-sent.svg @@ -0,0 +1,4 @@ + + + + diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index a878aa3cdd..344f012d45 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -137,9 +137,6 @@ $panel-divider-color: transparent; $widget-menu-bar-bg-color: $header-panel-bg-color; $widget-body-bg-color: rgba(141, 151, 165, 0.2); -// event tile lifecycle -$event-sending-color: $text-secondary-color; - // event redaction $event-redacted-fg-color: #606060; $event-redacted-border-color: #000000; diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 3e3c299af9..ca3ead9ea8 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -132,9 +132,6 @@ $panel-divider-color: $header-panel-border-color; $widget-menu-bar-bg-color: $header-panel-bg-color; $widget-body-bg-color: #1A1D23; -// event tile lifecycle -$event-sending-color: $text-secondary-color; - // event redaction $event-redacted-fg-color: #606060; $event-redacted-border-color: #000000; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index a740ba155c..fa44c128d0 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -222,8 +222,6 @@ $widget-body-bg-color: #fff; $yellow-background: #fff8e3; // event tile lifecycle -$event-encrypting-color: #abddbc; -$event-sending-color: #ddd; $event-notsent-color: #f44; $event-highlight-fg-color: $warning-color; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 1c89d83c01..ca52d0dcfa 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -222,8 +222,6 @@ $widget-body-bg-color: #FFF; $yellow-background: #fff8e3; // event tile lifecycle -$event-encrypting-color: #abddbc; -$event-sending-color: #ddd; $event-notsent-color: #f44; $event-highlight-fg-color: $warning-color; diff --git a/src/components/views/messages/EditHistoryMessage.js b/src/components/views/messages/EditHistoryMessage.js index df27773a40..6c420a16fc 100644 --- a/src/components/views/messages/EditHistoryMessage.js +++ b/src/components/views/messages/EditHistoryMessage.js @@ -158,6 +158,7 @@ export default class EditHistoryMessage extends React.PureComponent { const isSending = (['sending', 'queued', 'encrypting'].indexOf(this.state.sendStatus) !== -1); const classes = classNames({ "mx_EventTile": true, + // Note: we keep these sending state classes for tests, not for our styles "mx_EventTile_sending": isSending, "mx_EventTile_notSent": this.state.sendStatus === 'not_sent', }); diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 87fb190678..9110316850 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -454,8 +454,26 @@ export default class EventTile extends React.Component { }; getReadAvatars() { - // return early if there are no read receipts + // return early if there are no read receipts, with our message state if applicable if (!this.props.readReceipts || this.props.readReceipts.length === 0) { + const room = this.context.getRoom(this.props.mxEvent.getRoomId()); + const myUserId = MatrixClientPeg.get().getUserId(); + if (this.props.mxEvent.getSender() === myUserId && room) { + // We only search for the most recent 50 events because surely someone will have + // sent *something* in that time, even if it is a membership event or something. + const readUsers = room.getUsersWhoHaveRead(this.props.mxEvent, 50); + const hasBeenRead = readUsers.length === 0 || readUsers.some(u => u !== myUserId); + console.log(room.getUsersReadUpTo(this.props.mxEvent)); + let receipt = null; + if (!this.props.eventSendStatus || this.props.eventSendStatus === 'sent') { + if (!hasBeenRead) { + receipt = ; + } + } else { + receipt = ; + } + return {receipt}; + } return (); } @@ -692,6 +710,7 @@ export default class EventTile extends React.Component { mx_EventTile_isEditing: isEditing, mx_EventTile_info: isInfoMessage, mx_EventTile_12hr: this.props.isTwelveHour, + // Note: we keep these sending state classes for tests, not for our styles mx_EventTile_encrypting: this.props.eventSendStatus === 'encrypting', mx_EventTile_sending: !isEditing && isSending, mx_EventTile_notSent: this.props.eventSendStatus === 'not_sent', From db8978580c80f86a91dd2783a2415065dc49e57b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 1 Mar 2021 16:21:04 -0700 Subject: [PATCH 120/420] Improve special read receipt checking See comments in code --- src/components/views/rooms/EventTile.js | 133 ++++++++++++++++++++---- 1 file changed, 114 insertions(+), 19 deletions(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 9110316850..01e932dd3a 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -264,6 +264,79 @@ export default class EventTile extends React.Component { this._tile = createRef(); this._replyThread = createRef(); + + // Throughout the component we manage a read receipt listener to see if our tile still + // qualifies for a "sent" or "sending" state (based on their relevant conditions). We + // don't want to over-subscribe to the read receipt events being fired, so we use a flag + // to determine if we've already subscribed and use a combination of other flags to find + // out if we should even be subscribed at all. + this._isListeningForReceipts = false; + } + + /** + * When true, the tile qualifies for some sort of special read receipt. This could be a 'sending' + * or 'sent' receipt, for example. + * @returns {boolean} + * @private + */ + get _isEligibleForSpecialReceipt() { + // First, if there are other read receipts then just short-circuit this. + if (this.props.readReceipts && this.props.readReceipts.length > 0) return false; + if (!this.props.mxEvent) return false; + + // Sanity check (should never happen, but we shouldn't explode if it does) + const room = this.context.getRoom(this.props.mxEvent.getRoomId()); + if (!room) return false; + + // Quickly check to see if the event was sent by us. If it wasn't, it won't qualify for + // special read receipts. + const myUserId = MatrixClientPeg.get().getUserId(); + if (this.props.mxEvent.getSender() !== myUserId) return false; + + // Finally, determine if the type is relevant to the user. This notably excludes state + // events and pretty much anything that can't be sent by the composer as a message. For + // those we rely on local echo giving the impression of things changing, and expect them + // to be quick. + const simpleSendableEvents = [EventType.Sticker, EventType.RoomMessage, EventType.RoomMessageEncrypted]; + if (!simpleSendableEvents.includes(this.props.mxEvent.getType())) return false; + + // Default case + return true; + } + + get _shouldShowSentReceipt() { + // If we're not even eligible, don't show the receipt. + if (!this._isEligibleForSpecialReceipt) return false; + + // Check to make sure the sending state is appropriate. A null/undefined send status means + // that the message is 'sent', so we're just double checking that it's explicitly not sent. + if (this.props.eventSendStatus && this.props.eventSendStatus !== 'sent') return false; + + // No point in doing the complex math if we're not going to even show this special receipt. + if (this._shouldShowSendingReceipt) return false; + + // Next we check to see if any newer events have read receipts. If they do then we don't + // show our special state - the user already has feedback about their message. We only + // search for the most recent 50 events because surely someone will have sent *something* + // in that time, even if it is a membership event or something. + const room = this.context.getRoom(this.props.mxEvent.getRoomId()); + const myUserId = MatrixClientPeg.get().getUserId(); + const readUsers = room.getUsersWhoHaveRead(this.props.mxEvent, 50); + const hasBeenRead = readUsers.length === 0 || readUsers.some(u => u !== myUserId); + return !hasBeenRead; + } + + get _shouldShowSendingReceipt() { + // If we're not even eligible, don't show the receipt. + if (!this._isEligibleForSpecialReceipt) return false; + + // Check the event send status to see if we are pending. Null/undefined status means the + // message was sent, so check for that and 'sent' explicitly. + if (!this.props.eventSendStatus || this.props.eventSendStatus === 'sent') return false; + + // Default to showing - there's no other event properties/behaviours we care about at + // this point. + return true; } // TODO: [REACT-WARNING] Move into constructor @@ -281,6 +354,11 @@ export default class EventTile extends React.Component { if (this.props.showReactions) { this.props.mxEvent.on("Event.relationsCreated", this._onReactionsCreated); } + + if (this._shouldShowSentReceipt || this._shouldShowSendingReceipt) { + client.on("Room.receipt", this._onRoomReceipt); + this._isListeningForReceipts = true; + } } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event @@ -305,12 +383,40 @@ export default class EventTile extends React.Component { const client = this.context; client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged); client.removeListener("userTrustStatusChanged", this.onUserVerificationChanged); + client.removeListener("Room.receipt", this._onRoomReceipt); + this._isListeningForReceipts = false; this.props.mxEvent.removeListener("Event.decrypted", this._onDecrypted); if (this.props.showReactions) { this.props.mxEvent.removeListener("Event.relationsCreated", this._onReactionsCreated); } } + componentDidUpdate(prevProps, prevState, snapshot) { + // If we're not listening for receipts and expect to be, register a listener. + if (!this._isListeningForReceipts && (this._shouldShowSentReceipt || this._shouldShowSendingReceipt)) { + this.context.on("Room.receipt", this._onRoomReceipt); + this._isListeningForReceipts = true; + } + } + + _onRoomReceipt = (ev, room) => { + // ignore events for other rooms + const tileRoom = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); + if (room !== tileRoom) return; + + if (!this._shouldShowSentReceipt && !this._shouldShowSendingReceipt && !this._isListeningForReceipts) { + return; + } + + this.forceUpdate(() => { + // Per elsewhere in this file, we can remove the listener once we will have no further purpose for it. + if (!this._shouldShowSentReceipt && !this._shouldShowSendingReceipt) { + this.context.removeListener("Room.receipt", this._onRoomReceipt); + this._isListeningForReceipts = false; + } + }); + }; + /** called when the event is decrypted after we show it. */ _onDecrypted = () => { @@ -454,26 +560,15 @@ export default class EventTile extends React.Component { }; getReadAvatars() { - // return early if there are no read receipts, with our message state if applicable + if (this._shouldShowSentReceipt) { + return ; + } + if (this._shouldShowSendingReceipt) { + return ; + } + + // return early if there are no read receipts if (!this.props.readReceipts || this.props.readReceipts.length === 0) { - const room = this.context.getRoom(this.props.mxEvent.getRoomId()); - const myUserId = MatrixClientPeg.get().getUserId(); - if (this.props.mxEvent.getSender() === myUserId && room) { - // We only search for the most recent 50 events because surely someone will have - // sent *something* in that time, even if it is a membership event or something. - const readUsers = room.getUsersWhoHaveRead(this.props.mxEvent, 50); - const hasBeenRead = readUsers.length === 0 || readUsers.some(u => u !== myUserId); - console.log(room.getUsersReadUpTo(this.props.mxEvent)); - let receipt = null; - if (!this.props.eventSendStatus || this.props.eventSendStatus === 'sent') { - if (!hasBeenRead) { - receipt = ; - } - } else { - receipt = ; - } - return {receipt}; - } return (); } From 6fcb4c7cd2ddf0a5fc64ff1796d079389a3d8482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 2 Mar 2021 07:37:00 +0100 Subject: [PATCH 121/420] Fix quote Co-authored-by: Travis Ralston --- .../views/settings/tabs/user/PreferencesUserSettingsTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js index 5e1c2e7288..ae9cad4cfa 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js @@ -48,7 +48,7 @@ export default class PreferencesUserSettingsTab extends React.Component { 'showRedactions', 'enableSyntaxHighlightLanguageDetection', 'expandCodeByDefault', - `scrollToBottomOnMessageSent`, + 'scrollToBottomOnMessageSent', 'showCodeLineNumbers', 'showJoinLeaves', 'showAvatarChanges', From ebedd3cbcbadaa8d945f430f5934ab7215e6b0c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 2 Mar 2021 07:41:14 +0100 Subject: [PATCH 122/420] Remove space Co-authored-by: Travis Ralston --- src/components/views/rooms/AuxPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index b3ef8c3cc8..9d19c212c4 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -154,7 +154,7 @@ export default class AuxPanel extends React.Component { fileDropTarget = (
{ _t("Drop file here to upload") } From ff00683f321a0369bc41836fdc55007c38dfd75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 2 Mar 2021 07:42:07 +0100 Subject: [PATCH 123/420] Use === Co-authored-by: Travis Ralston --- src/components/structures/RoomView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 5b79f23e0b..4a58e21820 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1159,7 +1159,7 @@ export default class RoomView extends React.Component { dragCounter: this.state.dragCounter - 1, }); - if (this.state.dragCounter == 0) { + if (this.state.dragCounter === 0) { this.setState({ draggingFile: false, }); From 8efe7dcaa11b2de16a4f77cb6d0cbaae0fb6d3bc Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 09:51:11 +0000 Subject: [PATCH 124/420] Decorate Right Panel cards with Space header for when viewing it in that context --- res/css/structures/_RightPanel.scss | 17 ++++++ res/css/views/rooms/_MemberInfo.scss | 1 + res/css/views/rooms/_MemberList.scss | 4 ++ src/components/views/right_panel/UserInfo.tsx | 57 +++++++++++++------ src/components/views/rooms/MemberList.js | 22 ++++++- .../views/rooms/ThirdPartyMemberInfo.js | 19 +++++-- src/i18n/strings/en_EN.json | 3 +- 7 files changed, 98 insertions(+), 25 deletions(-) diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index 5bf0d953f3..5515fe4060 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -160,3 +160,20 @@ limitations under the License. mask-position: center; } } + +.mx_RightPanel_scopeHeader { + margin: 24px; + text-align: center; + font-weight: $font-semi-bold; + font-size: $font-18px; + line-height: $font-22px; + + .mx_BaseAvatar { + margin-right: 8px; + vertical-align: middle; + } + + .mx_BaseAvatar_image { + border-radius: 8px; + } +} diff --git a/res/css/views/rooms/_MemberInfo.scss b/res/css/views/rooms/_MemberInfo.scss index 182c280217..3f7f83d334 100644 --- a/res/css/views/rooms/_MemberInfo.scss +++ b/res/css/views/rooms/_MemberInfo.scss @@ -19,6 +19,7 @@ limitations under the License. flex-direction: column; flex: 1; overflow-y: auto; + margin-top: 8px; } .mx_MemberInfo_name { diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index 1e3506e371..631ddc484f 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -44,6 +44,10 @@ limitations under the License. .mx_AutoHideScrollbar { flex: 1 1 0; } + + .mx_RightPanel_scopeHeader { + margin-top: -8px; + } } .mx_GroupMemberList_query, diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index a4b5cd0fbb..eb47a56269 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -60,7 +60,9 @@ import QuestionDialog from "../dialogs/QuestionDialog"; import ConfirmUserActionDialog from "../dialogs/ConfirmUserActionDialog"; import InfoDialog from "../dialogs/InfoDialog"; import { EventType } from "matrix-js-sdk/src/@types/event"; -import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import RoomAvatar from "../avatars/RoomAvatar"; +import RoomName from "../elements/RoomName"; interface IDevice { deviceId: string; @@ -302,7 +304,8 @@ const UserOptionsSection: React.FC<{ member: RoomMember; isIgnored: boolean; canInvite: boolean; -}> = ({member, isIgnored, canInvite}) => { + isSpace?: boolean; +}> = ({member, isIgnored, canInvite, isSpace}) => { const cli = useContext(MatrixClientContext); let ignoreButton = null; @@ -342,7 +345,7 @@ const UserOptionsSection: React.FC<{ ); - if (member.roomId) { + if (member.roomId && !isSpace) { const onReadReceiptButton = function() { const room = cli.getRoom(member.roomId); dis.dispatch({ @@ -434,14 +437,18 @@ const UserOptionsSection: React.FC<{ ); }; -const warnSelfDemote = async () => { +const warnSelfDemote = async (isSpace) => { const {finished} = Modal.createTrackedDialog('Demoting Self', '', QuestionDialog, { title: _t("Demote yourself?"), description:
- { _t("You will not be able to undo this change as you are demoting yourself, " + - "if you are the last privileged user in the room it will be impossible " + - "to regain privileges.") } + { isSpace + ? _t("You will not be able to undo this change as you are demoting yourself, " + + "if you are the last privileged user in the space it will be impossible " + + "to regain privileges.") + : _t("You will not be able to undo this change as you are demoting yourself, " + + "if you are the last privileged user in the room it will be impossible " + + "to regain privileges.") }
, button: _t("Demote"), }); @@ -717,7 +724,7 @@ const MuteToggleButton: React.FC = ({member, room, powerLevels, // if muting self, warn as it may be irreversible if (target === cli.getUserId()) { try { - if (!(await warnSelfDemote())) return; + if (!(await warnSelfDemote(room?.isSpaceRoom()))) return; } catch (e) { console.error("Failed to warn about self demotion: ", e); return; @@ -806,7 +813,7 @@ const RoomAdminToolsContainer: React.FC = ({ if (canAffectUser && me.powerLevel >= kickPowerLevel) { kickButton = ; } - if (me.powerLevel >= redactPowerLevel) { + if (me.powerLevel >= redactPowerLevel && !room.isSpaceRoom()) { redactButton = ( ); @@ -1085,7 +1092,7 @@ const PowerLevelEditor: React.FC<{ } else if (myUserId === target) { // If we are changing our own PL it can only ever be decreasing, which we cannot reverse. try { - if (!(await warnSelfDemote())) return; + if (!(await warnSelfDemote(room?.isSpaceRoom()))) return; } catch (e) { console.error("Failed to warn about self demotion: ", e); } @@ -1315,12 +1322,10 @@ const BasicUserInfo: React.FC<{ if (!isRoomEncrypted) { if (!cryptoEnabled) { text = _t("This client does not support end-to-end encryption."); - } else if (room) { + } else if (room && !room.isSpaceRoom()) { text = _t("Messages in this room are not end-to-end encrypted."); - } else { - // TODO what to render for GroupMember } - } else { + } else if (!room.isSpaceRoom()) { text = _t("Messages in this room are end-to-end encrypted."); } @@ -1381,7 +1386,9 @@ const BasicUserInfo: React.FC<{ + member={member} + isSpace={room?.isSpaceRoom()} + /> { adminToolsContainer } @@ -1498,7 +1505,7 @@ interface IProps { user: Member; groupId?: string; room?: Room; - phase: RightPanelPhases.RoomMemberInfo | RightPanelPhases.GroupMemberInfo; + phase: RightPanelPhases.RoomMemberInfo | RightPanelPhases.GroupMemberInfo | RightPanelPhases.SpaceMemberInfo; onClose(): void; } @@ -1542,7 +1549,9 @@ const UserInfo: React.FC = ({ previousPhase = RightPanelPhases.RoomMemberInfo; refireParams = {member: member}; } else if (room) { - previousPhase = RightPanelPhases.RoomMemberList; + previousPhase = previousPhase = room.isSpaceRoom() + ? RightPanelPhases.SpaceMemberList + : RightPanelPhases.RoomMemberList; } const onEncryptionPanelClose = () => { @@ -1557,6 +1566,7 @@ const UserInfo: React.FC = ({ switch (phase) { case RightPanelPhases.RoomMemberInfo: case RightPanelPhases.GroupMemberInfo: + case RightPanelPhases.SpaceMemberInfo: content = ( = ({ } } - const header = ; + let scopeHeader; + if (room?.isSpaceRoom()) { + scopeHeader =
+ + +
; + } + + const header = + { scopeHeader } + + ; return ); + let previousPhase = RightPanelPhases.RoomSummary; + // We have no previousPhase for when viewing a MemberList from a Space + let scopeHeader; + if (room?.isSpaceRoom()) { + previousPhase = undefined; + scopeHeader =
+ + +
; + } + return + { scopeHeader } + { inviteButton } + } footer={footer} onClose={this.props.onClose} - previousPhase={RightPanelPhases.RoomSummary} + previousPhase={previousPhase} >
+ + +
; + } + // We shamelessly rip off the MemberInfo styles here. return (
+ { scopeHeader }
).": "Start a conversation with someone using their name or username (like ).", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here", "Go": "Go", - "Invite to this space": "Invite to this space", "Invite someone using their name, email address, username (like ) or share this room.": "Invite someone using their name, email address, username (like ) or share this room.", "Invite someone using their name, username (like ) or share this room.": "Invite someone using their name, username (like ) or share this room.", "Invite someone using their name, email address, username (like ) or share this space.": "Invite someone using their name, email address, username (like ) or share this space.", From dfd0aaffe348e85c32b13b702e6a7cf0feb048c5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 09:55:55 +0000 Subject: [PATCH 125/420] Iterate copy for some global warning prompts for spaces --- src/components/structures/MatrixChat.tsx | 14 ++++++++++---- src/i18n/strings/en_EN.json | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 8e3d3e6b5f..d9d8b659c9 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1072,6 +1072,7 @@ export default class MatrixChat extends React.PureComponent { private leaveRoomWarnings(roomId: string) { const roomToLeave = MatrixClientPeg.get().getRoom(roomId); + const isSpace = roomToLeave?.isSpaceRoom(); // Show a warning if there are additional complications. const joinRules = roomToLeave.currentState.getStateEvents('m.room.join_rules', ''); const warnings = []; @@ -1081,7 +1082,9 @@ export default class MatrixChat extends React.PureComponent { warnings.push(( {' '/* Whitespace, otherwise the sentences get smashed together */ } - { _t("This room is not public. You will not be able to rejoin without an invite.") } + { isSpace + ? _t("This space is not public. You will not be able to rejoin without an invite.") + : _t("This room is not public. You will not be able to rejoin without an invite.") } )); } @@ -1094,11 +1097,14 @@ export default class MatrixChat extends React.PureComponent { const roomToLeave = MatrixClientPeg.get().getRoom(roomId); const warnings = this.leaveRoomWarnings(roomId); - Modal.createTrackedDialog('Leave room', '', QuestionDialog, { - title: _t("Leave room"), + const isSpace = roomToLeave?.isSpaceRoom(); + Modal.createTrackedDialog(isSpace ? "Leave space" : "Leave room", '', QuestionDialog, { + title: isSpace ? _t("Leave space") : _t("Leave room"), description: ( - { _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) } + { isSpace + ? _t("Are you sure you want to leave the space '%(spaceName)s'?", {spaceName: roomToLeave.name}) + : _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) } { warnings } ), diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 472fd9b1e7..6603a83496 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2493,7 +2493,10 @@ "Failed to reject invitation": "Failed to reject invitation", "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 space is not public. You will not be able to rejoin without an invite.": "This space 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.": "This room is not public. You will not be able to rejoin without an invite.", + "Leave space": "Leave space", + "Are you sure you want to leave the space '%(spaceName)s'?": "Are you sure you want to leave the space '%(spaceName)s'?", "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 926e226a784d5bd66dee9f788618c060cb693e46 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 10:07:43 +0000 Subject: [PATCH 126/420] Add Invite CTA to Space View --- res/css/structures/_SpaceRoomView.scss | 54 +++++++++++++++++++ src/RoomInvite.js | 14 ++--- src/components/structures/SpaceRoomView.tsx | 18 ++++++- .../views/spaces/SpacePublicShare.tsx | 4 +- src/i18n/strings/en_EN.json | 1 + 5 files changed, 78 insertions(+), 13 deletions(-) diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index 559f405e59..946856eed3 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -135,6 +135,60 @@ $SpaceRoomViewInnerWidth: 428px; padding: 8px 22px; } } + + .mx_SpaceRoomView_landing_adminButtons { + margin-top: 32px; + + .mx_AccessibleButton { + position: relative; + width: 160px; + height: 124px; + box-sizing: border-box; + padding: 72px 16px 0; + border-radius: 12px; + border: 1px solid $space-button-outline-color; + margin-right: 28px; + margin-bottom: 28px; + font-size: $font-14px; + display: inline-block; + vertical-align: bottom; + + &:last-child { + margin-right: 0; + } + + &:hover { + background-color: rgba(141, 151, 165, 0.1); + } + + &::before, &::after { + position: absolute; + content: ""; + left: 16px; + top: 16px; + height: 40px; + width: 40px; + border-radius: 20px; + } + + &::after { + mask-position: center; + mask-size: 30px; + mask-repeat: no-repeat; + background: #ffffff; // white icon fill + } + + &.mx_SpaceRoomView_landing_inviteButton { + &::before { + background-color: $accent-color; + } + + &::after { + mask-image: url('$(res)/img/element-icons/room/invite.svg'); + } + } + } + } } .mx_SpaceRoomView_privateScope { diff --git a/src/RoomInvite.js b/src/RoomInvite.js index 728ae11e79..503411d2b3 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -50,10 +50,13 @@ export function showStartChatInviteDialog(initialText) { } export function showRoomInviteDialog(roomId) { + const isSpace = MatrixClientPeg.get()?.getRoom(roomId)?.isSpaceRoom(); // This dialog handles the room creation internally - we don't need to worry about it. - const InviteDialog = sdk.getComponent("dialogs.InviteDialog"); Modal.createTrackedDialog( - 'Invite Users', '', InviteDialog, {kind: KIND_INVITE, roomId}, + "Invite Users", isSpace ? "Space" : "Room", InviteDialog, { + kind: isSpace ? KIND_SPACE_INVITE : KIND_INVITE, + roomId, + }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, ); } @@ -75,13 +78,6 @@ export function showCommunityInviteDialog(communityId) { } } -export const showSpaceInviteDialog = (roomId) => { - Modal.createTrackedDialog("Invite Users", "Space", InviteDialog, { - kind: KIND_SPACE_INVITE, - roomId, - }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); -}; - /** * Checks if the given MatrixEvent is a valid 3rd party user invite. * @param {MatrixEvent} event The event to check diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 6c64df31eb..9e73b97d5a 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -25,7 +25,7 @@ import AccessibleButton from "../views/elements/AccessibleButton"; import RoomName from "../views/elements/RoomName"; import RoomTopic from "../views/elements/RoomTopic"; import FormButton from "../views/elements/FormButton"; -import {inviteMultipleToRoom, showSpaceInviteDialog} from "../../RoomInvite"; +import {inviteMultipleToRoom, showRoomInviteDialog} from "../../RoomInvite"; import {useRoomMembers} from "../../hooks/useRoomMembers"; import createRoom, {IOpts, Preset} from "../../createRoom"; import Field from "../views/elements/Field"; @@ -108,6 +108,17 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
; } + let inviteButton; + if (myMembership === "join" && space.canInvite(userId)) { + inviteButton = ( + { + showRoomInviteDialog(space.roomId); + }}> + { _t("Invite people") } + + ); + } + return
@@ -167,6 +178,9 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
{ joinButtons } +
+ { inviteButton } +
; }; @@ -361,7 +375,7 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
showSpaceInviteDialog(space.roomId)} + onClick={() => showRoomInviteDialog(space.roomId)} > { _t("Invite by username") } diff --git a/src/components/views/spaces/SpacePublicShare.tsx b/src/components/views/spaces/SpacePublicShare.tsx index 064d1640a2..3930c1db16 100644 --- a/src/components/views/spaces/SpacePublicShare.tsx +++ b/src/components/views/spaces/SpacePublicShare.tsx @@ -22,7 +22,7 @@ import AccessibleButton from "../elements/AccessibleButton"; import {copyPlaintext} from "../../../utils/strings"; import {sleep} from "../../../utils/promise"; import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; -import {showSpaceInviteDialog} from "../../../RoomInvite"; +import {showRoomInviteDialog} from "../../../RoomInvite"; interface IProps { space: Room; @@ -53,7 +53,7 @@ const SpacePublicShare = ({ space, onFinished }: IProps) => { { - showSpaceInviteDialog(space.roomId); + showRoomInviteDialog(space.roomId); onFinished(); }} > diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6603a83496..5f3d293571 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2561,6 +2561,7 @@ "You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.", "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", "Accept Invite": "Accept Invite", + "Invite people": "Invite people", "%(count)s members|other": "%(count)s members", "%(count)s members|one": "%(count)s member", " invited you to ": " invited you to ", From 0a4c0b69b0fa250fa01c3ff1a88ba272dc5ef64e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 2 Mar 2021 12:07:33 +0100 Subject: [PATCH 127/420] Move fileDropTarget to RoomView 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 | 17 +++++++++++++++-- src/components/views/rooms/AuxPanel.tsx | 17 ----------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 4a58e21820..af7b8ee704 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1782,6 +1782,19 @@ export default class RoomView extends React.Component { } } + let fileDropTarget = null; + if (this.state.draggingFile) { + fileDropTarget = ( +
+ + { _t("Drop file here to upload") } +
+ ); + } + // We have successfully loaded this room, and are not previewing. // Display the "normal" room view. @@ -1893,7 +1906,6 @@ export default class RoomView extends React.Component { room={this.state.room} fullHeight={false} userId={this.context.credentials.userId} - draggingFile={this.state.draggingFile} maxHeight={this.state.auxPanelMaxHeight} showApps={this.state.showApps} onResize={this.onResize} @@ -2059,8 +2071,9 @@ export default class RoomView extends React.Component { />
+ {auxPanel}
- {auxPanel} + {fileDropTarget} {topUnreadMessagesBar} {jumpToBottom} {messagePanel} diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 9d19c212c4..7aa7be42b6 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -35,9 +35,6 @@ interface IProps { userId: string, showApps: boolean, // Render apps - // set to true to show the file drop target - draggingFile: boolean, - // maxHeight attribute for the aux panel and the video // therein maxHeight: number, @@ -149,19 +146,6 @@ export default class AuxPanel extends React.Component { } render() { - let fileDropTarget = null; - if (this.props.draggingFile) { - fileDropTarget = ( -
- - { _t("Drop file here to upload") } -
- ); - } - const callView = ( { { stateViews } { appsDrawer } - { fileDropTarget } { callView } { this.props.children } From 4476843264eeb61380662eb0040a30edfec42c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 2 Mar 2021 12:12:10 +0100 Subject: [PATCH 128/420] Remove unused _t MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/AuxPanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 7aa7be42b6..c9821d51e3 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -20,7 +20,6 @@ import { Room } from 'matrix-js-sdk/src/models/room' import dis from "../../../dispatcher/dispatcher"; import * as ObjectUtils from '../../../ObjectUtils'; import AppsDrawer from './AppsDrawer'; -import { _t } from '../../../languageHandler'; import classNames from 'classnames'; import RateLimitedFunc from '../../../ratelimitedfunc'; import SettingsStore from "../../../settings/SettingsStore"; From 831cc7eaa0ecd57a25cff40daf582b02e3fd4e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 2 Mar 2021 12:14:36 +0100 Subject: [PATCH 129/420] 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 9af8ccc172..5d2c70be03 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1381,7 +1381,6 @@ "Remove %(phone)s?": "Remove %(phone)s?", "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.", "Phone Number": "Phone Number", - "Drop file here to upload": "Drop file here to upload", "This user has not verified all of their sessions.": "This user has not verified all of their sessions.", "You have not verified this user.": "You have not verified this user.", "You have verified this user. This user has verified all of their sessions.": "You have verified this user. This user has verified all of their sessions.", @@ -2513,6 +2512,7 @@ "No more results": "No more results", "Room": "Room", "Failed to reject invite": "Failed to reject invite", + "Drop file here to upload": "Drop file here to upload", "You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.", "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.": "Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.", From ab4b7b73ea7f8eeb18be6ab7491e2e1649b35969 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 10:34:28 +0000 Subject: [PATCH 130/420] Add a basic Space Settings view --- res/css/_components.scss | 1 + res/css/structures/_SpaceRoomView.scss | 10 ++ .../views/dialogs/_SpaceSettingsDialog.scss | 55 ++++++ src/components/structures/MatrixChat.tsx | 4 + src/components/structures/SpaceRoomView.tsx | 12 +- .../views/dialogs/SpaceSettingsDialog.tsx | 162 ++++++++++++++++++ src/i18n/strings/en_EN.json | 8 + src/utils/space.ts | 9 + 8 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 res/css/views/dialogs/_SpaceSettingsDialog.scss create mode 100644 src/components/views/dialogs/SpaceSettingsDialog.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index ca66aa60ec..db73eed3f2 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -91,6 +91,7 @@ @import "./views/dialogs/_SettingsDialog.scss"; @import "./views/dialogs/_ShareDialog.scss"; @import "./views/dialogs/_SlashCommandHelpDialog.scss"; +@import "./views/dialogs/_SpaceSettingsDialog.scss"; @import "./views/dialogs/_TabbedIntegrationManagerDialog.scss"; @import "./views/dialogs/_TermsDialog.scss"; @import "./views/dialogs/_UploadConfirmDialog.scss"; diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index 946856eed3..0a42db130a 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -187,6 +187,16 @@ $SpaceRoomViewInnerWidth: 428px; mask-image: url('$(res)/img/element-icons/room/invite.svg'); } } + + &.mx_SpaceRoomView_landing_settingsButton { + &::before { + background-color: #5c56f5; + } + + &::after { + mask-image: url('$(res)/img/element-icons/settings.svg'); + } + } } } } diff --git a/res/css/views/dialogs/_SpaceSettingsDialog.scss b/res/css/views/dialogs/_SpaceSettingsDialog.scss new file mode 100644 index 0000000000..c1fa539e9b --- /dev/null +++ b/res/css/views/dialogs/_SpaceSettingsDialog.scss @@ -0,0 +1,55 @@ +/* +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. +*/ + +.mx_SpaceSettingsDialog { + width: 480px; + color: $primary-fg-color; + + .mx_SpaceSettings_errorText { + font-weight: $font-semi-bold; + font-size: $font-12px; + line-height: $font-15px; + color: $notice-primary-color; + margin-bottom: 28px; + } + + .mx_ToggleSwitch { + display: inline-block; + vertical-align: middle; + margin-left: 16px; + } + + .mx_AccessibleButton_kind_danger { + margin-top: 28px; + } + + .mx_SpaceSettingsDialog_buttons { + display: flex; + margin-top: 64px; + + .mx_AccessibleButton { + display: inline-block; + } + + .mx_AccessibleButton_kind_link { + margin-left: auto; + } + } + + .mx_FormButton { + padding: 8px 22px; + } +} diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index d9d8b659c9..83b3565738 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1118,6 +1118,10 @@ export default class MatrixChat extends React.PureComponent { const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner'); d.finally(() => modal.close()); + dis.dispatch({ + action: "after_leave_room", + room_id: roomId, + }); } }, }); diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 9e73b97d5a..49af14017e 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -46,7 +46,7 @@ import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanelPhasePayload"; import {useStateArray} from "../../hooks/useStateArray"; import SpacePublicShare from "../views/spaces/SpacePublicShare"; -import {shouldShowSpaceSettings} from "../../utils/space"; +import {shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space"; import MemberAvatar from "../views/avatars/MemberAvatar"; interface IProps { @@ -119,6 +119,15 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => ); } + let settingsButton; + if (shouldShowSpaceSettings(cli, space)) { + settingsButton = { + showSpaceSettings(cli, space); + }}> + { _t("Settings") } + ; + } + return
@@ -180,6 +189,7 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => { joinButtons }
{ inviteButton } + { settingsButton }
; }; diff --git a/src/components/views/dialogs/SpaceSettingsDialog.tsx b/src/components/views/dialogs/SpaceSettingsDialog.tsx new file mode 100644 index 0000000000..f6bf5b87e6 --- /dev/null +++ b/src/components/views/dialogs/SpaceSettingsDialog.tsx @@ -0,0 +1,162 @@ +/* +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 React, {useState} from 'react'; +import {Room} from "matrix-js-sdk/src/models/room"; +import {MatrixClient} from "matrix-js-sdk/src/client"; +import {EventType} from "matrix-js-sdk/src/@types/event"; + +import {_t} from '../../../languageHandler'; +import {IDialogProps} from "./IDialogProps"; +import BaseDialog from "./BaseDialog"; +import DevtoolsDialog from "./DevtoolsDialog"; +import SpaceBasicSettings from '../spaces/SpaceBasicSettings'; +import {getTopic} from "../elements/RoomTopic"; +import {avatarUrlForRoom} from "../../../Avatar"; +import ToggleSwitch from "../elements/ToggleSwitch"; +import AccessibleButton from "../elements/AccessibleButton"; +import FormButton from "../elements/FormButton"; +import Modal from "../../../Modal"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import {allSettled} from "../../../utils/promise"; +import {useDispatcher} from "../../../hooks/useDispatcher"; + +interface IProps extends IDialogProps { + matrixClient: MatrixClient; + space: Room; +} + +const SpaceSettingsDialog: React.FC = ({ matrixClient: cli, space, onFinished }) => { + useDispatcher(defaultDispatcher, ({action, ...params}) => { + if (action === "after_leave_room" && params.room_id === space.roomId) { + onFinished(false); + } + }); + + const [busy, setBusy] = useState(false); + const [error, setError] = useState(""); + + const userId = cli.getUserId(); + + const [newAvatar, setNewAvatar] = useState(null); // undefined means to remove avatar + const canSetAvatar = space.currentState.maySendStateEvent(EventType.RoomAvatar, userId); + const avatarChanged = newAvatar !== null; + + const [name, setName] = useState(space.name); + const canSetName = space.currentState.maySendStateEvent(EventType.RoomName, userId); + const nameChanged = name !== space.name; + + const currentTopic = getTopic(space); + const [topic, setTopic] = useState(currentTopic); + const canSetTopic = space.currentState.maySendStateEvent(EventType.RoomTopic, userId); + const topicChanged = topic !== currentTopic; + + const currentJoinRule = space.getJoinRule(); + const [joinRule, setJoinRule] = useState(currentJoinRule); + const canSetJoinRule = space.currentState.maySendStateEvent(EventType.RoomJoinRules, userId); + const joinRuleChanged = joinRule !== currentJoinRule; + + const onSave = async () => { + setBusy(true); + const promises = []; + + if (avatarChanged) { + promises.push(cli.sendStateEvent(space.roomId, EventType.RoomAvatar, { + url: await cli.uploadContent(newAvatar), + }, "")); + } + + if (nameChanged) { + promises.push(cli.setRoomName(space.roomId, name)); + } + + if (topicChanged) { + promises.push(cli.setRoomTopic(space.roomId, topic)); + } + + if (joinRuleChanged) { + promises.push(cli.sendStateEvent(space.roomId, EventType.RoomJoinRules, { join_rule: joinRule }, "")); + } + + const results = await allSettled(promises); + setBusy(false); + const failures = results.filter(r => r.status === "rejected"); + if (failures.length > 0) { + console.error("Failed to save space settings: ", failures); + setError(_t("Failed to save space settings.")); + } + }; + + return +
+
{ _t("Edit settings relating to your space.") }
+ + { error &&
{ error }
} + + + +
+ { _t("Make this space private") } + setJoinRule(checked ? "private" : "invite")} + disabled={!canSetJoinRule} + aria-label={_t("Make this space private")} + /> +
+ + { + defaultDispatcher.dispatch({ + action: "leave_room", + room_id: space.roomId, + }); + }} + /> + +
+ Modal.createDialog(DevtoolsDialog, {roomId: space.roomId})}> + { _t("View dev tools") } + + + { _t("Cancel") } + + +
+
+
; +}; + +export default SpaceSettingsDialog; + diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5f3d293571..cd2fcf1117 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2282,6 +2282,14 @@ "Link to selected message": "Link to selected message", "Copy": "Copy", "Command Help": "Command Help", + "Failed to save space settings.": "Failed to save space settings.", + "Space settings": "Space settings", + "Edit settings relating to your space.": "Edit settings relating to your space.", + "Make this space private": "Make this space private", + "Leave Space": "Leave Space", + "View dev tools": "View dev tools", + "Saving...": "Saving...", + "Save Changes": "Save Changes", "To help us prevent this in future, please send us logs.": "To help us prevent this in future, please send us logs.", "Missing session data": "Missing session data", "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.", diff --git a/src/utils/space.ts b/src/utils/space.ts index 98801cabd0..2ee4d0071e 100644 --- a/src/utils/space.ts +++ b/src/utils/space.ts @@ -19,6 +19,8 @@ import {MatrixClient} from "matrix-js-sdk/src/client"; import {EventType} from "matrix-js-sdk/src/@types/event"; import {calculateRoomVia} from "../utils/permalinks/Permalinks"; +import Modal from "../Modal"; +import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog"; export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => { const userId = cli.getUserId(); @@ -37,3 +39,10 @@ export const makeSpaceParentEvent = (room: Room, canonical = false) => ({ }, state_key: room.roomId, }); + +export const showSpaceSettings = (cli: MatrixClient, space: Room) => { + Modal.createTrackedDialog("Space Settings", "", SpaceSettingsDialog, { + matrixClient: cli, + space, + }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); +}; From a687b9883ca42c927ec89d20ff066620a10b17ed Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 13:28:05 +0000 Subject: [PATCH 131/420] Add a create room in space CTA to Space View --- res/css/structures/_SpaceRoomView.scss | 10 ++++++++++ src/components/structures/SpaceRoomView.tsx | 16 +++++++++++++++- .../views/dialogs/CreateRoomDialog.js | 7 +++++++ src/i18n/strings/en_EN.json | 1 + src/utils/space.ts | 18 ++++++++++++++++++ 5 files changed, 51 insertions(+), 1 deletion(-) diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index 0a42db130a..eaaaa2f797 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -188,6 +188,16 @@ $SpaceRoomViewInnerWidth: 428px; } } + &.mx_SpaceRoomView_landing_createButton { + &::before { + background-color: #368bd6; + } + + &::after { + mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); + } + } + &.mx_SpaceRoomView_landing_settingsButton { &::before { background-color: #5c56f5; diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 49af14017e..4159a38cfe 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -46,7 +46,7 @@ import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanelPhasePayload"; import {useStateArray} from "../../hooks/useStateArray"; import SpacePublicShare from "../views/spaces/SpacePublicShare"; -import {shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space"; +import {showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space"; import MemberAvatar from "../views/avatars/MemberAvatar"; interface IProps { @@ -119,6 +119,19 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => ); } + const canAddRooms = myMembership === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId); + + let addRoomButtons; + if (canAddRooms) { + addRoomButtons = + { + showCreateNewRoom(cli, space); + }}> + { _t("Create a new room") } + + ; + } + let settingsButton; if (shouldShowSpaceSettings(cli, space)) { settingsButton = { @@ -189,6 +202,7 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => { joinButtons }
{ inviteButton } + { addRoomButtons } { settingsButton }
; diff --git a/src/components/views/dialogs/CreateRoomDialog.js b/src/components/views/dialogs/CreateRoomDialog.js index 2b6bb5e187..0771b0ec45 100644 --- a/src/components/views/dialogs/CreateRoomDialog.js +++ b/src/components/views/dialogs/CreateRoomDialog.js @@ -17,6 +17,8 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; +import {Room} from "matrix-js-sdk/src/models/room"; + import * as sdk from '../../../index'; import SdkConfig from '../../../SdkConfig'; import withValidation from '../elements/Validation'; @@ -30,6 +32,7 @@ export default class CreateRoomDialog extends React.Component { static propTypes = { onFinished: PropTypes.func.isRequired, defaultPublic: PropTypes.bool, + parentSpace: PropTypes.instanceOf(Room), }; constructor(props) { @@ -85,6 +88,10 @@ export default class CreateRoomDialog extends React.Component { opts.associatedWithCommunity = CommunityPrototypeStore.instance.getSelectedCommunityId(); } + if (this.props.parentSpace) { + opts.parentSpace = this.props.parentSpace; + } + return opts; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index cd2fcf1117..aeef76bf22 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2570,6 +2570,7 @@ "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", "Accept Invite": "Accept Invite", "Invite people": "Invite people", + "Create a new room": "Create a new room", "%(count)s members|other": "%(count)s members", "%(count)s members|one": "%(count)s member", " invited you to ": " invited you to ", diff --git a/src/utils/space.ts b/src/utils/space.ts index 2ee4d0071e..c995b860ee 100644 --- a/src/utils/space.ts +++ b/src/utils/space.ts @@ -21,6 +21,8 @@ import {EventType} from "matrix-js-sdk/src/@types/event"; import {calculateRoomVia} from "../utils/permalinks/Permalinks"; import Modal from "../Modal"; import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog"; +import CreateRoomDialog from "../components/views/dialogs/CreateRoomDialog"; +import createRoom, {IOpts} from "../createRoom"; export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => { const userId = cli.getUserId(); @@ -46,3 +48,19 @@ export const showSpaceSettings = (cli: MatrixClient, space: Room) => { space, }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); }; + +export const showCreateNewRoom = async (cli: MatrixClient, space: Room) => { + const modal = Modal.createTrackedDialog<[boolean, IOpts]>( + "Space Landing", + "Create Room", + CreateRoomDialog, + { + defaultPublic: space.getJoinRule() === "public", + parentSpace: space, + }, + ); + const [shouldCreate, opts] = await modal.finished; + if (shouldCreate) { + await createRoom(opts); + } +}; From e479edd47a9849a5cf12523b82eba92f1c9fe59b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 13:32:24 +0000 Subject: [PATCH 132/420] Add an add existing room to space CTA to Space View --- res/css/_components.scss | 1 + res/css/structures/_SpaceRoomView.scss | 10 + .../dialogs/_AddExistingToSpaceDialog.scss | 185 ++++++++++++++++ src/components/structures/SpaceRoomView.tsx | 10 +- .../dialogs/AddExistingToSpaceDialog.tsx | 208 ++++++++++++++++++ src/i18n/strings/en_EN.json | 11 +- src/utils/space.ts | 15 ++ 7 files changed, 438 insertions(+), 2 deletions(-) create mode 100644 res/css/views/dialogs/_AddExistingToSpaceDialog.scss create mode 100644 src/components/views/dialogs/AddExistingToSpaceDialog.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index db73eed3f2..8569f62de9 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -58,6 +58,7 @@ @import "./views/context_menus/_MessageContextMenu.scss"; @import "./views/context_menus/_StatusMessageContextMenu.scss"; @import "./views/context_menus/_TagTileContextMenu.scss"; +@import "./views/dialogs/_AddExistingToSpaceDialog.scss"; @import "./views/dialogs/_AddressPickerDialog.scss"; @import "./views/dialogs/_Analytics.scss"; @import "./views/dialogs/_BugReportDialog.scss"; diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index eaaaa2f797..ee60389c59 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -188,6 +188,16 @@ $SpaceRoomViewInnerWidth: 428px; } } + &.mx_SpaceRoomView_landing_addButton { + &::before { + background-color: #ac3ba8; + } + + &::after { + mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); + } + } + &.mx_SpaceRoomView_landing_createButton { &::before { background-color: #368bd6; diff --git a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss new file mode 100644 index 0000000000..0c9d8e3840 --- /dev/null +++ b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss @@ -0,0 +1,185 @@ +/* +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. +*/ + +.mx_AddExistingToSpaceDialog_wrapper { + .mx_Dialog { + display: flex; + flex-direction: column; + } +} + +.mx_AddExistingToSpaceDialog { + width: 480px; + color: $primary-fg-color; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + min-height: 0; + + .mx_Dialog_title { + display: flex; + + .mx_BaseAvatar { + display: inline-flex; + margin: 5px 16px 5px 5px; + vertical-align: middle; + } + + .mx_BaseAvatar_image { + border-radius: 8px; + margin: 0; + vertical-align: unset; + } + + > div { + > h1 { + font-weight: $font-semi-bold; + font-size: $font-18px; + line-height: $font-22px; + margin: 0; + } + + .mx_AddExistingToSpaceDialog_onlySpace { + color: $secondary-fg-color; + font-size: $font-15px; + line-height: $font-24px; + } + } + + .mx_Dropdown_input { + border: none; + + > .mx_Dropdown_option { + padding-left: 0; + flex: unset; + height: unset; + color: $secondary-fg-color; + font-size: $font-15px; + line-height: $font-24px; + + .mx_BaseAvatar { + display: none; + } + } + + .mx_Dropdown_menu { + .mx_AddExistingToSpaceDialog_dropdownOptionActive { + color: $accent-color; + padding-right: 32px; + position: relative; + + &::before { + content: ''; + width: 20px; + height: 20px; + top: 8px; + right: 0; + position: absolute; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background-color: $accent-color; + mask-image: url('$(res)/img/element-icons/roomlist/checkmark.svg'); + } + } + } + } + } + + .mx_SearchBox { + margin: 0; + } + + .mx_AddExistingToSpaceDialog_errorText { + font-weight: $font-semi-bold; + font-size: $font-12px; + line-height: $font-15px; + color: $notice-primary-color; + margin-bottom: 28px; + } + + .mx_AddExistingToSpaceDialog_content { + .mx_AddExistingToSpaceDialog_noResults { + margin-top: 24px; + } + } + + .mx_AddExistingToSpaceDialog_section { + margin-top: 24px; + + > h3 { + margin: 0; + color: $secondary-fg-color; + font-size: $font-12px; + font-weight: $font-semi-bold; + line-height: $font-15px; + } + + .mx_AddExistingToSpaceDialog_entry { + display: flex; + margin-top: 12px; + + .mx_BaseAvatar { + margin-right: 12px; + } + + .mx_AddExistingToSpaceDialog_entry_name { + font-size: $font-15px; + line-height: 30px; + flex-grow: 1; + } + + .mx_FormButton { + min-width: 92px; + font-weight: normal; + box-sizing: border-box; + } + } + } + + .mx_AddExistingToSpaceDialog_section_spaces { + .mx_BaseAvatar_image { + border-radius: 8px; + } + } + + .mx_AddExistingToSpaceDialog_footer { + display: flex; + margin-top: 32px; + + > span { + flex-grow: 1; + font-size: $font-12px; + line-height: $font-15px; + + > * { + vertical-align: middle; + } + } + + .mx_AccessibleButton { + display: inline-block; + } + + .mx_AccessibleButton_kind_link { + padding: 0; + } + } + + .mx_FormButton { + padding: 8px 22px; + } +} diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 4159a38cfe..f1a8a4d71b 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -46,7 +46,7 @@ import {RightPanelPhases} from "../../stores/RightPanelStorePhases"; import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanelPhasePayload"; import {useStateArray} from "../../hooks/useStateArray"; import SpacePublicShare from "../views/spaces/SpacePublicShare"; -import {showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space"; +import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space"; import MemberAvatar from "../views/avatars/MemberAvatar"; interface IProps { @@ -124,6 +124,14 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => let addRoomButtons; if (canAddRooms) { addRoomButtons = + { + const [added] = await showAddExistingRooms(cli, space); + if (added) { + // TODO update rooms shown once we show hierarchy here + } + }}> + { _t("Add existing rooms & spaces") } + { showCreateNewRoom(cli, space); }}> diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx new file mode 100644 index 0000000000..66efaefd9d --- /dev/null +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -0,0 +1,208 @@ +/* +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 React, {useState} from "react"; +import classNames from "classnames"; +import {Room} from "matrix-js-sdk/src/models/room"; +import {MatrixClient} from "matrix-js-sdk/src/client"; + +import {_t} from '../../../languageHandler'; +import {IDialogProps} from "./IDialogProps"; +import BaseDialog from "./BaseDialog"; +import FormButton from "../elements/FormButton"; +import Dropdown from "../elements/Dropdown"; +import SearchBox from "../../structures/SearchBox"; +import SpaceStore from "../../../stores/SpaceStore"; +import RoomAvatar from "../avatars/RoomAvatar"; +import {getDisplayAliasForRoom} from "../../../Rooms"; +import AccessibleButton from "../elements/AccessibleButton"; +import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; +import {allSettled} from "../../../utils/promise"; +import DMRoomMap from "../../../utils/DMRoomMap"; +import {calculateRoomVia} from "../../../utils/permalinks/Permalinks"; +import StyledCheckbox from "../elements/StyledCheckbox"; + +interface IProps extends IDialogProps { + matrixClient: MatrixClient; + space: Room; + onCreateRoomClick(cli: MatrixClient, space: Room): void; +} + +const Entry = ({ room, checked, onChange }) => { + return
+ + { room.name } + onChange(e.target.checked)} checked={checked} /> +
; +}; + +const AddExistingToSpaceDialog: React.FC = ({ matrixClient: cli, space, onCreateRoomClick, onFinished }) => { + const [query, setQuery] = useState(""); + const lcQuery = query.toLowerCase(); + + const [selectedSpace, setSelectedSpace] = useState(space); + const [selectedToAdd, setSelectedToAdd] = useState(new Set()); + + const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId); + const existingSubspacesSet = new Set(existingSubspaces); + const spaces = SpaceStore.instance.getSpaces().filter(s => { + return !existingSubspacesSet.has(s) // not already in space + && space !== s // not the top-level space + && selectedSpace !== s // not the selected space + && s.name.toLowerCase().includes(lcQuery); // contains query + }); + + const existingRooms = SpaceStore.instance.getChildRooms(space.roomId); + const existingRoomsSet = new Set(existingRooms); + const rooms = cli.getVisibleRooms().filter(room => { + return !existingRoomsSet.has(room) // not already in space + && room.name.toLowerCase().includes(lcQuery) // contains query + && !DMRoomMap.shared().getUserIdForRoomId(room.roomId); // not a DM + }); + + const [busy, setBusy] = useState(false); + const [error, setError] = useState(""); + + let spaceOptionSection; + if (existingSubspacesSet.size > 0) { + const options = [space, ...existingSubspaces].map((space) => { + const classes = classNames("mx_AddExistingToSpaceDialog_dropdownOption", { + mx_AddExistingToSpaceDialog_dropdownOptionActive: space === selectedSpace, + }); + return
+ + { space.name || getDisplayAliasForRoom(space) || space.roomId } +
; + }); + + spaceOptionSection = ( + { + setSelectedSpace(existingSubspaces.find(space => space.roomId === key) || space); + }} + value={selectedSpace.roomId} + label={_t("Space selection")} + > + { options } + + ); + } else { + spaceOptionSection =
+ { space.name || getDisplayAliasForRoom(space) || space.roomId } +
; + } + + const title = + +
+

{ _t("Add existing spaces/rooms") }

+ { spaceOptionSection } +
+
; + + return + { error &&
{ error }
} + + + + { spaces.length > 0 ? ( +
+

{ _t("Spaces") }

+ { spaces.map(space => { + return { + if (checked) { + selectedToAdd.add(space); + } else { + selectedToAdd.delete(space); + } + setSelectedToAdd(new Set(selectedToAdd)); + }} + />; + }) } +
+ ) : null } + + { rooms.length > 0 ? ( +
+

{ _t("Rooms") }

+ { rooms.map(room => { + return { + if (checked) { + selectedToAdd.add(room); + } else { + selectedToAdd.delete(room); + } + setSelectedToAdd(new Set(selectedToAdd)); + }} + />; + }) } +
+ ) : undefined } + + { spaces.length + rooms.length < 1 ? + { _t("No results") } + : undefined } +
+ +
+ +
{ _t("Don't want to add an existing room?") }
+ onCreateRoomClick(cli, space)} kind="link"> + { _t("Create a new room") } + +
+ + { + setBusy(true); + try { + await allSettled(Array.from(selectedToAdd).map((room) => + SpaceStore.instance.addRoomToSpace(space, room.roomId, calculateRoomVia(room)))); + onFinished(true); + } catch (e) { + console.error("Failed to add rooms to space", e); + setError(_t("Failed to add rooms to space")); + } + setBusy(false); + }} + /> +
+
; +}; + +export default AddExistingToSpaceDialog; + diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index aeef76bf22..bb800b2af2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1978,6 +1978,15 @@ "Add a new server...": "Add a new server...", "%(networkName)s rooms": "%(networkName)s rooms", "Matrix rooms": "Matrix rooms", + "Space selection": "Space selection", + "Add existing spaces/rooms": "Add existing spaces/rooms", + "Filter your rooms and spaces": "Filter your rooms and spaces", + "Spaces": "Spaces", + "Don't want to add an existing room?": "Don't want to add an existing room?", + "Create a new room": "Create a new room", + "Applying...": "Applying...", + "Apply": "Apply", + "Failed to add rooms to space": "Failed to add rooms to space", "Matrix ID": "Matrix ID", "Matrix Room ID": "Matrix Room ID", "email address": "email address", @@ -2570,7 +2579,7 @@ "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", "Accept Invite": "Accept Invite", "Invite people": "Invite people", - "Create a new room": "Create a new room", + "Add existing rooms & spaces": "Add existing rooms & spaces", "%(count)s members|other": "%(count)s members", "%(count)s members|one": "%(count)s member", " invited you to ": " invited you to ", diff --git a/src/utils/space.ts b/src/utils/space.ts index c995b860ee..bc31829f45 100644 --- a/src/utils/space.ts +++ b/src/utils/space.ts @@ -21,6 +21,7 @@ import {EventType} from "matrix-js-sdk/src/@types/event"; import {calculateRoomVia} from "../utils/permalinks/Permalinks"; import Modal from "../Modal"; import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog"; +import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToSpaceDialog"; import CreateRoomDialog from "../components/views/dialogs/CreateRoomDialog"; import createRoom, {IOpts} from "../createRoom"; @@ -49,6 +50,20 @@ export const showSpaceSettings = (cli: MatrixClient, space: Room) => { }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); }; +export const showAddExistingRooms = async (cli: MatrixClient, space: Room) => { + return Modal.createTrackedDialog( + "Space Landing", + "Add Existing", + AddExistingToSpaceDialog, + { + matrixClient: cli, + onCreateRoomClick: showCreateNewRoom, + space, + }, + "mx_AddExistingToSpaceDialog_wrapper", + ).finished; +}; + export const showCreateNewRoom = async (cli: MatrixClient, space: Room) => { const modal = Modal.createTrackedDialog<[boolean, IOpts]>( "Space Landing", From 4e93452275ed7ed8224d14a62c8711a85fdc6e6f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 14:02:03 +0000 Subject: [PATCH 133/420] Tweak resizer collapse distributor behaviour to work with the expanding space panel --- src/components/structures/LoggedInView.tsx | 12 +++++++++++- src/resizer/distributors/collapse.ts | 9 ++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 1694b4bcf5..4e768bd9e5 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -223,7 +223,14 @@ class LoggedInView extends React.Component { let size; let collapsed; const collapseConfig: ICollapseConfig = { - toggleSize: 260 - 50, + // TODO: the space panel currently does not have a fixed width, + // just the headers at each level have a max-width of 150px + // Taking 222px for the space panel for now, + // so this will look slightly off for now, + // depending on the depth of your space tree. + // To fix this, we'll need to turn toggleSize + // into a callback so it can be measured when starting the resize operation + toggleSize: 222 + 68, onCollapsed: (_collapsed) => { collapsed = _collapsed; if (_collapsed) { @@ -244,6 +251,9 @@ class LoggedInView extends React.Component { if (!collapsed) window.localStorage.setItem("mx_lhs_size", '' + size); this.props.resizeNotifier.stopResizing(); }, + isItemCollapsed: domNode => { + return domNode.classList.contains("mx_LeftPanel_minimized"); + }, }; const resizer = new Resizer(this._resizeContainer.current, CollapseDistributor, collapseConfig); resizer.setClassNames({ diff --git a/src/resizer/distributors/collapse.ts b/src/resizer/distributors/collapse.ts index ddf3bd687e..f8db0be52c 100644 --- a/src/resizer/distributors/collapse.ts +++ b/src/resizer/distributors/collapse.ts @@ -22,6 +22,7 @@ import Sizer from "../sizer"; export interface ICollapseConfig extends IConfig { toggleSize: number; onCollapsed?(collapsed: boolean, id: string, element: HTMLElement): void; + isItemCollapsed(element: HTMLElement): boolean; } class CollapseItem extends ResizeItem { @@ -31,6 +32,11 @@ class CollapseItem extends ResizeItem { callback(collapsed, this.id, this.domNode); } } + + get isCollapsed() { + const isItemCollapsed = this.resizer.config.isItemCollapsed; + return isItemCollapsed(this.domNode); + } } export default class CollapseDistributor extends FixedDistributor { @@ -39,11 +45,12 @@ export default class CollapseDistributor extends FixedDistributor Date: Tue, 2 Mar 2021 14:11:38 +0000 Subject: [PATCH 134/420] Initial Space room directory view --- res/css/_components.scss | 1 + res/css/structures/_SpaceRoomDirectory.scss | 231 +++++++ src/components/structures/MatrixChat.tsx | 17 +- .../structures/SpaceRoomDirectory.tsx | 572 ++++++++++++++++++ src/i18n/strings/en_EN.json | 10 + 5 files changed, 827 insertions(+), 4 deletions(-) create mode 100644 res/css/structures/_SpaceRoomDirectory.scss create mode 100644 src/components/structures/SpaceRoomDirectory.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 8569f62de9..daa7016623 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -28,6 +28,7 @@ @import "./structures/_ScrollPanel.scss"; @import "./structures/_SearchBox.scss"; @import "./structures/_SpacePanel.scss"; +@import "./structures/_SpaceRoomDirectory.scss"; @import "./structures/_SpaceRoomView.scss"; @import "./structures/_TabbedView.scss"; @import "./structures/_ToastContainer.scss"; diff --git a/res/css/structures/_SpaceRoomDirectory.scss b/res/css/structures/_SpaceRoomDirectory.scss new file mode 100644 index 0000000000..5cb91820cf --- /dev/null +++ b/res/css/structures/_SpaceRoomDirectory.scss @@ -0,0 +1,231 @@ +/* +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. +*/ + +.mx_SpaceRoomDirectory_dialogWrapper > .mx_Dialog { + max-width: 960px; + height: 100%; +} + +.mx_SpaceRoomDirectory { + height: 100%; + margin-bottom: 12px; + color: $primary-fg-color; + word-break: break-word; + display: flex; + flex-direction: column; + + .mx_Dialog_title { + display: flex; + + .mx_BaseAvatar { + margin-right: 16px; + } + + .mx_BaseAvatar_image { + border-radius: 8px; + } + + > div { + > h1 { + font-weight: $font-semi-bold; + font-size: $font-18px; + line-height: $font-22px; + margin: 0; + } + + > div { + color: $secondary-fg-color; + font-size: $font-15px; + line-height: $font-24px; + } + } + } + + .mx_Dialog_content { + // TODO fix scrollbar + //display: flex; + //flex-direction: column; + //height: calc(100% - 80px); + + .mx_AccessibleButton_kind_link { + padding: 0; + } + + .mx_SearchBox { + margin: 24px 0 28px; + } + + .mx_SpaceRoomDirectory_listHeader { + display: flex; + font-size: $font-12px; + line-height: $font-15px; + color: $secondary-fg-color; + + .mx_FormButton { + margin-bottom: 8px; + } + + > span { + margin: auto 0 0 auto; + } + } + } +} + +.mx_SpaceRoomDirectory_list { + margin-top: 8px; + + .mx_SpaceRoomDirectory_roomCount { + > h3 { + display: inline; + font-weight: $font-semi-bold; + font-size: $font-18px; + line-height: $font-22px; + color: $primary-fg-color; + } + + > span { + margin-left: 8px; + font-size: $font-15px; + line-height: $font-24px; + color: $secondary-fg-color; + } + } + + .mx_SpaceRoomDirectory_subspace { + margin-top: 8px; + + .mx_SpaceRoomDirectory_subspace_info { + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: 8px; + color: $secondary-fg-color; + font-weight: $font-semi-bold; + font-size: $font-12px; + line-height: $font-15px; + + .mx_BaseAvatar { + margin-right: 12px; + vertical-align: middle; + } + + .mx_BaseAvatar_image { + border-radius: 8px; + } + + .mx_SpaceRoomDirectory_actions { + text-align: right; + height: min-content; + margin-left: auto; + margin-right: 16px; + } + } + + .mx_SpaceRoomDirectory_subspace_children { + margin-left: 12px; + border-left: 2px solid $space-button-outline-color; + padding-left: 24px; + } + } + + .mx_SpaceRoomDirectory_roomTile { + padding: 16px; + border-radius: 8px; + border: 1px solid $space-button-outline-color; + margin: 8px 0 16px; + display: flex; + min-height: 76px; + box-sizing: border-box; + + &.mx_AccessibleButton:hover { + background-color: rgba(141, 151, 165, 0.1); + } + + .mx_BaseAvatar { + margin-right: 16px; + margin-top: 6px; + } + + .mx_SpaceRoomDirectory_roomTile_info { + display: inline-block; + font-size: $font-15px; + flex-grow: 1; + height: min-content; + margin: auto 0; + + .mx_SpaceRoomDirectory_roomTile_name { + font-weight: $font-semi-bold; + line-height: $font-18px; + } + .mx_SpaceRoomDirectory_roomTile_topic { + line-height: $font-24px; + color: $secondary-fg-color; + } + } + + .mx_SpaceRoomDirectory_roomTile_memberCount { + position: relative; + margin: auto 0 auto 24px; + padding: 0 0 0 28px; + line-height: $font-24px; + display: inline-block; + width: 32px; + + &::before { + position: absolute; + content: ''; + width: 24px; + height: 24px; + top: 0; + left: 0; + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + background-color: $secondary-fg-color; + mask-image: url('$(res)/img/element-icons/community-members.svg'); + } + } + + .mx_SpaceRoomDirectory_actions { + width: 180px; + text-align: right; + height: min-content; + margin: auto 0 auto 28px; + + .mx_AccessibleButton { + vertical-align: middle; + + & + .mx_AccessibleButton { + margin-left: 24px; + } + } + } + } + + .mx_SpaceRoomDirectory_actions { + .mx_SpaceRoomDirectory_actionsText { + font-weight: normal; + font-size: $font-12px; + line-height: $font-15px; + color: $secondary-fg-color; + } + + .mx_Checkbox { + display: inline-block; + } + } +} diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 83b3565738..1700b627db 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -82,6 +82,8 @@ import {UIFeature} from "../../settings/UIFeature"; import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore"; import DialPadModal from "../views/voip/DialPadModal"; import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast'; +import SpaceStore from "../../stores/SpaceStore"; +import SpaceRoomDirectory from "./SpaceRoomDirectory"; /** constants for MatrixChat.state.view */ export enum Views { @@ -691,10 +693,17 @@ export default class MatrixChat extends React.PureComponent { break; } case Action.ViewRoomDirectory: { - const RoomDirectory = sdk.getComponent("structures.RoomDirectory"); - Modal.createTrackedDialog('Room directory', '', RoomDirectory, { - initialText: payload.initialText, - }, 'mx_RoomDirectory_dialogWrapper', false, true); + if (SpaceStore.instance.activeSpace) { + Modal.createTrackedDialog("Space room directory", "", SpaceRoomDirectory, { + space: SpaceStore.instance.activeSpace, + initialText: payload.initialText, + }, "mx_SpaceRoomDirectory_dialogWrapper", false, true); + } else { + const RoomDirectory = sdk.getComponent("structures.RoomDirectory"); + Modal.createTrackedDialog('Room directory', '', RoomDirectory, { + initialText: payload.initialText, + }, 'mx_RoomDirectory_dialogWrapper', false, true); + } // View the welcome or home page if we need something to look at this.viewSomethingBehindModal(); diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx new file mode 100644 index 0000000000..7f7b9dbb99 --- /dev/null +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -0,0 +1,572 @@ +/* +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 React, {useMemo, useRef, useState} from "react"; +import Room from "matrix-js-sdk/src/models/room"; +import MatrixEvent from "matrix-js-sdk/src/models/event"; +import {EventType, RoomType} from "matrix-js-sdk/src/@types/event"; + +import {MatrixClientPeg} from "../../MatrixClientPeg"; +import dis from "../../dispatcher/dispatcher"; +import {_t} from "../../languageHandler"; +import AccessibleButton from "../views/elements/AccessibleButton"; +import BaseDialog from "../views/dialogs/BaseDialog"; +import FormButton from "../views/elements/FormButton"; +import SearchBox from "./SearchBox"; +import RoomAvatar from "../views/avatars/RoomAvatar"; +import RoomName from "../views/elements/RoomName"; +import {useAsyncMemo} from "../../hooks/useAsyncMemo"; +import {shouldShowSpaceSettings} from "../../utils/space"; +import {EnhancedMap} from "../../utils/maps"; +import StyledCheckbox from "../views/elements/StyledCheckbox"; +import AutoHideScrollbar from "./AutoHideScrollbar"; +import BaseAvatar from "../views/avatars/BaseAvatar"; + +interface IProps { + space: Room; + initialText?: string; + onFinished(): void; +} + +/* eslint-disable camelcase */ +export interface ISpaceSummaryRoom { + canonical_alias?: string; + aliases: string[]; + avatar_url?: string; + guest_can_join: boolean; + name?: string; + num_joined_members: number + room_id: string; + topic?: string; + world_readable: boolean; + num_refs: number; + room_type: string; +} + +export interface ISpaceSummaryEvent { + room_id: string; + event_id: string; + origin_server_ts: number; + type: string; + state_key: string; + content: { + order?: string; + auto_join?: boolean; + via?: string; + }; +} +/* eslint-enable camelcase */ + +interface ISubspaceProps { + space: ISpaceSummaryRoom; + event?: MatrixEvent; + editing?: boolean; + onPreviewClick?(): void; + queueAction?(action: IAction): void; + onJoinClick?(): void; +} + +const SubSpace: React.FC = ({ + space, + editing, + event, + queueAction, + onJoinClick, + onPreviewClick, + children, +}) => { + const name = space.name || space.canonical_alias || space.aliases?.[0] || _t("Unnamed Space"); + + const evContent = event?.getContent(); + const [autoJoin, _setAutoJoin] = useState(evContent?.auto_join); + const [removed, _setRemoved] = useState(!evContent?.via); + + const cli = MatrixClientPeg.get(); + const cliRoom = cli.getRoom(space.room_id); + const myMembership = cliRoom?.getMyMembership(); + + // TODO DRY code + let actions; + if (editing && queueAction) { + if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) { + const setAutoJoin = () => { + _setAutoJoin(v => { + queueAction({ + event, + removed, + autoJoin: !v, + }); + return !v; + }); + }; + + const setRemoved = () => { + _setRemoved(v => { + queueAction({ + event, + removed: !v, + autoJoin, + }); + return !v; + }); + }; + + if (removed) { + actions = + + ; + } else { + actions = + + + ; + } + } else { + actions = + { _t("No permissions")} + ; + } + // TODO confirm remove from space click behaviour here + } else { + if (myMembership === "join") { + actions = + { _t("You're in this space")} + ; + } else if (onJoinClick) { + actions = + + { _t("Preview") } + + + + } + } + + let url: string; + if (space.avatar_url) { + url = MatrixClientPeg.get().mxcUrlToHttp(space.avatar_url, + Math.floor(24 * window.devicePixelRatio), + Math.floor(24 * window.devicePixelRatio), + "crop"); + } + + return
+
+ + { name } + +
+ { actions } +
+
+
+ { children } +
+
+}; + +interface IAction { + event: MatrixEvent; + removed: boolean; + autoJoin: boolean; +} + +interface IRoomTileProps { + room: ISpaceSummaryRoom; + event?: MatrixEvent; + editing?: boolean; + onPreviewClick(): void; + queueAction?(action: IAction): void; + onJoinClick?(): void; +} + +const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinClick }: IRoomTileProps) => { + const name = room.name || room.canonical_alias || room.aliases?.[0] || _t("Unnamed Room"); + + const evContent = event?.getContent(); + const [autoJoin, _setAutoJoin] = useState(evContent?.auto_join); + const [removed, _setRemoved] = useState(!evContent?.via); + + const cli = MatrixClientPeg.get(); + const cliRoom = cli.getRoom(room.room_id); + const myMembership = cliRoom?.getMyMembership(); + + let actions; + if (editing && queueAction) { + if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) { + const setAutoJoin = () => { + _setAutoJoin(v => { + queueAction({ + event, + removed, + autoJoin: !v, + }); + return !v; + }); + }; + + const setRemoved = () => { + _setRemoved(v => { + queueAction({ + event, + removed: !v, + autoJoin, + }); + return !v; + }); + }; + + if (removed) { + actions = + + ; + } else { + actions = + + + ; + } + } else { + actions = + { _t("No permissions")} + ; + } + // TODO confirm remove from space click behaviour here + } else { + if (myMembership === "join") { + actions = + { _t("You're in this room")} + ; + } else if (onJoinClick) { + actions = + + { _t("Preview") } + + + + } + } + + let url: string; + if (room.avatar_url) { + url = cli.mxcUrlToHttp(room.avatar_url, + Math.floor(32 * window.devicePixelRatio), + Math.floor(32 * window.devicePixelRatio), + "crop"); + } + + const content = + + +
+
+ { name } +
+
+ { room.topic } +
+
+
+ { room.num_joined_members } +
+ +
+ { actions } +
+
; + + if (editing) { + return
+ { content } +
+ } + + return + { content } + ; +}; + +export const showRoom = (room: ISpaceSummaryRoom, viaServers?: string[], autoJoin = false) => { + // Don't let the user view a room they won't be able to either peek or join: + // fail earlier so they don't have to click back to the directory. + if (MatrixClientPeg.get().isGuest()) { + if (!room.world_readable && !room.guest_can_join) { + dis.dispatch({ action: "require_registration" }); + return; + } + } + + const roomAlias = getDisplayAliasForRoom(room) || undefined; + dis.dispatch({ + action: "view_room", + auto_join: autoJoin, + should_peek: true, + _type: "room_directory", // instrumentation + room_alias: roomAlias, + room_id: room.room_id, + via_servers: viaServers, + oob_data: { + avatarUrl: room.avatar_url, + // XXX: This logic is duplicated from the JS SDK which would normally decide what the name is. + name: room.name || roomAlias || _t("Unnamed room"), + }, + }); +}; + +interface IHierarchyLevelProps { + spaceId: string; + rooms: Map; + editing?: boolean; + relations: EnhancedMap; + parents: Set; + queueAction?(action: IAction): void; + onPreviewClick(roomId: string): void; + onRemoveFromSpaceClick?(roomId: string): void; + onJoinClick?(roomId: string): void; +} + +export const HierarchyLevel = ({ + spaceId, + rooms, + editing, + relations, + parents, + onPreviewClick, + onJoinClick, + queueAction, +}: IHierarchyLevelProps) => { + const cli = MatrixClientPeg.get(); + const space = cli.getRoom(spaceId); + // TODO respect order + const [subspaces, childRooms] = relations.get(spaceId)?.reduce((result, roomId: string) => { + if (!rooms.has(roomId)) return result; // TODO wat + result[rooms.get(roomId).room_type === RoomType.Space ? 0 : 1].push(roomId); + return result; + }, [[], []]) || [[], []]; + + // Don't render this subspace if it has no rooms we can show + // TODO this is broken - as a space may have subspaces we still need to show + // if (!childRooms.length) return null; + + const userId = cli.getUserId(); + + const newParents = new Set(parents).add(spaceId); + return + { + childRooms.map(roomId => ( + { + onPreviewClick(roomId); + }} + onJoinClick={onJoinClick ? () => { + onJoinClick(roomId); + } : undefined} + /> + )) + } + + { + subspaces.filter(roomId => !newParents.has(roomId)).map(roomId => ( + { + onPreviewClick(roomId); + }} + onJoinClick={() => { + onJoinClick(roomId); + }} + > + + + )) + } + +}; + +const SpaceRoomDirectory: React.FC = ({ space, initialText = "", onFinished }) => { + // TODO pagination + const cli = MatrixClientPeg.get(); + const [query, setQuery] = useState(initialText); + const [isEditing, setIsEditing] = useState(false); + + const onCreateRoomClick = () => { + dis.dispatch({ + action: 'view_create_room', + public: true, + }); + onFinished(); + }; + + // stored within a ref as we don't need to re-render when it changes + const pendingActions = useRef(new Map()); + + let adminButton; + if (shouldShowSpaceSettings(cli, space)) { // TODO this is an imperfect test + const onManageButtonClicked = () => { + setIsEditing(true); + }; + + const onSaveButtonClicked = () => { + // TODO setBusy + pendingActions.current.forEach(({event, autoJoin, removed}) => { + const content = { + ...event.getContent(), + auto_join: autoJoin, + }; + + if (removed) { + delete content["via"]; + } + + cli.sendStateEvent(event.getRoomId(), event.getType(), content, event.getStateKey()); + }); + setIsEditing(false); + }; + + if (isEditing) { + adminButton = + + { _t("All users join by default") } + ; + } else { + adminButton = ; + } + } + + const [rooms, relations, viaMap] = useAsyncMemo(async () => { + try { + const data = await cli.getSpaceSummary(space.roomId); + + const parentChildRelations = new EnhancedMap(); + const viaMap = new EnhancedMap>(); + data.events.map((ev: ISpaceSummaryEvent) => { + if (ev.type === EventType.SpaceChild) { + parentChildRelations.getOrCreate(ev.room_id, []).push(ev.state_key); + } + if (Array.isArray(ev.content["via"])) { + const set = viaMap.getOrCreate(ev.state_key, new Set()); + ev.content["via"].forEach(via => set.add(via)); + } + }); + + return [data.rooms, parentChildRelations, viaMap]; + } catch (e) { + console.error(e); // TODO + } + + return []; + }, [space], []); + + const roomsMap = useMemo(() => { + if (!rooms) return null; + const lcQuery = query.toLowerCase(); + + const filteredRooms = rooms.filter(r => { + return r.room_type === RoomType.Space // always include spaces to allow filtering of sub-space rooms + || r.name?.toLowerCase().includes(lcQuery) + || r.topic?.toLowerCase().includes(lcQuery); + }); + + return new Map(filteredRooms.map(r => [r.room_id, r])); + // const root = rooms.get(space.roomId); + }, [rooms, query]); + + const title = + +
+

{ _t("Explore rooms") }

+
+
+
; + const explanation = + _t("If you can't find the room you're looking for, ask for an invite or Create a new room.", null, + {a: sub => { + return {sub}; + }}, + ); + + let content; + if (roomsMap) { + content = + { + pendingActions.current.set(action.event.room_id, action); + }} + onPreviewClick={roomId => { + showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), false); + onFinished(); + }} + onJoinClick={(roomId) => { + showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), true); + onFinished(); + }} + /> + ; + } + + // TODO loading state/error state + return ( + +
+ { explanation } + + + +
+ { adminButton } +
+ { content } +
+
+ ); +}; + +export default SpaceRoomDirectory; + +// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom +// but works with the objects we get from the public room list +function getDisplayAliasForRoom(room: ISpaceSummaryRoom) { + return room.canonical_alias || (room.aliases ? room.aliases[0] : ""); +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index bb800b2af2..04a6d63272 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2577,6 +2577,16 @@ "Failed to reject invite": "Failed to reject invite", "You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.", "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", + "Unnamed Space": "Unnamed Space", + "Undo": "Undo", + "Remove from Space": "Remove from Space", + "No permissions": "No permissions", + "You're in this space": "You're in this space", + "You're in this room": "You're in this room", + "Save changes": "Save changes", + "All users join by default": "All users join by default", + "Manage rooms": "Manage rooms", + "Find a room...": "Find a room...", "Accept Invite": "Accept Invite", "Invite people": "Invite people", "Add existing rooms & spaces": "Add existing rooms & spaces", From ca1bd78921e0fb05bbaaae185f3a29402b61a38e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 14:19:40 +0000 Subject: [PATCH 135/420] Add space specific variant of the dropdown on "Rooms +" sublist --- res/css/views/rooms/_RoomList.scss | 5 +- .../element-icons/roomlist/hash-circle.svg | 7 +++ .../element-icons/roomlist/plus-circle.svg | 3 ++ src/components/views/rooms/RoomList.tsx | 47 +++++++++++++++++++ src/i18n/strings/en_EN.json | 4 ++ 5 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 res/img/element-icons/roomlist/hash-circle.svg create mode 100644 res/img/element-icons/roomlist/plus-circle.svg diff --git a/res/css/views/rooms/_RoomList.scss b/res/css/views/rooms/_RoomList.scss index 66e1b827d0..d49ed4b736 100644 --- a/res/css/views/rooms/_RoomList.scss +++ b/res/css/views/rooms/_RoomList.scss @@ -19,7 +19,10 @@ limitations under the License. } .mx_RoomList_iconPlus::before { - mask-image: url('$(res)/img/element-icons/roomlist/plus.svg'); + mask-image: url('$(res)/img/element-icons/roomlist/plus-circle.svg'); +} +.mx_RoomList_iconHash::before { + mask-image: url('$(res)/img/element-icons/roomlist/hash-circle.svg'); } .mx_RoomList_iconExplore::before { mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); diff --git a/res/img/element-icons/roomlist/hash-circle.svg b/res/img/element-icons/roomlist/hash-circle.svg new file mode 100644 index 0000000000..924b22cf32 --- /dev/null +++ b/res/img/element-icons/roomlist/hash-circle.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/element-icons/roomlist/plus-circle.svg b/res/img/element-icons/roomlist/plus-circle.svg new file mode 100644 index 0000000000..251ded225c --- /dev/null +++ b/res/img/element-icons/roomlist/plus-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 45db15df7c..f7da6571da 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -47,6 +47,9 @@ import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../con import AccessibleButton from "../elements/AccessibleButton"; import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; import CallHandler from "../../../CallHandler"; +import SpaceStore from "../../../stores/SpaceStore"; +import { showAddExistingRooms, showCreateNewRoom } from "../../../utils/space"; +import { EventType } from "matrix-js-sdk/src/@types/event"; interface IProps { onKeyDown: (ev: React.KeyboardEvent) => void; @@ -152,6 +155,50 @@ const TAG_AESTHETICS: ITagAestheticsMap = { defaultHidden: false, addRoomLabel: _td("Add room"), addRoomContextMenu: (onFinished: () => void) => { + if (SpaceStore.instance.activeSpace) { + const canAddRooms = SpaceStore.instance.activeSpace.currentState.maySendStateEvent(EventType.SpaceChild, + MatrixClientPeg.get().getUserId()); + + return + { + e.preventDefault(); + e.stopPropagation(); + onFinished(); + showCreateNewRoom(MatrixClientPeg.get(), SpaceStore.instance.activeSpace); + }} + disabled={!canAddRooms} + tooltip={canAddRooms ? undefined + : _t("You do not have permissions to create new rooms in this space")} + /> + { + e.preventDefault(); + e.stopPropagation(); + onFinished(); + showAddExistingRooms(MatrixClientPeg.get(), SpaceStore.instance.activeSpace); + }} + disabled={!canAddRooms} + tooltip={canAddRooms ? undefined + : _t("You do not have permissions to add rooms to this space")} + /> + { + e.preventDefault(); + e.stopPropagation(); + onFinished(); + defaultDispatcher.fire(Action.ViewRoomDirectory); + }} + /> + ; + } + return Date: Tue, 2 Mar 2021 14:34:47 +0000 Subject: [PATCH 136/420] Add context menu to spaces in the space panel --- res/css/structures/_SpacePanel.scss | 75 ++++++ src/accessibility/context_menu/MenuItem.tsx | 11 +- src/components/views/dialogs/InfoDialog.js | 6 +- .../views/spaces/SpaceTreeLevel.tsx | 216 ++++++++++++++++++ src/i18n/strings/en_EN.json | 16 +- 5 files changed, 314 insertions(+), 10 deletions(-) diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index 24d2243912..9937117086 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -212,6 +212,30 @@ $activeBorderColor: $secondary-fg-color; border-radius: 8px; } } + + .mx_SpaceButton_menuButton { + width: 20px; + min-width: 20px; // yay flex + height: 20px; + margin-top: auto; + margin-bottom: auto; + position: relative; + display: none; + + &::before { + top: 2px; + left: 2px; + content: ''; + width: 16px; + height: 16px; + position: absolute; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + mask-image: url('$(res)/img/element-icons/context-menu.svg'); + background: $primary-fg-color; + } + } } .mx_SpacePanel_badgeContainer { @@ -254,6 +278,10 @@ $activeBorderColor: $secondary-fg-color; height: 0; display: none; } + + .mx_SpaceButton_menuButton { + display: block; + } } } @@ -272,3 +300,50 @@ $activeBorderColor: $secondary-fg-color; } } } + +.mx_SpacePanel_contextMenu { + .mx_SpacePanel_contextMenu_header { + margin: 12px 16px 12px; + font-weight: $font-semi-bold; + font-size: $font-15px; + line-height: $font-18px; + } + + .mx_IconizedContextMenu_optionList .mx_AccessibleButton.mx_SpacePanel_contextMenu_inviteButton { + color: $accent-color; + + .mx_SpacePanel_iconInvite::before { + background-color: $accent-color; + mask-image: url('$(res)/img/element-icons/room/invite.svg'); + } + } + + .mx_SpacePanel_iconSettings::before { + mask-image: url('$(res)/img/element-icons/settings.svg'); + } + + .mx_SpacePanel_iconLeave::before { + mask-image: url('$(res)/img/element-icons/leave.svg'); + } + + .mx_SpacePanel_iconHome::before { + mask-image: url('$(res)/img/element-icons/roomlist/home.svg'); + } + + .mx_SpacePanel_iconMembers::before { + mask-image: url('$(res)/img/element-icons/room/members.svg'); + } + + .mx_SpacePanel_iconPlus::before { + mask-image: url('$(res)/img/element-icons/plus.svg'); + } + + .mx_SpacePanel_iconExplore::before { + mask-image: url('$(res)/img/element-icons/roomlist/explore.svg'); + } +} + + +.mx_SpacePanel_sharePublicSpace { + margin: 0; +} diff --git a/src/accessibility/context_menu/MenuItem.tsx b/src/accessibility/context_menu/MenuItem.tsx index 0bb169abf8..9a7c1d1f0a 100644 --- a/src/accessibility/context_menu/MenuItem.tsx +++ b/src/accessibility/context_menu/MenuItem.tsx @@ -19,14 +19,23 @@ limitations under the License. import React from "react"; import AccessibleButton from "../../components/views/elements/AccessibleButton"; +import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton"; interface IProps extends React.ComponentProps { label?: string; + tooltip?: string; } // Semantic component for representing a role=menuitem -export const MenuItem: React.FC = ({children, label, ...props}) => { +export const MenuItem: React.FC = ({children, label, tooltip, ...props}) => { const ariaLabel = props["aria-label"] || label; + + if (tooltip) { + return + { children } + ; + } + return ( { children } diff --git a/src/components/views/dialogs/InfoDialog.js b/src/components/views/dialogs/InfoDialog.js index 97ae968ff3..6dc9fc01b0 100644 --- a/src/components/views/dialogs/InfoDialog.js +++ b/src/components/views/dialogs/InfoDialog.js @@ -27,7 +27,7 @@ export default class InfoDialog extends React.Component { className: PropTypes.string, title: PropTypes.string, description: PropTypes.node, - button: PropTypes.string, + button: PropTypes.oneOfType(PropTypes.string, PropTypes.bool), onFinished: PropTypes.func, hasCloseButton: PropTypes.bool, onKeyDown: PropTypes.func, @@ -60,11 +60,11 @@ export default class InfoDialog extends React.Component {
{ this.props.description }
- - + } ); } diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index f94798433f..04d6c02208 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -23,7 +23,27 @@ import SpaceStore from "../../../stores/SpaceStore"; import NotificationBadge from "../rooms/NotificationBadge"; import {RovingAccessibleButton} from "../../../accessibility/roving/RovingAccessibleButton"; import {RovingAccessibleTooltipButton} from "../../../accessibility/roving/RovingAccessibleTooltipButton"; +import IconizedContextMenu, { + IconizedContextMenuOption, + IconizedContextMenuOptionList, +} from "../context_menus/IconizedContextMenu"; +import {_t} from "../../../languageHandler"; +import {ContextMenuTooltipButton} from "../../../accessibility/context_menu/ContextMenuTooltipButton"; +import {toRightOf} from "../../structures/ContextMenu"; +import {shouldShowSpaceSettings, showCreateNewRoom, showSpaceSettings} from "../../../utils/space"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import {ButtonEvent} from "../elements/AccessibleButton"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import Modal from "../../../Modal"; +import SpacePublicShare from "./SpacePublicShare"; +import {Action} from "../../../dispatcher/actions"; +import RoomViewStore from "../../../stores/RoomViewStore"; +import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; +import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; +import {showRoomInviteDialog} from "../../../RoomInvite"; +import InfoDialog from "../dialogs/InfoDialog"; +import {EventType} from "matrix-js-sdk/src/@types/event"; +import SpaceRoomDirectory from "../../structures/SpaceRoomDirectory"; interface IItemProps { space?: Room; @@ -78,6 +98,200 @@ export class SpaceItem extends React.PureComponent { SpaceStore.instance.setActiveSpace(this.props.space); }; + private onMenuOpenClick = (ev: React.MouseEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + const target = ev.target as HTMLButtonElement; + this.setState({contextMenuPosition: target.getBoundingClientRect()}); + }; + + private onMenuClose = () => { + this.setState({contextMenuPosition: null}); + }; + + private onHomeClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + defaultDispatcher.dispatch({ + action: "view_room", + room_id: this.props.space.roomId, + }); + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private onInviteClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + if (this.props.space.getJoinRule() === "public") { + const modal = Modal.createTrackedDialog("Space Invite", "User Menu", InfoDialog, { + title: _t("Invite members"), + description: + { _t("Share your public space") } + modal.close()} /> + , + fixedWidth: false, + button: false, + className: "mx_SpacePanel_sharePublicSpace", + hasCloseButton: true, + }); + } else { + showRoomInviteDialog(this.props.space.roomId); + } + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private onSettingsClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + showSpaceSettings(this.context, this.props.space); + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private onLeaveClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + defaultDispatcher.dispatch({ + action: "leave_room", + room_id: this.props.space.roomId, + }); + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private onNewRoomClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + showCreateNewRoom(this.context, this.props.space); + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private onMembersClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + if (!RoomViewStore.getRoomId()) { + defaultDispatcher.dispatch({ + action: "view_room", + room_id: this.props.space.roomId, + }, true); + } + + defaultDispatcher.dispatch({ + action: Action.SetRightPanelPhase, + phase: RightPanelPhases.SpaceMemberList, + refireParams: { space: this.props.space }, + }); + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private onExploreRoomsClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + Modal.createTrackedDialog("Space room directory", "Space panel", SpaceRoomDirectory, { + space: this.props.space, + }, "mx_SpaceRoomDirectory_dialogWrapper", false, true); + this.setState({contextMenuPosition: null}); // also close the menu + }; + + private renderContextMenu(): React.ReactElement { + let contextMenu = null; + if (this.state.contextMenuPosition) { + const userId = this.context.getUserId(); + + let inviteOption; + if (this.props.space.canInvite(userId)) { + inviteOption = ( + + ); + } + + let settingsOption; + let leaveSection; + if (shouldShowSpaceSettings(this.context, this.props.space)) { + settingsOption = ( + + ); + } else { + leaveSection = + + ; + } + + let newRoomOption; + if (this.props.space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) { + newRoomOption = ( + + ); + } + + contextMenu = +
+ { this.props.space.name } +
+ + { inviteOption } + + + { settingsOption } + + { newRoomOption } + + { leaveSection } +
; + } + + return ( + + + { contextMenu } + + ); + } + render() { const {space, activeSpaces, isNested} = this.props; @@ -133,6 +347,7 @@ export class SpaceItem extends React.PureComponent {
{ notifBadge } + { this.renderContextMenu() }
); @@ -149,6 +364,7 @@ export class SpaceItem extends React.PureComponent { { space.name } { notifBadge } + { this.renderContextMenu() }
); diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6d2f41ceae..19324e1540 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1003,6 +1003,16 @@ "Failed to copy": "Failed to copy", "Share invite link": "Share invite link", "Invite by email or username": "Invite by email or username", + "Invite members": "Invite members", + "Share your public space": "Share your public space", + "Invite people": "Invite people", + "Settings": "Settings", + "Leave space": "Leave space", + "New room": "New room", + "Space Home": "Space Home", + "Members": "Members", + "Explore rooms": "Explore rooms", + "Space options": "Space options", "Remove": "Remove", "This bridge was provisioned by .": "This bridge was provisioned by .", "This bridge is managed by .": "This bridge is managed by .", @@ -1583,7 +1593,6 @@ "Favourited": "Favourited", "Favourite": "Favourite", "Low Priority": "Low Priority", - "Settings": "Settings", "Leave Room": "Leave Room", "Room options": "Room options", "%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.", @@ -1672,7 +1681,6 @@ "The homeserver the user you’re verifying is connected to": "The homeserver the user you’re verifying is connected to", "Yours, or the other users’ internet connection": "Yours, or the other users’ internet connection", "Yours, or the other users’ session": "Yours, or the other users’ session", - "Members": "Members", "Room Info": "Room Info", "You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets", "Unpin": "Unpin", @@ -2510,13 +2518,11 @@ "Explore Public Rooms": "Explore Public Rooms", "Create a Group Chat": "Create a Group Chat", "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", "You do not have permission to create rooms in this community.": "You do not have permission to create rooms in this community.", "This space is not public. You will not be able to rejoin without an invite.": "This space 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.": "This room is not public. You will not be able to rejoin without an invite.", - "Leave space": "Leave space", "Are you sure you want to leave the space '%(spaceName)s'?": "Are you sure you want to leave the space '%(spaceName)s'?", "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", @@ -2592,7 +2598,6 @@ "Manage rooms": "Manage rooms", "Find a room...": "Find a room...", "Accept Invite": "Accept Invite", - "Invite people": "Invite people", "Add existing rooms & spaces": "Add existing rooms & spaces", "%(count)s members|other": "%(count)s members", "%(count)s members|one": "%(count)s member", @@ -2607,7 +2612,6 @@ "Failed to create initial space rooms": "Failed to create initial space rooms", "Skip for now": "Skip for now", "Creating rooms...": "Creating rooms...", - "Share your public space": "Share your public space", "At the moment only you can see it.": "At the moment only you can see it.", "Finish": "Finish", "Who are you working with?": "Who are you working with?", From 43cc7deedae82893a96f22493a0e360399f12968 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 14:37:28 +0000 Subject: [PATCH 137/420] Show hierarchy of auto_join rooms in the space view --- res/css/structures/_SpaceRoomView.scss | 8 +++ src/components/structures/SpaceRoomView.tsx | 59 ++++++++++++++++++++- src/i18n/strings/en_EN.json | 2 + 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index ee60389c59..38310d39a9 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -219,6 +219,14 @@ $SpaceRoomViewInnerWidth: 428px; } } } + + .mx_SpaceRoomDirectory_list { + max-width: 600px; + + .mx_SpaceRoomDirectory_roomTile_actions { + display: none; + } + } } .mx_SpaceRoomView_privateScope { diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index f1a8a4d71b..5c91efc1c0 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import React, {RefObject, useContext, useRef, useState} from "react"; -import {EventType} from "matrix-js-sdk/src/@types/event"; +import {EventType, RoomType} from "matrix-js-sdk/src/@types/event"; import {Room} from "matrix-js-sdk/src/models/room"; import MatrixClientContext from "../../contexts/MatrixClientContext"; @@ -24,6 +24,7 @@ import {_t} from "../../languageHandler"; import AccessibleButton from "../views/elements/AccessibleButton"; import RoomName from "../views/elements/RoomName"; import RoomTopic from "../views/elements/RoomTopic"; +import InlineSpinner from "../views/elements/InlineSpinner"; import FormButton from "../views/elements/FormButton"; import {inviteMultipleToRoom, showRoomInviteDialog} from "../../RoomInvite"; import {useRoomMembers} from "../../hooks/useRoomMembers"; @@ -47,7 +48,12 @@ import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanel import {useStateArray} from "../../hooks/useStateArray"; import SpacePublicShare from "../views/spaces/SpacePublicShare"; import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space"; +import {HierarchyLevel, ISpaceSummaryEvent, ISpaceSummaryRoom, showRoom} from "./SpaceRoomDirectory"; +import {useAsyncMemo} from "../../hooks/useAsyncMemo"; +import {EnhancedMap} from "../../utils/maps"; +import AutoHideScrollbar from "./AutoHideScrollbar"; import MemberAvatar from "../views/avatars/MemberAvatar"; +import {useStateToggle} from "../../hooks/useStateToggle"; interface IProps { space: Room; @@ -121,13 +127,15 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => const canAddRooms = myMembership === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId); + const [_, forceUpdate] = useStateToggle(false); // TODO + let addRoomButtons; if (canAddRooms) { addRoomButtons = { const [added] = await showAddExistingRooms(cli, space); if (added) { - // TODO update rooms shown once we show hierarchy here + forceUpdate(); } }}> { _t("Add existing rooms & spaces") } @@ -149,6 +157,51 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => ; } + const [loading, roomsMap, relations, numRooms] = useAsyncMemo(async () => { + try { + const data = await cli.getSpaceSummary(space.roomId, undefined, myMembership !== "join"); + + const parentChildRelations = new EnhancedMap(); + data.events.map((ev: ISpaceSummaryEvent) => { + if (ev.type === EventType.SpaceChild) { + parentChildRelations.getOrCreate(ev.room_id, []).push(ev.state_key); + } + }); + + const roomsMap = new Map(data.rooms.map(r => [r.room_id, r])); + const numRooms = data.rooms.filter(r => r.room_type !== RoomType.Space).length; + return [false, roomsMap, parentChildRelations, numRooms]; + } catch (e) { + console.error(e); // TODO + } + + return [false]; + }, [space, _], [true]); + + let previewRooms; + if (roomsMap) { + previewRooms = +
+

{ myMembership === "join" ? _t("Rooms") : _t("Default Rooms")}

+ { numRooms } +
+ { + showRoom(roomsMap.get(roomId), [], false); // TODO + }} + /> +
; + } else if (loading) { + previewRooms = ; + } else { + previewRooms =

{_t("Your server does not support showing space hierarchies.")}

; + } + return
@@ -213,6 +266,8 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => { addRoomButtons } { settingsButton }
+ + { previewRooms }
; }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 19324e1540..8609af6a71 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2599,6 +2599,8 @@ "Find a room...": "Find a room...", "Accept Invite": "Accept Invite", "Add existing rooms & spaces": "Add existing rooms & spaces", + "Default Rooms": "Default Rooms", + "Your server does not support showing space hierarchies.": "Your server does not support showing space hierarchies.", "%(count)s members|other": "%(count)s members", "%(count)s members|one": "%(count)s member", " invited you to ": " invited you to ", From 20e57d15fd2d77e9dc783ef55480a963a5329054 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 2 Mar 2021 15:20:54 +0000 Subject: [PATCH 138/420] Option for audio streaming --- .../views/context_menus/WidgetContextMenu.tsx | 13 +++++++++++++ src/i18n/strings/en_EN.json | 1 + src/stores/widgets/ElementWidgetActions.ts | 1 + 3 files changed, 15 insertions(+) diff --git a/src/components/views/context_menus/WidgetContextMenu.tsx b/src/components/views/context_menus/WidgetContextMenu.tsx index c1af86eae6..e7d1c02c66 100644 --- a/src/components/views/context_menus/WidgetContextMenu.tsx +++ b/src/components/views/context_menus/WidgetContextMenu.tsx @@ -31,6 +31,7 @@ import QuestionDialog from "../dialogs/QuestionDialog"; import {WidgetType} from "../../../widgets/WidgetType"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; +import { getConfigLivestreamUrl, startJitsiAudioLivestream } from "../../../Livestream"; interface IProps extends React.ComponentProps { app: IApp; @@ -54,6 +55,17 @@ const WidgetContextMenu: React.FC = ({ const widgetMessaging = WidgetMessagingStore.instance.getMessagingForId(app.id); const canModify = userWidget || WidgetUtils.canUserModifyWidgets(roomId); + let streamAudioStreamButton; + if (getConfigLivestreamUrl() && (app.type === "m.jitsi" || app.type === "jitsi")) { + const onStreamAudioClick = () => { + startJitsiAudioLivestream(widgetMessaging, roomId); + onFinished(); + }; + streamAudioStreamButton = ; + } + let unpinButton; if (showUnpin) { const onUnpinClick = () => { @@ -163,6 +175,7 @@ const WidgetContextMenu: React.FC = ({ return + { streamAudioStreamButton } { editButton } { revokeButton } { deleteButton } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 38460a5f6e..7242ed7de6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2357,6 +2357,7 @@ "Set status": "Set status", "Set a new status...": "Set a new status...", "View Community": "View Community", + "Start audio stream": "Start audio stream", "Take a picture": "Take a picture", "Delete Widget": "Delete Widget", "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?": "Deleting a widget removes it for all users in this room. Are you sure you want to delete this widget?", diff --git a/src/stores/widgets/ElementWidgetActions.ts b/src/stores/widgets/ElementWidgetActions.ts index de48746a74..cd591a6fb4 100644 --- a/src/stores/widgets/ElementWidgetActions.ts +++ b/src/stores/widgets/ElementWidgetActions.ts @@ -19,6 +19,7 @@ import { IWidgetApiRequest } from "matrix-widget-api"; export enum ElementWidgetActions { ClientReady = "im.vector.ready", HangupCall = "im.vector.hangup", + StartLiveStream = "im.vector.start_live_stream", OpenIntegrationManager = "integration_manager_open", /** From d1a75885a7a65477ab106e65b745a6e2e972daf6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 2 Mar 2021 15:35:02 +0000 Subject: [PATCH 139/420] Protect onAction dispatch handler on the SpaceStore with Spaces disabled --- src/stores/SpaceStore.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index d675879138..8e0066da91 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -408,6 +408,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } protected async onAction(payload: ActionPayload) { + if (!SettingsStore.getValue("feature_spaces")) return; switch (payload.action) { case "view_room": { const room = this.matrixClient?.getRoom(payload.room_id); From 867ce322e1da4675b78bb884256bbcef423f585e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 2 Mar 2021 10:47:49 -0700 Subject: [PATCH 140/420] Add .tmp files to gitignore My git client is convinced that `src/component-index.js.tmp` needs to be checked in, which is nice of it but also wrong. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 33e8bfc7ac..e1dd7726e1 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ package-lock.json /src/component-index.js .DS_Store +*.tmp From 08d35073de93367b059b23c1581b8eb765822754 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 2 Mar 2021 11:04:12 -0700 Subject: [PATCH 141/420] Improve commentary --- src/components/views/messages/EditHistoryMessage.js | 2 +- src/components/views/rooms/EventTile.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/views/messages/EditHistoryMessage.js b/src/components/views/messages/EditHistoryMessage.js index 6c420a16fc..0967be937a 100644 --- a/src/components/views/messages/EditHistoryMessage.js +++ b/src/components/views/messages/EditHistoryMessage.js @@ -158,7 +158,7 @@ export default class EditHistoryMessage extends React.PureComponent { const isSending = (['sending', 'queued', 'encrypting'].indexOf(this.state.sendStatus) !== -1); const classes = classNames({ "mx_EventTile": true, - // Note: we keep these sending state classes for tests, not for our styles + // Note: we keep the `sending` state class for tests, not for our styles "mx_EventTile_sending": isSending, "mx_EventTile_notSent": this.state.sendStatus === 'not_sent', }); diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 01e932dd3a..b4192fc8d3 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -408,6 +408,8 @@ export default class EventTile extends React.Component { return; } + // We force update because we have no state or prop changes to queue up, instead relying on + // the getters we use here to determine what needs rendering. this.forceUpdate(() => { // Per elsewhere in this file, we can remove the listener once we will have no further purpose for it. if (!this._shouldShowSentReceipt && !this._shouldShowSendingReceipt) { @@ -805,8 +807,7 @@ export default class EventTile extends React.Component { mx_EventTile_isEditing: isEditing, mx_EventTile_info: isInfoMessage, mx_EventTile_12hr: this.props.isTwelveHour, - // Note: we keep these sending state classes for tests, not for our styles - mx_EventTile_encrypting: this.props.eventSendStatus === 'encrypting', + // Note: we keep the `sending` state class for tests, not for our styles mx_EventTile_sending: !isEditing && isSending, mx_EventTile_notSent: this.props.eventSendStatus === 'not_sent', mx_EventTile_highlight: this.props.tileShape === 'notif' ? false : this.shouldHighlight(), From d9a801910a44a80c63035fcaab5769450951c612 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 3 Mar 2021 11:34:29 +0000 Subject: [PATCH 142/420] Tweak spaces copy --- src/RoomInvite.js | 7 ++--- src/components/structures/SpaceRoomView.tsx | 2 +- src/components/views/dialogs/InviteDialog.tsx | 31 +++++++++++-------- .../views/spaces/SpaceCreateMenu.tsx | 11 +++---- src/i18n/strings/en_EN.json | 18 ++++++----- 5 files changed, 37 insertions(+), 32 deletions(-) diff --git a/src/RoomInvite.js b/src/RoomInvite.js index 503411d2b3..9ae41b851a 100644 --- a/src/RoomInvite.js +++ b/src/RoomInvite.js @@ -22,7 +22,7 @@ import MultiInviter from './utils/MultiInviter'; import Modal from './Modal'; import * as sdk from './'; import { _t } from './languageHandler'; -import InviteDialog, {KIND_DM, KIND_INVITE, KIND_SPACE_INVITE} from "./components/views/dialogs/InviteDialog"; +import InviteDialog, {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog"; import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog"; import {CommunityPrototypeStore} from "./stores/CommunityPrototypeStore"; @@ -50,11 +50,10 @@ export function showStartChatInviteDialog(initialText) { } export function showRoomInviteDialog(roomId) { - const isSpace = MatrixClientPeg.get()?.getRoom(roomId)?.isSpaceRoom(); // This dialog handles the room creation internally - we don't need to worry about it. Modal.createTrackedDialog( - "Invite Users", isSpace ? "Space" : "Room", InviteDialog, { - kind: isSpace ? KIND_SPACE_INVITE : KIND_INVITE, + "Invite Users", "", InviteDialog, { + kind: KIND_INVITE, roomId, }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true, diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 5c91efc1c0..9bacdd975d 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -557,7 +557,7 @@ export default class SpaceRoomView extends React.PureComponent { case Phase.PublicCreateRooms: return this.setState({ phase: Phase.PublicShare })} />; diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index 9bc5b6476f..6d5cb8786e 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -48,7 +48,6 @@ import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; export const KIND_DM = "dm"; export const KIND_INVITE = "invite"; -export const KIND_SPACE_INVITE = "space_invite"; export const KIND_CALL_TRANSFER = "call_transfer"; const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first @@ -310,7 +309,7 @@ interface IInviteDialogProps { // not provided. kind: string, - // The room ID this dialog is for. Only required for KIND_INVITE and KIND_SPACE_INVITE. + // The room ID this dialog is for. Only required for KIND_INVITE. roomId: string, // The call to transfer. Only required for KIND_CALL_TRANSFER. @@ -349,8 +348,8 @@ export default class InviteDialog extends React.PureComponent) or share this room."); + "(like ) or share this space."); } else { helpTextUntranslated = _td("Invite someone using their name, username " + - "(like ) or share this room."); + "(like ) or share this space."); } - } else { // KIND_SPACE_INVITE + } else { if (identityServersEnabled) { helpTextUntranslated = _td("Invite someone using their name, email address, username " + - "(like ) or share this space."); + "(like ) or share this room."); } else { helpTextUntranslated = _td("Invite someone using their name, username " + - "(like ) or share this space."); + "(like ) or share this room."); } } diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index 9d0543a6c5..88098d1b66 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -107,7 +107,8 @@ const SpaceCreateMenu = ({ onFinished }) => { if (visibility === null) { body =

{ _t("Create a space") }

-

{ _t("Organise rooms into spaces, for just you or anyone") }

+

{ _t("Spaces are new ways to group rooms and people. " + + "To join an existing space you’ll need an invite") }

{ /> setVisibility(Visibility.Private)} /> - {/*

{ _t("Looking to join an existing space?") }

*/} +

{ _t("You can change this later") }

; } else { body = @@ -134,9 +135,7 @@ const SpaceCreateMenu = ({ onFinished }) => {

{ - visibility === Visibility.Public - ? _t("Personalise your public space") - : _t("Personalise your private space") + visibility === Visibility.Public ? _t("Your public space") : _t("Your private space") }

diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8609af6a71..e256e6bb2f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -983,14 +983,15 @@ "Name": "Name", "Description": "Description", "Create a space": "Create a space", - "Organise rooms into spaces, for just you or anyone": "Organise rooms into spaces, for just you or anyone", + "Spaces are new ways to group rooms and people. To join an existing space you’ll need an invite": "Spaces are new ways to group rooms and people. To join an existing space you’ll need an invite", "Public": "Public", "Open space for anyone, best for communities": "Open space for anyone, best for communities", "Private": "Private", - "Invite only space, best for yourself or teams": "Invite only space, best for yourself or teams", + "Invite only, best for yourself or teams": "Invite only, best for yourself or teams", + "You can change this later": "You can change this later", "Go back": "Go back", - "Personalise your public space": "Personalise your public space", - "Personalise your private space": "Personalise your private space", + "Your public space": "Your public space", + "Your private space": "Your private space", "Give it a photo, name and description to help you identify it.": "Give it a photo, name and description to help you identify it.", "You can change these at any point.": "You can change these at any point.", "Creating...": "Creating...", @@ -2190,10 +2191,12 @@ "Start a conversation with someone using their name or username (like ).": "Start a conversation with someone using their name or username (like ).", "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here", "Go": "Go", - "Invite someone using their name, email address, username (like ) or share this room.": "Invite someone using their name, email address, username (like ) or share this room.", - "Invite someone using their name, username (like ) or share this room.": "Invite someone using their name, username (like ) or share this room.", + "Invite to %(spaceName)s": "Invite to %(spaceName)s", + "Unnamed Space": "Unnamed Space", "Invite someone using their name, email address, username (like ) or share this space.": "Invite someone using their name, email address, username (like ) or share this space.", "Invite someone using their name, username (like ) or share this space.": "Invite someone using their name, username (like ) or share this space.", + "Invite someone using their name, email address, username (like ) or share this room.": "Invite someone using their name, email address, username (like ) or share this room.", + "Invite someone using their name, username (like ) or share this room.": "Invite someone using their name, username (like ) or share this room.", "Transfer": "Transfer", "a new master key signature": "a new master key signature", "a new cross-signing key signature": "a new cross-signing key signature", @@ -2587,7 +2590,6 @@ "Failed to reject invite": "Failed to reject invite", "You have %(count)s unread notifications in a prior version of this room.|other": "You have %(count)s unread notifications in a prior version of this room.", "You have %(count)s unread notifications in a prior version of this room.|one": "You have %(count)s unread notification in a prior version of this room.", - "Unnamed Space": "Unnamed Space", "Undo": "Undo", "Remove from Space": "Remove from Space", "No permissions": "No permissions", @@ -2626,7 +2628,7 @@ "Invite your teammates": "Invite your teammates", "Invite by username": "Invite by username", "Inviting...": "Inviting...", - "What discussions do you want to have?": "What discussions do you want to have?", + "What are some things you want to discuss?": "What are some things you want to discuss?", "We'll create rooms for each topic.": "We'll create rooms for each topic.", "What projects are you working on?": "What projects are you working on?", "We'll create rooms for each of them. You can add existing rooms after setup.": "We'll create rooms for each of them. You can add existing rooms after setup.", From 85985db441da38bf3872f6483d15758af49f1b15 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 3 Mar 2021 11:50:41 +0000 Subject: [PATCH 143/420] add comment --- res/css/views/rooms/_MemberList.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/css/views/rooms/_MemberList.scss b/res/css/views/rooms/_MemberList.scss index 631ddc484f..075e9ff585 100644 --- a/res/css/views/rooms/_MemberList.scss +++ b/res/css/views/rooms/_MemberList.scss @@ -46,6 +46,8 @@ limitations under the License. } .mx_RightPanel_scopeHeader { + // vertically align with position on other right panel cards + // to prevent it bouncing as user navigates right panel margin-top: -8px; } } From 73411fa53dd9b8a2f3b9300779ee1bfe45c8c0f9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 3 Mar 2021 13:42:44 +0000 Subject: [PATCH 144/420] tidy code style --- src/components/structures/SpaceRoomDirectory.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index 7f7b9dbb99..06df6a528e 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -157,10 +157,12 @@ const SubSpace: React.FC = ({ let url: string; if (space.avatar_url) { - url = MatrixClientPeg.get().mxcUrlToHttp(space.avatar_url, + url = MatrixClientPeg.get().mxcUrlToHttp( + space.avatar_url, Math.floor(24 * window.devicePixelRatio), Math.floor(24 * window.devicePixelRatio), - "crop"); + "crop", + ); } return

@@ -262,10 +264,12 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli let url: string; if (room.avatar_url) { - url = cli.mxcUrlToHttp(room.avatar_url, + url = cli.mxcUrlToHttp( + room.avatar_url, Math.floor(32 * window.devicePixelRatio), Math.floor(32 * window.devicePixelRatio), - "crop"); + "crop", + ); } const content = From d8483ddf0de4a08074a3c59ed0eff71da1390e74 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 3 Mar 2021 20:23:21 +0000 Subject: [PATCH 145/420] Don't place another call if there's already one ongoing The 'call' button doesn't turn into a hangup button as soon as there's a call in the room, but we should have been doing this anyway. --- src/CallHandler.tsx | 8 ++++++++ src/i18n/strings/en_EN.json | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 42a38c7a54..8621f441de 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -706,6 +706,14 @@ export default class CallHandler { return; } + if (this.getCallForRoom(room.roomId)) { + Modal.createTrackedDialog('Call Handler', 'Existing Call with user', ErrorDialog, { + title: _t('Already in call'), + description: _t("You're already in a call with this person."), + }); + return; + } + const members = room.getJoinedMembers(); if (members.length <= 1) { Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fa7f446b7c..a1999acb3b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -58,6 +58,8 @@ "You cannot place VoIP calls in this browser.": "You cannot place VoIP calls in this browser.", "Too Many Calls": "Too Many Calls", "You've reached the maximum number of simultaneous calls.": "You've reached the maximum number of simultaneous calls.", + "Already in call": "Already in call", + "You're already in a call with this person.": "You're already in a call with this person.", "You cannot place a call with yourself.": "You cannot place a call with yourself.", "Call in Progress": "Call in Progress", "A call is currently being placed!": "A call is currently being placed!", From ae08f74336d3aa24988f7f3dba7d817cd5c96741 Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Wed, 3 Mar 2021 22:38:30 +0200 Subject: [PATCH 146/420] feat: improve "view source" display encrypted and decrypted event source on the same dialog keep only one "View Source" action on event context menu --- res/css/structures/_ViewSource.scss | 11 ++++++- src/components/structures/ViewSource.js | 32 ++++++++++++++----- .../views/context_menus/MessageContextMenu.js | 12 ++----- .../views/dialogs/RoomSettingsDialog.js | 2 +- .../views/dialogs/UserSettingsDialog.js | 2 +- src/i18n/strings/en_EN.json | 4 ++- src/i18n/strings/en_US.json | 4 ++- 7 files changed, 44 insertions(+), 23 deletions(-) diff --git a/res/css/structures/_ViewSource.scss b/res/css/structures/_ViewSource.scss index 421d1f03cd..0cb5dd8f3a 100644 --- a/res/css/structures/_ViewSource.scss +++ b/res/css/structures/_ViewSource.scss @@ -22,9 +22,18 @@ limitations under the License. float: right; } -.mx_ViewSource_label_bottom { +.mx_ViewSource_separator { clear: both; border-bottom: 1px solid #e5e5e5; + padding-top: 0.7em; + padding-bottom: 0.7em; +} + +.mx_ViewSource_heading { + font-size: $font-17px; + font-weight: 400; + color: $primary-fg-color; + margin-top: 0.7em; } .mx_ViewSource pre { diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index 0b969784e5..866f2e0a0b 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -19,7 +19,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import SyntaxHighlight from '../views/elements/SyntaxHighlight'; -import {_t} from "../../languageHandler"; +import {_t, _td} from "../../languageHandler"; import * as sdk from "../../index"; @@ -29,20 +29,36 @@ export default class ViewSource extends React.Component { onFinished: PropTypes.func.isRequired, roomId: PropTypes.string.isRequired, eventId: PropTypes.string.isRequired, + isEncrypted: PropTypes.bool.isRequired, + decryptedContent: PropTypes.object, }; render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + + const DecryptedSection = <> +
+
{_t("Decrypted event source")}
+ + { JSON.stringify(this.props.decryptedContent, null, 2) } + + ; + + const OriginalSection = <> +
+
{_t("Original event source")}
+ + { JSON.stringify(this.props.content, null, 2) } + + ; + return ( -
Room ID: { this.props.roomId }
-
Event ID: { this.props.eventId }
-
-
- - { JSON.stringify(this.props.content, null, 2) } - +
Room ID: { this.props.roomId }
+
Event ID: { this.props.eventId }
+ { this.props.isEncrypted && DecryptedSection } + { OriginalSection }
); diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 6b871e4f24..b002d1ec62 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -130,6 +130,8 @@ export default class MessageContextMenu extends React.Component { roomId: ev.getRoomId(), eventId: ev.getId(), content: ev.event, + isEncrypted: this.props.mxEvent.getType() !== this.props.mxEvent.getWireType(), + decryptedContent: ev._clearEvent, }, 'mx_Dialog_viewsource'); this.closeMenu(); }; @@ -309,7 +311,6 @@ export default class MessageContextMenu extends React.Component { let cancelButton; let forwardButton; let pinButton; - let viewClearSourceButton; let unhidePreviewButton; let externalURLButton; let quoteButton; @@ -389,14 +390,6 @@ export default class MessageContextMenu extends React.Component { ); - if (mxEvent.getType() !== mxEvent.getWireType()) { - viewClearSourceButton = ( - - { _t('View Decrypted Source') } - - ); - } - if (this.props.eventTileOps) { if (this.props.eventTileOps.isWidgetHidden()) { unhidePreviewButton = ( @@ -481,7 +474,6 @@ export default class MessageContextMenu extends React.Component { { forwardButton } { pinButton } { viewSourceButton } - { viewClearSourceButton } { unhidePreviewButton } { permalinkButton } { quoteButton } diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js index 9d9313f08f..368f2aeccd 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.js +++ b/src/components/views/dialogs/RoomSettingsDialog.js @@ -116,7 +116,7 @@ export default class RoomSettingsDialog extends React.Component { return ( -
+
diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js index 7164540aea..3291fa2387 100644 --- a/src/components/views/dialogs/UserSettingsDialog.js +++ b/src/components/views/dialogs/UserSettingsDialog.js @@ -155,7 +155,7 @@ export default class UserSettingsDialog extends React.Component { return ( -
+
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fa7f446b7c..b777adcf0c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2875,5 +2875,7 @@ "Esc": "Esc", "Enter": "Enter", "Space": "Space", - "End": "End" + "End": "End", + "Decrypted event source": "Decrypted event source", + "Original event source": "Original event source" } diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index a1275fb089..9dc6d18f8a 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -650,5 +650,7 @@ "Error upgrading room": "Error upgrading room", "Double check that your server supports the room version chosen and try again.": "Double check that your server supports the room version chosen and try again.", "Changes the avatar of the current room": "Changes the avatar of the current room", - "Changes your avatar in all rooms": "Changes your avatar in all rooms" + "Changes your avatar in all rooms": "Changes your avatar in all rooms", + "Decrypted event source": "Decrypted event source", + "Original event source": "Original event source" } From 0a1f372371a73736a720deface3564be172a9953 Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Wed, 3 Mar 2021 23:26:31 +0200 Subject: [PATCH 147/420] fix: lint --- src/components/structures/ViewSource.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index 866f2e0a0b..b57efe1dd3 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -19,7 +19,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; import SyntaxHighlight from '../views/elements/SyntaxHighlight'; -import {_t, _td} from "../../languageHandler"; +import {_t} from "../../languageHandler"; import * as sdk from "../../index"; From 6d792cc08cec87e5ad6a856c5e4c9e593ac974df Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Wed, 3 Mar 2021 23:48:39 +0200 Subject: [PATCH 148/420] feat: use
to hide encrypted block --- res/css/structures/_ViewSource.scss | 4 +++ src/components/structures/ViewSource.js | 47 ++++++++++++++++--------- 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/res/css/structures/_ViewSource.scss b/res/css/structures/_ViewSource.scss index 0cb5dd8f3a..0126c16599 100644 --- a/res/css/structures/_ViewSource.scss +++ b/res/css/structures/_ViewSource.scss @@ -43,3 +43,7 @@ limitations under the License. word-wrap: break-word; white-space: pre-wrap; } + +.mx_ViewSource_details { + margin-top: 0.8em; +} diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index b57efe1dd3..ca6c0d4226 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -36,29 +36,42 @@ export default class ViewSource extends React.Component { render() { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const DecryptedSection = <> -
-
{_t("Decrypted event source")}
- - { JSON.stringify(this.props.decryptedContent, null, 2) } - - ; - - const OriginalSection = <> -
-
{_t("Original event source")}
- - { JSON.stringify(this.props.content, null, 2) } - - ; + let content; + if (this.props.isEncrypted) { + content = <> +
+ + {_t("Decrypted event source")} + + + { JSON.stringify(this.props.decryptedContent, null, 2) } + +
+
+ + {_t("Original event source")} + + + { JSON.stringify(this.props.content, null, 2) } + +
+ ; + } else { + content = <> +
{_t("Original event source")}
+ + { JSON.stringify(this.props.content, null, 2) } + + ; + } return (
Room ID: { this.props.roomId }
Event ID: { this.props.eventId }
- { this.props.isEncrypted && DecryptedSection } - { OriginalSection } +
+ { content }
); From 725162ee0012c85111859fa54c9058462945e761 Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Thu, 4 Mar 2021 00:09:00 +0200 Subject: [PATCH 149/420] fix: i18n strings --- src/i18n/strings/en_EN.json | 7 +++---- src/i18n/strings/en_US.json | 4 +--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b777adcf0c..c3752d7942 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2396,7 +2396,6 @@ "Cancel Sending": "Cancel Sending", "Forward Message": "Forward Message", "Pin Message": "Pin Message", - "View Decrypted Source": "View Decrypted Source", "Unhide Preview": "Unhide Preview", "Share Permalink": "Share Permalink", "Share Message": "Share Message", @@ -2652,6 +2651,8 @@ "User menu": "User menu", "Community and user menu": "Community and user menu", "Could not load user profile": "Could not load user profile", + "Decrypted event source": "Decrypted event source", + "Original event source": "Original event source", "Verify this login": "Verify this login", "Session verified": "Session verified", "Failed to send email": "Failed to send email", @@ -2875,7 +2876,5 @@ "Esc": "Esc", "Enter": "Enter", "Space": "Space", - "End": "End", - "Decrypted event source": "Decrypted event source", - "Original event source": "Original event source" + "End": "End" } diff --git a/src/i18n/strings/en_US.json b/src/i18n/strings/en_US.json index 9dc6d18f8a..a1275fb089 100644 --- a/src/i18n/strings/en_US.json +++ b/src/i18n/strings/en_US.json @@ -650,7 +650,5 @@ "Error upgrading room": "Error upgrading room", "Double check that your server supports the room version chosen and try again.": "Double check that your server supports the room version chosen and try again.", "Changes the avatar of the current room": "Changes the avatar of the current room", - "Changes your avatar in all rooms": "Changes your avatar in all rooms", - "Decrypted event source": "Decrypted event source", - "Original event source": "Original event source" + "Changes your avatar in all rooms": "Changes your avatar in all rooms" } From 1cb19554eb3a151706b6b964f0c42f61b58c7a23 Mon Sep 17 00:00:00 2001 From: Ayush Kumar <2580ayush2580@gmail.com> Date: Thu, 4 Mar 2021 14:03:02 +0530 Subject: [PATCH 150/420] Fix Bottom border of state counters is white on the dark theme --- res/css/views/rooms/_AuxPanel.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_AuxPanel.scss b/res/css/views/rooms/_AuxPanel.scss index 34ef5e01d4..17a6294bf0 100644 --- a/res/css/views/rooms/_AuxPanel.scss +++ b/res/css/views/rooms/_AuxPanel.scss @@ -17,7 +17,7 @@ limitations under the License. .m_RoomView_auxPanel_stateViews { padding: 5px; padding-left: 19px; - border-bottom: 1px solid #e5e5e5; + border-bottom: 1px solid $primary-hairline-color; } .m_RoomView_auxPanel_stateViews_span a { From f2d2a048e184f597acd5d605c83944e63ecdecb1 Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Thu, 4 Mar 2021 11:07:14 +0200 Subject: [PATCH 151/420] Ensure HostSignupDialog border colour matches light theme Ensure dialog borders are always white as the HostSignupDialog does not yet support dark mode or theming in general. In the future we might want to pass the theme to the called iframe, should some hosting provider have that need. --- res/css/views/dialogs/_HostSignupDialog.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/css/views/dialogs/_HostSignupDialog.scss b/res/css/views/dialogs/_HostSignupDialog.scss index 1378ac9053..ac4bc41951 100644 --- a/res/css/views/dialogs/_HostSignupDialog.scss +++ b/res/css/views/dialogs/_HostSignupDialog.scss @@ -19,6 +19,11 @@ limitations under the License. max-width: 580px; height: 80vh; max-height: 600px; + // Ensure dialog borders are always white as the HostSignupDialog + // does not yet support dark mode or theming in general. + // In the future we might want to pass the theme to the called + // iframe, should some hosting provider have that need. + background-color: #ffffff; .mx_HostSignupDialog_info { text-align: center; From 63944b9f6da84dc4b385cb3f7f3b995cabab56e4 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 4 Mar 2021 12:22:31 +0000 Subject: [PATCH 152/420] Add the new file --- src/Livestream.ts | 52 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/Livestream.ts diff --git a/src/Livestream.ts b/src/Livestream.ts new file mode 100644 index 0000000000..d4bed63dbd --- /dev/null +++ b/src/Livestream.ts @@ -0,0 +1,52 @@ +/* +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 { ClientWidgetApi } from "matrix-widget-api"; +import { MatrixClientPeg } from "./MatrixClientPeg"; +import SdkConfig from "./SdkConfig"; +import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions"; + +export function getConfigLivestreamUrl() { + return SdkConfig.get()["audioStreamUrl"]; +} + +async function createLiveStream(roomId: string) { + const openIdToken = await MatrixClientPeg.get().getOpenIdToken(); + + const url = getConfigLivestreamUrl() + "/createStream"; + + const response = await window.fetch(url, { + method: 'POST', + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + room_id: roomId, + openid_token: openIdToken, + }), + }); + + const respBody = response.json(); + return respBody['stream_id']; +} + +export async function startJitsiAudioLivestream(widgetMessaging: ClientWidgetApi, roomId: string) { + const streamId = await createLiveStream(roomId); + + widgetMessaging.transport.send(ElementWidgetActions.StartLiveStream, { + rtmpStreamKey: 'audioStream:' + streamId, + }); +} From ab4220b20d98d4e47574f945baee64bbf2ec3b48 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 4 Mar 2021 13:04:58 +0000 Subject: [PATCH 153/420] Defer auto-joining within spaces and switch to using `suggested` --- .../structures/SpaceRoomDirectory.tsx | 33 ++++++++++--------- src/i18n/strings/en_EN.json | 2 +- src/stores/SpaceStore.tsx | 3 +- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index 7f7b9dbb99..0a59ac0195 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -64,6 +64,7 @@ export interface ISpaceSummaryEvent { state_key: string; content: { order?: string; + suggested?: boolean; auto_join?: boolean; via?: string; }; @@ -91,7 +92,7 @@ const SubSpace: React.FC = ({ const name = space.name || space.canonical_alias || space.aliases?.[0] || _t("Unnamed Space"); const evContent = event?.getContent(); - const [autoJoin, _setAutoJoin] = useState(evContent?.auto_join); + const [suggested, _setSuggested] = useState(evContent?.suggested); const [removed, _setRemoved] = useState(!evContent?.via); const cli = MatrixClientPeg.get(); @@ -102,12 +103,12 @@ const SubSpace: React.FC = ({ let actions; if (editing && queueAction) { if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) { - const setAutoJoin = () => { - _setAutoJoin(v => { + const setSuggested = () => { + _setSuggested(v => { queueAction({ event, removed, - autoJoin: !v, + suggested: !v, }); return !v; }); @@ -118,7 +119,7 @@ const SubSpace: React.FC = ({ queueAction({ event, removed: !v, - autoJoin, + suggested, }); return !v; }); @@ -131,7 +132,7 @@ const SubSpace: React.FC = ({ } else { actions = - + ; } } else { @@ -180,8 +181,8 @@ const SubSpace: React.FC = ({ interface IAction { event: MatrixEvent; + suggested: boolean; removed: boolean; - autoJoin: boolean; } interface IRoomTileProps { @@ -197,7 +198,7 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli const name = room.name || room.canonical_alias || room.aliases?.[0] || _t("Unnamed Room"); const evContent = event?.getContent(); - const [autoJoin, _setAutoJoin] = useState(evContent?.auto_join); + const [suggested, _setSuggested] = useState(evContent?.suggested); const [removed, _setRemoved] = useState(!evContent?.via); const cli = MatrixClientPeg.get(); @@ -207,12 +208,12 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli let actions; if (editing && queueAction) { if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) { - const setAutoJoin = () => { - _setAutoJoin(v => { + const setSuggested = () => { + _setSuggested(v => { queueAction({ event, removed, - autoJoin: !v, + suggested: !v, }); return !v; }); @@ -223,7 +224,7 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli queueAction({ event, removed: !v, - autoJoin, + suggested, }); return !v; }); @@ -236,7 +237,7 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli } else { actions = - + ; } } else { @@ -441,10 +442,10 @@ const SpaceRoomDirectory: React.FC = ({ space, initialText = "", onFinis const onSaveButtonClicked = () => { // TODO setBusy - pendingActions.current.forEach(({event, autoJoin, removed}) => { + pendingActions.current.forEach(({event, suggested, removed}) => { const content = { ...event.getContent(), - auto_join: autoJoin, + suggested, }; if (removed) { @@ -459,7 +460,7 @@ const SpaceRoomDirectory: React.FC = ({ space, initialText = "", onFinis if (isEditing) { adminButton = - { _t("All users join by default") } + { _t("Promoted to users") } ; } else { adminButton = ; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e256e6bb2f..7b680b6590 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2596,7 +2596,7 @@ "You're in this space": "You're in this space", "You're in this room": "You're in this room", "Save changes": "Save changes", - "All users join by default": "All users join by default", + "Promoted to users": "Promoted to users", "Manage rooms": "Manage rooms", "Find a room...": "Find a room...", "Accept Invite": "Accept Invite", diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx index 8e0066da91..c334144d70 100644 --- a/src/stores/SpaceStore.tsx +++ b/src/stores/SpaceStore.tsx @@ -108,9 +108,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient { } } - public addRoomToSpace(space: Room, roomId: string, via: string[], autoJoin = false) { + public addRoomToSpace(space: Room, roomId: string, via: string[], suggested = false, autoJoin = false) { return this.matrixClient.sendStateEvent(space.roomId, EventType.SpaceChild, { via, + suggested, auto_join: autoJoin, }, roomId); } From dd792c3d7329400bf67c95cf671190203d54afd7 Mon Sep 17 00:00:00 2001 From: Ayush Kumar <2580ayush2580@gmail.com> Date: Thu, 4 Mar 2021 19:24:35 +0530 Subject: [PATCH 154/420] Fix Clicking on the avatar for opening member info requires pixel-perfect accuracy --- res/css/views/rooms/_GroupLayout.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_GroupLayout.scss b/res/css/views/rooms/_GroupLayout.scss index 903fabc8fd..818509785b 100644 --- a/res/css/views/rooms/_GroupLayout.scss +++ b/res/css/views/rooms/_GroupLayout.scss @@ -21,7 +21,7 @@ $left-gutter: 64px; .mx_EventTile { > .mx_SenderProfile { line-height: $font-20px; - padding-left: $left-gutter; + margin-left: $left-gutter; } > .mx_EventTile_line { From 0f1b7a001e1b943ff28acc664b009264cc7fd588 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 4 Mar 2021 17:52:49 +0000 Subject: [PATCH 155/420] Better error handling for streams Also use older youtubeStreamKey as it appears our jitsi doesn't support the newer one. --- src/Livestream.ts | 6 +++--- .../views/context_menus/WidgetContextMenu.tsx | 15 +++++++++++++-- src/i18n/strings/en_EN.json | 2 ++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Livestream.ts b/src/Livestream.ts index d4bed63dbd..cd8cdea179 100644 --- a/src/Livestream.ts +++ b/src/Livestream.ts @@ -39,14 +39,14 @@ async function createLiveStream(roomId: string) { }), }); - const respBody = response.json(); + const respBody = await response.json(); return respBody['stream_id']; } export async function startJitsiAudioLivestream(widgetMessaging: ClientWidgetApi, roomId: string) { const streamId = await createLiveStream(roomId); - widgetMessaging.transport.send(ElementWidgetActions.StartLiveStream, { - rtmpStreamKey: 'audioStream:' + streamId, + await widgetMessaging.transport.send(ElementWidgetActions.StartLiveStream, { + rtmpStreamKey: 'rtmp://audiostream.dummy/' + streamId, }); } diff --git a/src/components/views/context_menus/WidgetContextMenu.tsx b/src/components/views/context_menus/WidgetContextMenu.tsx index e7d1c02c66..0503df038a 100644 --- a/src/components/views/context_menus/WidgetContextMenu.tsx +++ b/src/components/views/context_menus/WidgetContextMenu.tsx @@ -28,6 +28,7 @@ import dis from "../../../dispatcher/dispatcher"; import SettingsStore from "../../../settings/SettingsStore"; import Modal from "../../../Modal"; import QuestionDialog from "../dialogs/QuestionDialog"; +import ErrorDialog from "../dialogs/ErrorDialog"; import {WidgetType} from "../../../widgets/WidgetType"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { Container, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; @@ -57,8 +58,18 @@ const WidgetContextMenu: React.FC = ({ let streamAudioStreamButton; if (getConfigLivestreamUrl() && (app.type === "m.jitsi" || app.type === "jitsi")) { - const onStreamAudioClick = () => { - startJitsiAudioLivestream(widgetMessaging, roomId); + const onStreamAudioClick = async () => { + try { + await startJitsiAudioLivestream(widgetMessaging, roomId); + } catch (err) { + console.log("Failed to start livestream", err); + // XXX: won't i18n well, but looks like widget api only support 'message'? + const message = err.message || _t("Unable to start audio streaming."); + Modal.createTrackedDialog('WidgetContext Menu', 'Livestream failed', ErrorDialog, { + title: _t('Failed to start livestream'), + description: message, + }); + } onFinished(); }; streamAudioStreamButton = Date: Thu, 4 Mar 2021 17:58:43 +0000 Subject: [PATCH 156/420] 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 f33c4368ef..2183a0c68f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2357,8 +2357,8 @@ "Set status": "Set status", "Set a new status...": "Set a new status...", "View Community": "View Community", - "Failed to start livestream": "Failed to start livestream", "Unable to start audio streaming.": "Unable to start audio streaming.", + "Failed to start livestream": "Failed to start livestream", "Start audio stream": "Start audio stream", "Take a picture": "Take a picture", "Delete Widget": "Delete Widget", From af5cfff51db48c4a96c5393a25c76675839e864d Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Thu, 4 Mar 2021 23:17:29 +0200 Subject: [PATCH 157/420] feat: edit button on View Source dialog reuse component SendCustomEvent swap it in place in the View Source dialog the Back button takes you to the View Source dialog, not the DevTools dialog do not display the flip toggle box for changing between State Event and Normal Event --- src/components/structures/ViewSource.js | 180 ++++++++++++++---- .../views/context_menus/MessageContextMenu.js | 14 +- .../views/dialogs/DevtoolsDialog.js | 7 +- 3 files changed, 152 insertions(+), 49 deletions(-) diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index ca6c0d4226..7fe862cff5 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -16,12 +16,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import PropTypes from 'prop-types'; -import SyntaxHighlight from '../views/elements/SyntaxHighlight'; -import {_t} from "../../languageHandler"; +import React from "react"; +import PropTypes from "prop-types"; +import SyntaxHighlight from "../views/elements/SyntaxHighlight"; +import { _t } from "../../languageHandler"; import * as sdk from "../../index"; - +import MatrixClientContext from "../../contexts/MatrixClientContext"; +import { SendCustomEvent } from "../views/dialogs/DevtoolsDialog"; export default class ViewSource extends React.Component { static propTypes = { @@ -31,48 +32,157 @@ export default class ViewSource extends React.Component { eventId: PropTypes.string.isRequired, isEncrypted: PropTypes.bool.isRequired, decryptedContent: PropTypes.object, + event: PropTypes.object.isRequired, // the MatrixEvent associated with the context menu }; + constructor(props) { + super(props); + + this.state = { + editComponent: null, + }; + } + + onBack() { + this.setState({ editComponent: null }); + } + + editEvent() { + const isStateEvent = this.props.event.isState(); + console.log("isStateEvent", isStateEvent); + if (isStateEvent) { + this.setState({ + editComponent: ( + + {(cli) => ( + this.onBack()} + inputs={{ + eventType: this.props.event.getType(), + evContent: JSON.stringify( + this.props.event.getContent(), + null, + "\t" + ), + stateKey: this.props.event.getStateKey(), + }} + /> + )} + + ), + }); + } else { + // send an edit-message event + // prefill the "m.new_content" field + const originalContent = this.props.event.getContent(); + const originalEventId = this.props.eventId; + const content = { + ...originalContent, + "m.new_content": originalContent, + "m.relates_to": { + rel_type: "m.replace", + event_id: originalEventId, + }, + }; + this.setState({ + editComponent: ( + + {(cli) => ( + this.onBack()} + inputs={{ + eventType: this.props.event.getType(), + evContent: JSON.stringify( + content, + null, + "\t" + ), + }} + /> + )} + + ), + }); + } + } + render() { - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog"); let content; if (this.props.isEncrypted) { - content = <> -
- - {_t("Decrypted event source")} - - - { JSON.stringify(this.props.decryptedContent, null, 2) } - -
-
- - {_t("Original event source")} - - - { JSON.stringify(this.props.content, null, 2) } - -
- ; + content = ( + <> +
+ + + {_t("Decrypted event source")} + + + + {JSON.stringify( + this.props.decryptedContent, + null, + 2 + )} + +
+
+ + + {_t("Original event source")} + + + + {JSON.stringify(this.props.content, null, 2)} + +
+ + ); } else { - content = <> -
{_t("Original event source")}
- - { JSON.stringify(this.props.content, null, 2) } - - ; + content = ( + <> +
+ {_t("Original event source")} +
+ + {JSON.stringify(this.props.content, null, 2)} + + + ); } + const isEditing = this.state.editComponent !== null; + console.log(isEditing); + return ( - -
-
Room ID: { this.props.roomId }
-
Event ID: { this.props.eventId }
+ +
+
+ Room ID: {this.props.roomId} +
+
+ Event ID: {this.props.eventId} +
- { content } + {isEditing ? this.state.editComponent : content}
+ {!isEditing && ( +
+ +
+ )} ); } diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index b002d1ec62..a1c111b19c 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -130,20 +130,10 @@ export default class MessageContextMenu extends React.Component { roomId: ev.getRoomId(), eventId: ev.getId(), content: ev.event, + event: ev, isEncrypted: this.props.mxEvent.getType() !== this.props.mxEvent.getWireType(), - decryptedContent: ev._clearEvent, - }, 'mx_Dialog_viewsource'); - this.closeMenu(); - }; - - onViewClearSourceClick = () => { - const ev = this.props.mxEvent.replacingEvent() || this.props.mxEvent; - const ViewSource = sdk.getComponent('structures.ViewSource'); - Modal.createTrackedDialog('View Clear Event Source', '', ViewSource, { - roomId: ev.getRoomId(), - eventId: ev.getId(), // FIXME: _clearEvent is private - content: ev._clearEvent, + decryptedContent: ev._clearEvent, }, 'mx_Dialog_viewsource'); this.closeMenu(); }; diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js index 814378bb51..5d571461fc 100644 --- a/src/components/views/dialogs/DevtoolsDialog.js +++ b/src/components/views/dialogs/DevtoolsDialog.js @@ -73,13 +73,14 @@ class GenericEditor extends React.PureComponent { } } -class SendCustomEvent extends GenericEditor { +export class SendCustomEvent extends GenericEditor { static getLabel() { return _t('Send Custom Event'); } static propTypes = { onBack: PropTypes.func.isRequired, room: PropTypes.instanceOf(Room).isRequired, forceStateEvent: PropTypes.bool, + forceGeneralEvent: PropTypes.bool, inputs: PropTypes.object, }; @@ -140,6 +141,8 @@ class SendCustomEvent extends GenericEditor {
; } + const showTglFlip = !this.state.message && !this.props.forceStateEvent && !this.props.forceGeneralEvent; + return
@@ -155,7 +158,7 @@ class SendCustomEvent extends GenericEditor {
{ !this.state.message && } - { !this.state.message && !this.props.forceStateEvent &&
+ { showTglFlip &&
} From 288d98daede9f1ea6b1045e6de930c61f5f14c0e Mon Sep 17 00:00:00 2001 From: Panagiotis <27917356+panoschal@users.noreply.github.com> Date: Fri, 5 Mar 2021 00:07:59 +0200 Subject: [PATCH 158/420] chore: format, lint --- src/components/structures/ViewSource.js | 58 ++++--------------- .../views/dialogs/DevtoolsDialog.js | 2 +- 2 files changed, 13 insertions(+), 47 deletions(-) diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js index 7fe862cff5..a31876ea76 100644 --- a/src/components/structures/ViewSource.js +++ b/src/components/structures/ViewSource.js @@ -61,11 +61,7 @@ export default class ViewSource extends React.Component { onBack={() => this.onBack()} inputs={{ eventType: this.props.event.getType(), - evContent: JSON.stringify( - this.props.event.getContent(), - null, - "\t" - ), + evContent: JSON.stringify(this.props.event.getContent(), null, "\t"), stateKey: this.props.event.getStateKey(), }} /> @@ -97,11 +93,7 @@ export default class ViewSource extends React.Component { onBack={() => this.onBack()} inputs={{ eventType: this.props.event.getType(), - evContent: JSON.stringify( - content, - null, - "\t" - ), + evContent: JSON.stringify(content, null, "\t"), }} /> )} @@ -120,39 +112,23 @@ export default class ViewSource extends React.Component { <>
- - {_t("Decrypted event source")} - + {_t("Decrypted event source")} - - {JSON.stringify( - this.props.decryptedContent, - null, - 2 - )} - + {JSON.stringify(this.props.decryptedContent, null, 2)}
- - {_t("Original event source")} - + {_t("Original event source")} - - {JSON.stringify(this.props.content, null, 2)} - + {JSON.stringify(this.props.content, null, 2)}
); } else { content = ( <> -
- {_t("Original event source")} -
- - {JSON.stringify(this.props.content, null, 2)} - +
{_t("Original event source")}
+ {JSON.stringify(this.props.content, null, 2)} ); } @@ -161,26 +137,16 @@ export default class ViewSource extends React.Component { console.log(isEditing); return ( - +
-
- Room ID: {this.props.roomId} -
-
- Event ID: {this.props.eventId} -
+
Room ID: {this.props.roomId}
+
Event ID: {this.props.eventId}
{isEditing ? this.state.editComponent : content}
{!isEditing && (
- +
)} diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.js index 5d571461fc..82f2df6534 100644 --- a/src/components/views/dialogs/DevtoolsDialog.js +++ b/src/components/views/dialogs/DevtoolsDialog.js @@ -142,7 +142,7 @@ export class SendCustomEvent extends GenericEditor { } const showTglFlip = !this.state.message && !this.props.forceStateEvent && !this.props.forceGeneralEvent; - + return
From 5d6e3d5711b56eddb764e0ee8924b20c4e5ed5e2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 4 Mar 2021 20:07:48 -0700 Subject: [PATCH 159/420] UI refresh for uploaded files Fixes https://github.com/vector-im/element-web/issues/16557 Fixes https://github.com/vector-im/element-web/issues/9482 (technically) There's two changes in this: 1. The actual file body in the timeline now has a placeholder thing. 2. We're intentionally dropping all the "Travis uploaded a file" sender profile states. --- res/css/views/messages/_MFileBody.scss | 43 +++++++++++++++++++ res/themes/dark/css/_dark.scss | 4 ++ res/themes/legacy-dark/css/_legacy-dark.scss | 4 ++ .../legacy-light/css/_legacy-light.scss | 4 ++ res/themes/light/css/_light.scss | 4 ++ src/components/views/messages/MAudioBody.js | 2 +- src/components/views/messages/MFileBody.js | 26 ++++++++++- src/components/views/messages/MImageBody.js | 2 +- src/components/views/messages/MVideoBody.tsx | 2 +- .../views/messages/SenderProfile.js | 10 +---- src/components/views/rooms/EventTile.js | 7 +-- src/i18n/strings/en_EN.json | 3 -- 12 files changed, 88 insertions(+), 23 deletions(-) diff --git a/res/css/views/messages/_MFileBody.scss b/res/css/views/messages/_MFileBody.scss index 6cbce68745..e219c0c5e4 100644 --- a/res/css/views/messages/_MFileBody.scss +++ b/res/css/views/messages/_MFileBody.scss @@ -45,3 +45,46 @@ limitations under the License. * big the content of the iframe is. */ height: 1.5em; } + +.mx_MFileBody_info { + background-color: $message-body-panel-bg-color; + border-radius: 4px; + width: 270px; + padding: 8px; + color: $message-body-panel-fg-color; + + .mx_MFileBody_info_icon { + background-color: $message-body-panel-icon-bg-color; + border-radius: 20px; + display: inline-block; + width: 32px; + height: 32px; + position: relative; + vertical-align: middle; + margin-right: 12px; + + &::before { + content: ''; + mask-repeat: no-repeat; + mask-position: center; + mask-size: cover; + mask-image: url('$(res)/img/element-icons/room/composer/attach.svg'); + background-color: $message-body-panel-fg-color; + width: 13px; + height: 15px; + + position: absolute; + top: 8px; + left: 9px; + } + } + + .mx_MFileBody_info_filename { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + display: inline-block; + width: calc(100% - 32px - 12px); // 32px icon, 12px margin on the icon + vertical-align: middle; + } +} diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 0de5e69782..a4648d2051 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -203,6 +203,10 @@ $breadcrumb-placeholder-bg-color: #272c35; $user-tile-hover-bg-color: $header-panel-bg-color; +$message-body-panel-bg-color: #21262c82; +$message-body-panel-icon-bg-color: #8e99a4; +$message-body-panel-fg-color: $primary-fg-color; + // Appearance tab colors $appearance-tab-border-color: $room-highlight-color; diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 8c5f20178b..8752e41d18 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -198,6 +198,10 @@ $breadcrumb-placeholder-bg-color: #272c35; $user-tile-hover-bg-color: $header-panel-bg-color; +$message-body-panel-bg-color: #21262c82; +$message-body-panel-icon-bg-color: #8e99a4; +$message-body-panel-fg-color: $primary-fg-color; + // Appearance tab colors $appearance-tab-border-color: $room-highlight-color; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 3ba10a68ea..58b75f32aa 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -322,6 +322,10 @@ $breadcrumb-placeholder-bg-color: #e8eef5; $user-tile-hover-bg-color: $header-panel-bg-color; +$message-body-panel-bg-color: #e3e8f082; +$message-body-panel-icon-bg-color: #ffffff; +$message-body-panel-fg-color: $muted-fg-color; + // FontSlider colors $appearance-tab-border-color: $input-darker-bg-color; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 76bf2ddc21..fd2bfe4628 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -323,6 +323,10 @@ $breadcrumb-placeholder-bg-color: #e8eef5; $user-tile-hover-bg-color: $header-panel-bg-color; +$message-body-panel-bg-color: #e3e8f082; +$message-body-panel-icon-bg-color: #ffffff; +$message-body-panel-fg-color: $muted-fg-color; + // FontSlider colors $appearance-tab-border-color: $input-darker-bg-color; diff --git a/src/components/views/messages/MAudioBody.js b/src/components/views/messages/MAudioBody.js index 37f85a108f..587dee4513 100644 --- a/src/components/views/messages/MAudioBody.js +++ b/src/components/views/messages/MAudioBody.js @@ -105,7 +105,7 @@ export default class MAudioBody extends React.Component { return ( ); } diff --git a/src/components/views/messages/MFileBody.js b/src/components/views/messages/MFileBody.js index 770cd4fff3..676f0b7986 100644 --- a/src/components/views/messages/MFileBody.js +++ b/src/components/views/messages/MFileBody.js @@ -126,6 +126,12 @@ export default class MFileBody extends React.Component { onHeightChanged: PropTypes.func, /* the shape of the tile, used */ tileShape: PropTypes.string, + /* whether or not to show the default placeholder for the file. Defaults to true. */ + showGenericPlaceholder: PropTypes.bool, + }; + + static defaultProps = { + showGenericPlaceholder: true, }; constructor(props) { @@ -145,9 +151,10 @@ export default class MFileBody extends React.Component { * link text. * * @param {Object} content The "content" key of the matrix event. + * @param {boolean} withSize Whether to include size information. Default true. * @return {string} the human readable link text for the attachment. */ - presentableTextForFile(content) { + presentableTextForFile(content, withSize = true) { let linkText = _t("Attachment"); if (content.body && content.body.length > 0) { // The content body should be the name of the file including a @@ -155,7 +162,7 @@ export default class MFileBody extends React.Component { linkText = content.body; } - if (content.info && content.info.size) { + if (content.info && content.info.size && withSize) { // If we know the size of the file then add it as human readable // string to the end of the link text so that the user knows how // big a file they are downloading. @@ -218,6 +225,16 @@ export default class MFileBody extends React.Component { const fileSize = content.info ? content.info.size : null; const fileType = content.info ? content.info.mimetype : "application/octet-stream"; + let placeholder = null; + if (this.props.showGenericPlaceholder) { + placeholder = ( +
+ + {this.presentableTextForFile(content, false)} +
+ ); + } + if (isEncrypted) { if (this.state.decryptedBlob === null) { // Need to decrypt the attachment @@ -248,6 +265,7 @@ export default class MFileBody extends React.Component { // but it is not guaranteed between various browsers' settings. return ( + {placeholder}
{ _t("Decrypt %(text)s", { text: text }) } @@ -278,6 +296,7 @@ export default class MFileBody extends React.Component { // If the attachment is encrypted then put the link inside an iframe. return ( + {placeholder}
{ /* @@ -346,6 +365,7 @@ export default class MFileBody extends React.Component { if (this.props.tileShape === "file_grid") { return ( + {placeholder}
{ fileName } @@ -359,6 +379,7 @@ export default class MFileBody extends React.Component { } else { return ( + {placeholder}
@@ -371,6 +392,7 @@ export default class MFileBody extends React.Component { } else { const extra = text ? (': ' + text) : ''; return + {placeholder} { _t("Invalid file%(extra)s", { extra: extra }) } ; } diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js index a8cdc17abf..771d12accd 100644 --- a/src/components/views/messages/MImageBody.js +++ b/src/components/views/messages/MImageBody.js @@ -452,7 +452,7 @@ export default class MImageBody extends React.Component { // Overidden by MStickerBody getFileBody() { - return ; + return ; } render() { diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index 9628f11809..ce4a4eda76 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -243,7 +243,7 @@ export default class MVideoBody extends React.PureComponent { onPlay={this.videoOnPlay} > - + ); } diff --git a/src/components/views/messages/SenderProfile.js b/src/components/views/messages/SenderProfile.js index afe2d6d118..e2bb1ff38d 100644 --- a/src/components/views/messages/SenderProfile.js +++ b/src/components/views/messages/SenderProfile.js @@ -25,7 +25,6 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; export default class SenderProfile extends React.Component { static propTypes = { mxEvent: PropTypes.object.isRequired, // event whose sender we're showing - text: PropTypes.string, // Text to show. Defaults to sender name onClick: PropTypes.func, }; @@ -118,17 +117,10 @@ export default class SenderProfile extends React.Component { { flair } ; - const content = this.props.text ? - - - { _t(this.props.text, { senderName: () => nameElem }) } - - : nameFlair; - return (
- { content } + { nameFlair }
); diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 87fb190678..2009eb6d1c 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -768,15 +768,10 @@ export default class EventTile extends React.Component { } if (needsSenderProfile) { - let text = null; if (!this.props.tileShape || this.props.tileShape === 'reply' || this.props.tileShape === 'reply_preview') { - if (msgtype === 'm.image') text = _td('%(senderName)s sent an image'); - else if (msgtype === 'm.video') text = _td('%(senderName)s sent a video'); - else if (msgtype === 'm.file') text = _td('%(senderName)s uploaded a file'); sender = ; + enableFlair={this.props.enableFlair} />; } else { sender = ; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index a1999acb3b..f3232416b1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1430,9 +1430,6 @@ "Edit message": "Edit message", "Mod": "Mod", "This event could not be displayed": "This event could not be displayed", - "%(senderName)s sent an image": "%(senderName)s sent an image", - "%(senderName)s sent a video": "%(senderName)s sent a video", - "%(senderName)s uploaded a file": "%(senderName)s uploaded a file", "Your key share request has been sent - please check your other sessions for key share requests.": "Your key share request has been sent - please check your other sessions for key share requests.", "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.", "If your other sessions do not have the key for this message you will not be able to decrypt them.": "If your other sessions do not have the key for this message you will not be able to decrypt them.", From 8d143331a8d8fa72c89b8730f70b9b3ff9ba7009 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 4 Mar 2021 20:10:47 -0700 Subject: [PATCH 160/420] Appease the linter --- src/components/views/messages/SenderProfile.js | 1 - src/components/views/rooms/EventTile.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/messages/SenderProfile.js b/src/components/views/messages/SenderProfile.js index e2bb1ff38d..d2db05252c 100644 --- a/src/components/views/messages/SenderProfile.js +++ b/src/components/views/messages/SenderProfile.js @@ -18,7 +18,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import Flair from '../elements/Flair.js'; import FlairStore from '../../../stores/FlairStore'; -import { _t } from '../../../languageHandler'; import {getUserNameColorClass} from '../../../utils/FormattingUtils'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 2009eb6d1c..198b3427bc 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -22,7 +22,7 @@ import React, {createRef} from 'react'; import PropTypes from 'prop-types'; import classNames from "classnames"; import {EventType} from "matrix-js-sdk/src/@types/event"; -import { _t, _td } from '../../../languageHandler'; +import { _t } from '../../../languageHandler'; import * as TextForEvent from "../../../TextForEvent"; import * as sdk from "../../../index"; import dis from '../../../dispatcher/dispatcher'; From d7310bc5b36cb66abbee507ff74cea3f56592c70 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 4 Mar 2021 20:17:29 -0700 Subject: [PATCH 161/420] Remove dead classes --- res/css/views/rooms/_IRCLayout.scss | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss index 792c2f1f58..21baa795e6 100644 --- a/res/css/views/rooms/_IRCLayout.scss +++ b/res/css/views/rooms/_IRCLayout.scss @@ -181,8 +181,7 @@ $irc-line-height: $font-18px; > span { display: flex; - > .mx_SenderProfile_name, - > .mx_SenderProfile_aux { + > .mx_SenderProfile_name { overflow: hidden; text-overflow: ellipsis; min-width: var(--name-width); @@ -212,8 +211,7 @@ $irc-line-height: $font-18px; background: transparent; > span { - > .mx_SenderProfile_name, - > .mx_SenderProfile_aux { + > .mx_SenderProfile_name { min-width: inherit; } } From c80cbc38dd28e89d1b643189acb9c659914989dc Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 5 Mar 2021 10:32:54 +0000 Subject: [PATCH 162/420] Use helper class (It did not need imports) Co-authored-by: Travis Ralston --- src/components/views/context_menus/WidgetContextMenu.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/context_menus/WidgetContextMenu.tsx b/src/components/views/context_menus/WidgetContextMenu.tsx index 0503df038a..03e63edbcc 100644 --- a/src/components/views/context_menus/WidgetContextMenu.tsx +++ b/src/components/views/context_menus/WidgetContextMenu.tsx @@ -57,7 +57,7 @@ const WidgetContextMenu: React.FC = ({ const canModify = userWidget || WidgetUtils.canUserModifyWidgets(roomId); let streamAudioStreamButton; - if (getConfigLivestreamUrl() && (app.type === "m.jitsi" || app.type === "jitsi")) { + if (getConfigLivestreamUrl() && WidgetType.JITSI.matches(app.type)) { const onStreamAudioClick = async () => { try { await startJitsiAudioLivestream(widgetMessaging, roomId); @@ -199,4 +199,3 @@ const WidgetContextMenu: React.FC = ({ }; export default WidgetContextMenu; - From 8bcf0f08385368ce0dce69510e814f478aa90e2c Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 5 Mar 2021 10:34:03 +0000 Subject: [PATCH 163/420] console.error Co-authored-by: Travis Ralston --- src/components/views/context_menus/WidgetContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/context_menus/WidgetContextMenu.tsx b/src/components/views/context_menus/WidgetContextMenu.tsx index 03e63edbcc..623fe04f2f 100644 --- a/src/components/views/context_menus/WidgetContextMenu.tsx +++ b/src/components/views/context_menus/WidgetContextMenu.tsx @@ -62,7 +62,7 @@ const WidgetContextMenu: React.FC = ({ try { await startJitsiAudioLivestream(widgetMessaging, roomId); } catch (err) { - console.log("Failed to start livestream", err); + console.error("Failed to start livestream", err); // XXX: won't i18n well, but looks like widget api only support 'message'? const message = err.message || _t("Unable to start audio streaming."); Modal.createTrackedDialog('WidgetContext Menu', 'Livestream failed', ErrorDialog, { From 572f15522f28320f671980a5892ec2cf10fd0d8e Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 5 Mar 2021 10:40:11 +0000 Subject: [PATCH 164/420] use a constant --- src/Livestream.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Livestream.ts b/src/Livestream.ts index cd8cdea179..2389132762 100644 --- a/src/Livestream.ts +++ b/src/Livestream.ts @@ -23,6 +23,9 @@ export function getConfigLivestreamUrl() { return SdkConfig.get()["audioStreamUrl"]; } +// Dummy rtmp URL used to signal that we want a special audio-only stream +const AUDIOSTREAM_DUMMY_URL = 'rtmp://audiostream.dummy/'; + async function createLiveStream(roomId: string) { const openIdToken = await MatrixClientPeg.get().getOpenIdToken(); @@ -47,6 +50,6 @@ export async function startJitsiAudioLivestream(widgetMessaging: ClientWidgetApi const streamId = await createLiveStream(roomId); await widgetMessaging.transport.send(ElementWidgetActions.StartLiveStream, { - rtmpStreamKey: 'rtmp://audiostream.dummy/' + streamId, + rtmpStreamKey: AUDIOSTREAM_DUMMY_URL + streamId, }); } From 1c1d239d5b0853e2b6ee56f823020655d2a68a2b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 5 Mar 2021 13:19:06 +0000 Subject: [PATCH 165/420] Add Edge to the targets list Part of https://github.com/vector-im/element-web/issues/9175 --- babel.config.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/babel.config.js b/babel.config.js index d5a97d56ce..0a3a34a391 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,12 +3,15 @@ module.exports = { "presets": [ ["@babel/preset-env", { "targets": [ - "last 2 Chrome versions", "last 2 Firefox versions", "last 2 Safari versions" + "last 2 Chrome versions", + "last 2 Firefox versions", + "last 2 Safari versions", + "last 2 Edge versions", ], }], "@babel/preset-typescript", "@babel/preset-flow", - "@babel/preset-react" + "@babel/preset-react", ], "plugins": [ ["@babel/plugin-proposal-decorators", {legacy: true}], @@ -18,6 +21,6 @@ module.exports = { "@babel/plugin-proposal-object-rest-spread", "@babel/plugin-transform-flow-comments", "@babel/plugin-syntax-dynamic-import", - "@babel/plugin-transform-runtime" - ] + "@babel/plugin-transform-runtime", + ], }; From 35dbc82b87f5aba8b5ab91afa4a7afb6d0ab5a4a Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 5 Mar 2021 15:01:48 +0000 Subject: [PATCH 166/420] Add issue comments --- src/components/structures/LoggedInView.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 508b7f05e7..5acc8c6891 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -93,6 +93,8 @@ interface IProps { } interface IUsageLimit { + // "hs_disabled" is NOT a specced string, but is used in Synapse + // This is tracked over at https://github.com/matrix-org/synapse/issues/9237 // eslint-disable-next-line camelcase limit_type: "monthly_active_user" | "hs_disabled" | string; // eslint-disable-next-line camelcase @@ -102,6 +104,8 @@ interface IUsageLimit { interface IState { syncErrorData?: { error: { + // This is not specced, but used in Synapse. See + // https://github.com/matrix-org/synapse/issues/9237#issuecomment-768238922 data: IUsageLimit; errcode: string; }; From e5b03488d890a3c289cd06d0e2ab8dd11bbb77ce Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 5 Mar 2021 17:52:51 +0000 Subject: [PATCH 167/420] Fix widget resizing There was a line of CSS that set pointer-events: none on widget iframes whilst they were being resized to stop iframes swallowing the mousemove/up events while dragging the resize handle, but a) all widgets are now in a persisted element wrapper and therefore not in the right place in the DOM to get that CSS and b) that only got set when resizing the whole aps drawer vertically, not dragging the handle between apps to change the width distribution. Add a pointer events prop to AppTile to allow the pointer-events style to be set by the parent, and set it when dragging either resize handle. Fixes https://github.com/vector-im/element-web/issues/16473 --- res/css/views/rooms/_AppsDrawer.scss | 5 ----- src/components/views/elements/AppTile.js | 14 ++++++++++---- src/components/views/rooms/AppsDrawer.js | 14 +++++++++++++- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss index 492ed95973..fd80836237 100644 --- a/res/css/views/rooms/_AppsDrawer.scss +++ b/res/css/views/rooms/_AppsDrawer.scss @@ -370,11 +370,6 @@ $MinWidth: 240px; display: none; } -/* Avoid apptile iframes capturing mouse event focus when resizing */ -.mx_AppsDrawer_resizing iframe { - pointer-events: none; -} - .mx_AppsDrawer_resizing .mx_AppTile_persistedWrapper { z-index: 1; } diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 213351889f..747d00a8df 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -328,6 +328,10 @@ export default class AppTile extends React.Component { const iframeFeatures = "microphone; camera; encrypted-media; autoplay; display-capture;"; const appTileBodyClass = 'mx_AppTileBody' + (this.props.miniMode ? '_mini ' : ' '); + const appTileBodyStyles = {}; + if (this.props.pointerEvents) { + appTileBodyStyles['pointer-events'] = this.props.pointerEvents; + } const loadingElement = (
@@ -338,7 +342,7 @@ export default class AppTile extends React.Component { // only possible for room widgets, can assert this.props.room here const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId); appTileBody = ( -
+
+
{ loadingElement }
); } else { if (this.isMixedContent()) { appTileBody = ( -
+
); } else { appTileBody = ( -
+
{ this.state.loading && loadingElement }