diff --git a/res/css/structures/_UserMenu.pcss b/res/css/structures/_UserMenu.pcss index f25c15e48e6..3d37fa73a66 100644 --- a/res/css/structures/_UserMenu.pcss +++ b/res/css/structures/_UserMenu.pcss @@ -200,6 +200,10 @@ limitations under the License. mask-image: url("$(res)/img/element-icons/settings.svg"); } + .mx_UserMenu_iconInfo::before { + mask-image: url("$(res)/img/element-icons/info.svg"); + } + .mx_UserMenu_iconMessage::before { mask-image: url("$(res)/img/element-icons/feedback.svg"); } diff --git a/res/css/views/auth/_CompleteSecurityBody.pcss b/res/css/views/auth/_CompleteSecurityBody.pcss index 53d5988c6dd..9b8ad77b146 100644 --- a/res/css/views/auth/_CompleteSecurityBody.pcss +++ b/res/css/views/auth/_CompleteSecurityBody.pcss @@ -16,7 +16,12 @@ limitations under the License. */ .mx_CompleteSecurityBody { + /* :TCHAP: width: 600px; + */ + width: 660px; + /* end :TCHAP: */ + color: $authpage-primary-color; background-color: $background; border-radius: 4px; diff --git a/res/css/views/directory/_NetworkDropdown.pcss b/res/css/views/directory/_NetworkDropdown.pcss index 8dbd7f020db..0b9172c015f 100644 --- a/res/css/views/directory/_NetworkDropdown.pcss +++ b/res/css/views/directory/_NetworkDropdown.pcss @@ -17,6 +17,7 @@ limitations under the License. .mx_NetworkDropdown_wrapper .mx_ContextualMenu { .mx_GenericDropdownMenu_Option { &.mx_GenericDropdownMenu_Option--header { + display:none; padding-top: $spacing-12; padding-bottom: $spacing-4; min-width: 160px; @@ -45,12 +46,14 @@ limitations under the License. } .mx_GenericDropdownMenu_divider { + display:none; margin-top: $spacing-4; margin-bottom: $spacing-4; } } .mx_NetworkDropdown_addServer { + display:none; font-weight: normal; font-size: $font-15px; } diff --git a/res/css/views/rooms/_RoomHeader.pcss b/res/css/views/rooms/_RoomHeader.pcss index bc66cd21417..a5330afe293 100644 --- a/res/css/views/rooms/_RoomHeader.pcss +++ b/res/css/views/rooms/_RoomHeader.pcss @@ -116,3 +116,17 @@ limitations under the License. .mx_RoomHeader .mx_BaseAvatar { flex-shrink: 0; } + +/* :tchap: largely inspired from ;mx_RoomHeader_topic but I couldn't get this add-on to work with $variable */ +.tc_RoomHeader_external { + color: var(--external-color); + width: 80px; + font-weight: 400; + font-size: 0.8125rem; + line-height: 0.8125rem; + max-height: calc(0.8125rem * 2); + overflow: hidden; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + display: -webkit-box; +} diff --git a/src/IConfigOptions.ts b/src/IConfigOptions.ts index 501d8a3bd64..19c2f3df5a1 100644 --- a/src/IConfigOptions.ts +++ b/src/IConfigOptions.ts @@ -211,3 +211,14 @@ export interface ISsoRedirectOptions { immediate?: boolean; on_welcome_page?: boolean; } + +/* :tchap: +* Add tchap specific options to IConfigOptions. Both interfaces get merged in compilation. https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces +* IConfigOptions declares which options can get retrieved with SdkConfig, if not declared SdkConfig complains it does not know the parameter. +*/ +export interface IConfigOptions { + tchap_features?: { + feature_email_notification?:[string]//activate email notification on a list of home servers, ie : "dev01.tchap.incubateur.net" + } +} +//end :tchap: \ No newline at end of file diff --git a/src/IdentityAuthClient.tsx b/src/IdentityAuthClient.tsx index 9dc6ebd8439..133b3c2206a 100644 --- a/src/IdentityAuthClient.tsx +++ b/src/IdentityAuthClient.tsx @@ -29,6 +29,7 @@ import { } from "./utils/IdentityServerUtils"; import QuestionDialog from "./components/views/dialogs/QuestionDialog"; import { abbreviateUrl } from "./utils/UrlUtils"; +import TchapUIFeature from "../../../src/tchap/util/TchapUIFeature"; export class AbortedIdentityActionError extends Error {} @@ -135,6 +136,18 @@ export default class IdentityAuthClient { throw e; } + // :TCHAP: no need confirmation of Terms and Conditions to set a default identity server as we trust our backend servers + if (TchapUIFeature.autoAcceptTermsAndConditions){ + if ( + !this.tempClient && + !doesAccountDataHaveIdentityServer(this.matrixClient) + ) { + setToDefaultIdentityServer(this.matrixClient); + } + return; + } + // end :TCHAP: + if ( !this.tempClient && !doesAccountDataHaveIdentityServer(this.matrixClient) && diff --git a/src/Terms.ts b/src/Terms.ts index afa65248280..7f54dbb8be8 100644 --- a/src/Terms.ts +++ b/src/Terms.ts @@ -20,6 +20,11 @@ import { logger } from "matrix-js-sdk/src/logger"; import Modal from "./Modal"; import TermsDialog from "./components/views/dialogs/TermsDialog"; +import { + doesAccountDataHaveIdentityServer, + setToDefaultIdentityServer, +} from './utils/IdentityServerUtils'; +import TchapUIFeature from "../../../src/tchap/util/TchapUIFeature"; export class TermsNotSignedError extends Error {} @@ -83,6 +88,15 @@ export async function startTermsFlow( services: Service[], interactionCallback: TermsInteractionCallback = dialogTermsInteractionCallback, ): Promise { + // :TCHAP: no need to go through Terms flow as we trust our backend servers + if (TchapUIFeature.autoAcceptTermsAndConditions){ + if (!doesAccountDataHaveIdentityServer(client)) { + setToDefaultIdentityServer(client); + } + return; + } + // end :TCHAP: + const termsPromises = services.map((s) => client.getTerms(s.serviceType, s.baseUrl)); /* diff --git a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx index 036fb5038b3..bb1d8cd6ff7 100644 --- a/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx +++ b/src/async-components/views/dialogs/security/CreateSecretStorageDialog.tsx @@ -211,13 +211,38 @@ export default class CreateSecretStorageDialog extends React.PureComponent ); } else { @@ -760,6 +798,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent + {/* tchap: add this

*/} +

{_t("This is your recovery key")}

+

{_t("Warning: this is the only time this code will be displayed!")}

+ {/* end tchap */}

{_t("settings|key_backup|setup_secure_backup|security_key_safety_reminder")}

@@ -767,6 +809,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent{this.recoveryKey?.encodedPrivateKey}
+ {/* :TCHAP: remove + end :TCHAP: */} { return; } + //:tchap: add a screen to open user tab security + if (screen === TchapUrls.secureBackupFragment) { + //open the security tab + //there is no anchor to sauvegarde-automatique subection + const payload: OpenToTabPayload = { action: Action.ViewUserSettings, initialTabId: UserTab.Security }; + dis.dispatch(payload); + } else + //:tchap: end + if (screen === "register") { dis.dispatch({ action: "start_registration", diff --git a/src/components/structures/RoomSearchView.tsx b/src/components/structures/RoomSearchView.tsx index bfa993059bf..245fc75ee3b 100644 --- a/src/components/structures/RoomSearchView.tsx +++ b/src/components/structures/RoomSearchView.tsx @@ -36,6 +36,7 @@ import ResizeNotifier from "../../utils/ResizeNotifier"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks"; import RoomContext from "../../contexts/RoomContext"; +import Tchapi18nUtils from "../../../../../src/tchap/i18n/Tchapi18nUtils"; const DEBUG = false; let debuglog = function (msg: string): void {}; @@ -136,7 +137,8 @@ export const RoomSearchView = forwardRef( logger.error("Search failed", error); Modal.createDialog(ErrorDialog, { title: _t("error_dialog|search_failed|title"), - description: error?.message ?? _t("error_dialog|search_failed|server_unavailable"), + // :TCHAP: description: error?.message ?? _t("error_dialog|search_failed|server_unavailable"), + description: error?.message ?? Tchapi18nUtils.getServerDownMessage(), }); return false; }, diff --git a/src/components/structures/RoomStatusBar.tsx b/src/components/structures/RoomStatusBar.tsx index 311f6b89b55..ce2f3f87aed 100644 --- a/src/components/structures/RoomStatusBar.tsx +++ b/src/components/structures/RoomStatusBar.tsx @@ -29,6 +29,7 @@ import InlineSpinner from "../views/elements/InlineSpinner"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import { RoomStatusBarUnsentMessages } from "./RoomStatusBarUnsentMessages"; import ExternalLink from "../views/elements/ExternalLink"; +import Tchapi18nUtils from '../../../../../src/tchap/i18n/Tchapi18nUtils'; // :TCHAP: const STATUS_BAR_HIDDEN = 0; const STATUS_BAR_EXPANDED = 1; @@ -270,7 +271,9 @@ export default class RoomStatusBar extends React.PureComponent {
- {_t("room|status_bar|server_connectivity_lost_title")} + {/* :TCHAP: _t("room|status_bar|server_connectivity_lost_title") */} + {Tchapi18nUtils.getServerDownMessage()} + {/* end :TCHAP: */}
{_t("room|status_bar|server_connectivity_lost_description")} diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index f24fa57d7d8..f8c0bbf1f03 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -246,6 +246,10 @@ export default class UserMenu extends React.Component { this.setState({ contextMenuPosition: null }); // also close the menu }; + private onOpenFAQPage = () => { + window.open("https://www.tchap.gouv.fr/faq", '_blank'); + }; + private onProvideFeedback = (ev: ButtonEvent): void => { ev.preventDefault(); ev.stopPropagation(); @@ -382,6 +386,11 @@ export default class UserMenu extends React.Component { onClick={(e) => this.onSettingsOpen(e)} /> {feedbackButton} + { return null; } else if (phase === Phase.Intro) { if (lostKeys) { + /* :tchap: hide anxious icon of warning icon = ; + end :tchap: */ title = _t("encryption|verification|after_new_login|unable_to_verify"); } else { icon = ; @@ -92,9 +94,16 @@ export default class CompleteSecurity extends React.Component { let skipButton; if (phase === Phase.Intro || phase === Phase.ConfirmReset) { + // :Tchap: Condition to skip Phase.ConfirmSkip and its "Are you sure" modal after login for csss + const tchapOnSkipClick = phase === Phase.Intro ? this.props.onFinished : this.onSkipClick; + // end :Tchap: + skipButton = ( diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 9487fc5ebef..f9a0d5e9d2b 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -42,6 +42,8 @@ import { VerifyEmailModal } from "./forgot-password/VerifyEmailModal"; import Spinner from "../../views/elements/Spinner"; import { formatSeconds } from "../../../DateUtils"; import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils"; +import TchapUtils from "../../../../../../src/tchap/util/TchapUtils"; // :TCHAP: +import Tchapi18nUtils from "../../../../../../src/tchap/i18n/Tchapi18nUtils"; const emailCheckInterval = 2000; @@ -61,7 +63,7 @@ enum Phase { } interface Props { - serverConfig: ValidatedServerConfig; + // :TCHAP: we get serverConfig when user enters email, so remove it from props - serverConfig: ValidatedServerConfig; onLoginClick: () => void; onComplete: () => void; } @@ -104,10 +106,13 @@ export default class ForgotPassword extends React.Component { serverDeadError: "", logoutDevices: false, }; - this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl); + // :TCHAP: no known server yet, this.reset stays undefined - this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl); } + // todo remove checkServerCapabilities in componentDidMount -> gone ? + public componentDidUpdate(prevProps: Readonly): void { + /* :TCHAP: we ignore serverConfig passed in props. So no use checking the server here. if ( prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl || prevProps.serverConfig.isUrl !== this.props.serverConfig.isUrl @@ -115,6 +120,7 @@ export default class ForgotPassword extends React.Component { // Do a liveliness check on the new URLs this.checkServerLiveliness(this.props.serverConfig); } + end :TCHAP: */ } private async checkServerLiveliness(serverConfig: ValidatedServerConfig): Promise { @@ -136,9 +142,38 @@ export default class ForgotPassword extends React.Component { } } + // :TCHAP: + private useNewServerConfig = async (serverConfig) => { + console.log('Using serverConfig corresponding to this email :', serverConfig); + + this.reset = new PasswordReset(serverConfig.hsUrl, serverConfig.isUrl); + // Note : this.reset is not a react state variable. It doesn't seem necessary to make it one, it seems to work this way. + // Note : we do not shut down or close the previous this.reset object in any way. Existing sessions just stay there. There doesn't + // seem to be an API for revoking tokens. + + // If the server is not available, this displays "Server unavailable, overloaded, or something else went wrong." + await this.checkServerLiveliness(serverConfig); + //await this.checkServerCapabilities(serverConfig); // todo + } + // end :TCHAP: + private async onPhaseEmailInputSubmit(): Promise { this.phase = Phase.SendingEmail; + // :TCHAP: find the server corresponding to the email. + const serverResult = await TchapUtils.fetchHomeserverForEmail(this.state.email); + if (!serverResult) { + this.setState({ + serverIsAlive: false, + errorText: Tchapi18nUtils.getServerDownMessage(), + phase: Phase.EnterEmail, // return to original phase, to remove the loding spinner from the submit button. + }); + return; + } + const serverConfig = await TchapUtils.makeValidatedServerConfig(serverResult); + await this.useNewServerConfig(serverConfig); + // end :TCHAP: + if (await this.sendVerificationMail()) { this.phase = Phase.EmailSent; return; @@ -300,8 +335,10 @@ export default class ForgotPassword extends React.Component { errorText: "", }); + /* :TCHAP: at this point we may not know the serverConfig to use yet. So don't check. // Refresh the server errors. Just in case the server came back online of went offline. await this.checkServerLiveliness(this.props.serverConfig); + end :TCHAP: */ // Server error if (!this.state.serverIsAlive) return; @@ -335,7 +372,7 @@ export default class ForgotPassword extends React.Component { public isBusy = (): boolean => !!this.state.busy || !!this.props.busy; + tchap_setServerInMemory = async (serverConfig) => { + const validatedServerConf = await TchapUtils.makeValidatedServerConfig(serverConfig); + + // Simulate the end of the serverPicker component flow. + this.props.onServerConfigChange(validatedServerConf); + + await this.initLoginLogic(validatedServerConf); + }; + public onPasswordLogin: OnPasswordLogin = async ( username: string | undefined, phoneCountry: string | undefined, phoneNumber: string | undefined, password: string, ): Promise => { + /* :TCHAP: remove alive check, we don't know which server to call yet. if (!this.state.serverIsAlive) { this.setState({ busy: true }); // Do a quick liveliness check on the URLs @@ -195,6 +207,7 @@ export default class LoginComponent extends React.PureComponent return; } } + end :TCHAP: */ this.setState({ busy: true, @@ -203,6 +216,23 @@ export default class LoginComponent extends React.PureComponent loginIncorrect: false, }); + /* :TCHAP: fetch homeserver corresponding to email */ + const serverResult = await TchapUtils.fetchHomeserverForEmail(username); + + if (!serverResult) { + this.setState({ + busy: false, + busyLoggingIn: false, + //errorText: _t('Server unavailable, overloaded, or something else went wrong.'), // reuse existing string + errorText: Tchapi18nUtils.getServerDownMessage("(err-03)"), + loginIncorrect: false, + }); + return; + } + + await this.tchap_setServerInMemory(serverResult); + /** end :TCHAP: */ + this.loginLogic.loginViaPassword(username, phoneCountry, phoneNumber, password).then( (data) => { this.setState({ serverIsAlive: true }); // it must be, we logged in. @@ -555,10 +585,12 @@ export default class LoginComponent extends React.PureComponent {errorTextSection} {serverDeadSection} + { /* :TCHAP: remove server picker, we don't allow user to chose, server is assigned to each email. + end :TCHAP :*/} {this.renderLoginComponentForFlows()} {footer} diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index 4da7282660e..751b266752b 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -50,11 +50,15 @@ import InteractiveAuth, { InteractiveAuthCallback } from "../InteractiveAuth"; import Spinner from "../../views/elements/Spinner"; import { AuthHeaderDisplay } from "./header/AuthHeaderDisplay"; import { AuthHeaderProvider } from "./header/AuthHeaderProvider"; +import { AuthHeaderModifier } from './header/AuthHeaderModifier'; // :TCHAP: import SettingsStore from "../../../settings/SettingsStore"; import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig"; import { Features } from "../../../settings/Settings"; import { startOidcLogin } from "../../../utils/oidc/authorize"; +import TchapUtils from '../../../../../../src/tchap/util/TchapUtils'; // :TCHAP: +import TchapUrls from "../../../../../../src/tchap/util/TchapUrls"; + const debuglog = (...args: any[]): void => { if (SettingsStore.getValue("debug_registration")) { logger.log.call(console, "Registration debuglog:", ...args); @@ -308,11 +312,24 @@ export default class Registration extends React.Component { } private onFormSubmit = async (formVals: Record): Promise => { + // :TCHAP: find the server corresponding to the entered email + const server = await TchapUtils.fetchHomeserverForEmail(formVals.email); + const validatedServerConfig = await TchapUtils.makeValidatedServerConfig(server); + // Note : onServerConfigChange triggers a state change at the matrixChat level. All the children are rerendered. + this.props.onServerConfigChange(validatedServerConfig); + // end :TCHAP: + this.setState({ errorText: "", busy: true, formVals, doingUIAuth: true, + // :TCHAP: pass a new temporary client so that InteractiveAuth is set up with the right serverconfig. + matrixClient: createClient({ + baseUrl: validatedServerConfig.hsUrl, + idBaseUrl: validatedServerConfig.isUrl, + }), + // end :TCHAP: }); }; @@ -360,6 +377,10 @@ export default class Registration extends React.Component { errorText = _t("auth|username_in_use"); } else if (response instanceof MatrixError && response.errcode === "M_THREEPID_IN_USE") { errorText = _t("auth|3pid_in_use"); + // :TCHAP: add error message for common case + } else if (response instanceof MatrixError && response.errcode === "M_THREEPID_DENIED") { + errorText = _t("That email is not allowed on Tchap", {}, {a: (sub)=>{sub}}); + // end :TCHAP: } this.setState({ @@ -491,6 +512,11 @@ export default class Registration extends React.Component { inhibit_login: undefined, }; if (auth) registerParams.auth = auth; + //:tchap: do not send username as tchap workflow does not use username + // https://github.com/tchapgouv/tchap-web-v4/issues/281 + registerParams.username = undefined; + //:tchap end + debuglog("Registration: sending registration request:", auth); return this.state.matrixClient.registerRequest(registerParams); }; @@ -740,6 +766,12 @@ export default class Registration extends React.Component { {errorText} {serverDeadSection} + { /* :TCHAP: remove the serverpicker, using AuthHeaderModifier. Inspired by InteractiveAuthEntryComponents. */} + + { /* end :TCHAP: */} {this.renderRegisterComponent()}
diff --git a/src/components/structures/auth/SetupEncryptionBody.tsx b/src/components/structures/auth/SetupEncryptionBody.tsx index 3ad4638306d..4558f5d6148 100644 --- a/src/components/structures/auth/SetupEncryptionBody.tsx +++ b/src/components/structures/auth/SetupEncryptionBody.tsx @@ -28,6 +28,10 @@ import { SetupEncryptionStore, Phase } from "../../../stores/SetupEncryptionStor import EncryptionPanel from "../../views/right_panel/EncryptionPanel"; import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton"; import Spinner from "../../views/elements/Spinner"; +import defaultDispatcher from "../../../dispatcher/dispatcher"; +import { Action } from "matrix-react-sdk/src/dispatcher/actions"; +import { UserTab } from "matrix-react-sdk/src/components/views/dialogs/UserTab"; +import { OpenToTabPayload } from "matrix-react-sdk/src/dispatcher/payloads/OpenToTabPayload"; function keyHasPassphrase(keyInfo: ISecretStorageKeyInfo): boolean { return Boolean(keyInfo.passphrase && keyInfo.passphrase.salt && keyInfo.passphrase.iterations); @@ -159,11 +163,22 @@ export default class SetupEncryptionBody extends React.Component if (lostKeys) { return (
-

{_t("encryption|verification|no_key_or_device")}

+ { /*:TCHAP: change

{_t("encryption|verification|no_key_or_device")}

*/ } +

+ {_t( + "

The Tchap team is working on the deployment of a new feature to "+ + "prevent encryption key loss.

"+ + "

You can access it in the section :

Security and privacy > Secure Backup

", + {}, { 'p': (sub) =>

{sub}

} + )} +

+ {/* end :TCHAP: */}
- {_t("encryption|verification|reset_proceed_prompt")} + {/* :TCHAP: _t("encryption|verification|reset_proceed_prompt") */} + {_t("Set up")} + {/* end :TCHAP: */}
diff --git a/src/components/structures/auth/forgot-password/EnterEmail.tsx b/src/components/structures/auth/forgot-password/EnterEmail.tsx index 11e44e65626..94bbedd7d57 100644 --- a/src/components/structures/auth/forgot-password/EnterEmail.tsx +++ b/src/components/structures/auth/forgot-password/EnterEmail.tsx @@ -64,9 +64,11 @@ export const EnterEmail: React.FC = ({ <>

{_t("auth|enter_email_heading")}

+ { /* :TCHAP: we don't display homeservers to users

{_t("auth|enter_email_explainer", { homeserver }, { b: (t) => {t} })}

+ end :TCHAP: */ }
diff --git a/src/components/views/auth/PassphraseField.tsx b/src/components/views/auth/PassphraseField.tsx index a9048d74154..87230166694 100644 --- a/src/components/views/auth/PassphraseField.tsx +++ b/src/components/views/auth/PassphraseField.tsx @@ -23,6 +23,7 @@ import withValidation, { IFieldState, IValidationResult } from "../elements/Vali import { _t, _td, TranslationKey } from "../../../languageHandler"; import Field, { IInputProps } from "../elements/Field"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; +import TchapStrongPassword from "../../../../../../src/tchap/util/TchapStrongPassword"; interface IProps extends Omit { autoFocus?: boolean; @@ -99,7 +100,10 @@ class PassphraseField extends PureComponent { }); public onValidate = async (fieldState: IFieldState): Promise => { - const result = await this.validate(fieldState); + // :TCHAP: use home-made validation + // const result = await this.validate(fieldState); + const result = await TchapStrongPassword.validate(fieldState); + // end :TCHAP: if (this.props.onValidate) { this.props.onValidate(result); } diff --git a/src/components/views/auth/PasswordLogin.tsx b/src/components/views/auth/PasswordLogin.tsx index 951262d2a29..a4f2f41ba2c 100644 --- a/src/components/views/auth/PasswordLogin.tsx +++ b/src/components/views/auth/PasswordLogin.tsx @@ -50,6 +50,7 @@ interface IProps { } interface IState { + displayPassword: boolean; fieldValid: Partial>; loginType: LoginField.Email | LoginField.MatrixId | LoginField.Phone; password: string; @@ -84,13 +85,21 @@ export default class PasswordLogin extends React.PureComponent { public constructor(props: IProps) { super(props); this.state = { + displayPassword: false, // Field error codes by field ID fieldValid: {}, - loginType: LoginField.MatrixId, + // :TCHAP: force email login + // loginType: LoginField.MatrixId, + loginType: LoginField.Email, + // password: "", }; } + private setDisplayPassword = (value: boolean): void => { + this.setState({ displayPassword: value }); + }; + private onForgotPasswordClick = (ev: ButtonEvent): void => { ev.preventDefault(); ev.stopPropagation(); @@ -421,13 +430,17 @@ export default class PasswordLogin extends React.PureComponent { return (
- {loginType} + { + /* :TCHAP: remove loginType selector, we only want Email loginType + loginType + */ + } {loginField} { autoFocus={autoFocusPassword} onValidate={this.onPasswordValidate} ref={(field) => (this[LoginField.Password] = field)} + postfixComponent={( +
this.setDisplayPassword(true)} + onMouseUp={() => this.setDisplayPassword(false)} + > + {_t("Eye")} +
+ )} /> {forgotPasswordJsx} {!this.props.busy && ( diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index 6709b07885e..fe697e1ec80 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -267,7 +267,7 @@ export default class RegistrationForm extends React.PureComponent _t("auth|reset_password_email_field_description"), + // :TCHAP: this is confusing because email=username in the Tchap case. //description: () => _t("auth|reset_password_email_field_description"), hideDescriptionIfValid: true, rules: [ { @@ -275,7 +275,10 @@ export default class RegistrationForm extends React.PureComponent _t("auth|reset_password_email_field_required_invalid"), + // :TCHAP: don't mention homeserver, Tchap hides the concept from users. + //invalid: () => _t("auth|reset_password_email_field_required_invalid"), + invalid: () => _t("auth|email_field_label_required"), + // end :TCHAP: }, { key: "email", @@ -568,16 +571,23 @@ export default class RegistrationForm extends React.PureComponent -
{this.renderUsername()}
+ { /* :TCHAP: remove username field, the server will generate it from email.
- {this.renderPassword()} - {this.renderPasswordConfirm()} + { this.renderUsername() } +
+ end :TCHAP: */} + + { /** :TCHAP: switch fields : email first, password under */} +
+ { this.renderEmail() } + { this.renderPhoneNumber() }
- {this.renderEmail()} - {this.renderPhoneNumber()} + { this.renderPassword() } + { this.renderPasswordConfirm() }
- {emailHelperText} + { /* end :TCHAP: */} + { /** :TCHAP: remove helper text, adds confusion since email=username in tchap. // emailHelperText */ } {registerButton}
diff --git a/src/components/views/avatars/DecoratedRoomAvatar.tsx b/src/components/views/avatars/DecoratedRoomAvatar.tsx index ed8afe7b61a..a35de5653b6 100644 --- a/src/components/views/avatars/DecoratedRoomAvatar.tsx +++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx @@ -31,6 +31,10 @@ import DMRoomMap from "../../../utils/DMRoomMap"; import { IOOBData } from "../../../stores/ThreepidInviteStore"; import { getJoinedNonFunctionalMembers } from "../../../utils/room/getJoinedNonFunctionalMembers"; +import TchapRoomUtils from "../../../../../../src/tchap/util/TchapRoomUtils"; +import "../../../../../../res/css/views/avatars/_TchapDecoratedRoomAvatar.pcss"; +import { TchapRoomType } from "../../../../../../src/tchap/@types/tchap"; + interface IProps { room: Room; size: string; @@ -54,6 +58,11 @@ enum Icon { // Note: the names here are used in CSS class names None = "NONE", // ... except this one Globe = "GLOBE", + // :TCHAP: add icons for custom room types + Forum = "FORUM", + Private = "PRIVATE", + External = "EXTERNAL", + // end :TCHAP: PresenceOnline = "ONLINE", PresenceAway = "AWAY", PresenceOffline = "OFFLINE", @@ -64,6 +73,14 @@ function tooltipText(variant: Icon): string | undefined { switch (variant) { case Icon.Globe: return _t("room|header|room_is_public"); + // :TCHAP: add icons for custom room types + case Icon.Forum: + return _t("This room is a public forum"); + case Icon.Private: + return _t("This room is private"); + case Icon.External: + return _t("This room is private and open to external users"); + // end :TCHAP: case Icon.PresenceOnline: return _t("presence|online"); case Icon.PresenceAway: @@ -167,7 +184,23 @@ export default class DecoratedRoomAvatar extends React.PureComponent +
+ { /*:TCHAP: extra div to fix positioning. + https://github.com/tchapgouv/tchap-web-v4/issues/890 + Issue should be opened in element-web. */ } +
)} {badge} +
{ /*:TCHAP: close div */ }
); } diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index e0fca0a4c0b..c836aeab66a 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -60,6 +60,8 @@ import { getForwardableEvent } from "../../../events/forward/getForwardableEvent import { getShareableLocationEvent } from "../../../events/location/getShareableLocationEvent"; import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; import { CardContext } from "../right_panel/context"; +//:tchap: add feature flags +import TchapUIFeature from "../../../../../../src/tchap/util/TchapUIFeature"; interface IReplyInThreadButton { mxEvent: MatrixEvent; @@ -676,7 +678,10 @@ export default class MessageContextMenu extends React.Component {reactButton} {replyButton} - {replyInThreadButton} + {/* :TCHAP: activate Thread based on homeserver feature flag + {replyInThreadButton} */} + {TchapUIFeature.isFeatureActiveForHomeserver("feature_thread") ? replyInThreadButton : null} + {/*:TCHAP: end */} {editButton} ); diff --git a/src/components/views/dialogs/BugReportDialog.tsx b/src/components/views/dialogs/BugReportDialog.tsx index 5d826f283e3..51baf281569 100644 --- a/src/components/views/dialogs/BugReportDialog.tsx +++ b/src/components/views/dialogs/BugReportDialog.tsx @@ -32,6 +32,8 @@ import DialogButtons from "../elements/DialogButtons"; import { sendSentryReport } from "../../../sentry"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import { Action } from "../../../dispatcher/actions"; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; // :TCHAP: +import TchapUtils from "../../../../../../src/tchap/util/TchapUtils"; // :TCHAP: interface IProps { onFinished: (success: boolean) => void; @@ -96,12 +98,21 @@ export default class BugReportDialog extends React.Component { }; private onSubmit = (): void => { + /* :TCHAP: do not ask for a github issue if ((!this.state.text || !this.state.text.trim()) && (!this.state.issueUrl || !this.state.issueUrl.trim())) { this.setState({ err: _t("bug_reporting|error_empty"), }); return; } + */ + if ((!this.state.text || !this.state.text.trim())) { + this.setState({ + err: _t("Please tell us what went wrong in the \"Notes\" field."), + }); + return; + } + // end :TCHAP: const userText = (this.state.text.length > 0 ? this.state.text + "\n\n" : "") + @@ -111,11 +122,34 @@ export default class BugReportDialog extends React.Component { this.setState({ busy: true, progress: null, err: null }); this.sendProgressCallback(_t("bug_reporting|preparing_logs")); - sendBugReport(SdkConfig.get().bug_report_endpoint_url, { - userText, - sendLogs: true, - progressCallback: this.sendProgressCallback, - labels: this.props.label ? [this.props.label] : [], + // :TCHAP: customise report : add email, prefix with "tchap-web" + const client = MatrixClientPeg.get(); + const customFields = {}; + client.getThreePids().then(result => { + result.threepids.forEach(threepid => { + return customFields[threepid.medium] = threepid.address; + }); + return customFields; + }).then(customFields => { + // is this a voip report ? Add it in "context" field + if (this.props.label === "voip-feedback") { + customFields.context = "voip"; + } + + return TchapUtils.isCurrentlyUsingBluetooth().then(isCurrentlyUsingBluetooth => { + customFields.audio_input = isCurrentlyUsingBluetooth ? "headset_bluetooth" : "device"; + return customFields; + }) + }).then(customFields => { + return sendBugReport(SdkConfig.get().bug_report_endpoint_url, { + userText, + sendLogs: true, + progressCallback: this.sendProgressCallback, + labels: this.props.label ? [this.props.label] : [], + customApp: 'tchap-web', // :TCHAP: + customFields: customFields, // :TCHAP: + }); + // end :TCHAP: }).then( () => { if (!this.unmounted) { @@ -150,6 +184,7 @@ export default class BugReportDialog extends React.Component { sendLogs: true, progressCallback: this.downloadProgressCallback, labels: this.props.label ? [this.props.label] : [], + customApp: 'tchap-web', // :TCHAP: we don't add email here. You know your own email already. }); this.setState({ @@ -214,6 +249,53 @@ export default class BugReportDialog extends React.Component { ); } + { /** :TCHAP: replace with our own dialog */} + return ( + +
+ {warning} +

{_t("bug_reporting|description")}

+ + {progress} + {error} + + +
+

+ { _t("Just want to get your own logs, without sharing them with the Tchap team?") } +

+ + {this.state.downloadProgress && {this.state.downloadProgress} ...} +
+
+
+ ); + {/* return ( { /> ); + end :TCHAP: */} } } diff --git a/src/components/views/dialogs/IncomingSasDialog.tsx b/src/components/views/dialogs/IncomingSasDialog.tsx index a562760b6a1..2d136064e5c 100644 --- a/src/components/views/dialogs/IncomingSasDialog.tsx +++ b/src/components/views/dialogs/IncomingSasDialog.tsx @@ -175,6 +175,7 @@ export default class IncomingSasDialog extends React.Component { profile = ; } + /* :TCHAP: remove code const userDetailText = [

{_t("encryption|verification|incoming_sas_user_dialog_text_1")}

,

@@ -190,11 +191,23 @@ export default class IncomingSasDialog extends React.Component {

{_t("encryption|verification|incoming_sas_device_dialog_text_1")}

,

{_t("encryption|verification|incoming_sas_device_dialog_text_2")}

, ]; + end :TCHAP: */ return (
+ {/* :TCHAP: remove code {profile} {isSelf ? selfDetailText : userDetailText} + end :TCHAP: */} + + {/* :TCHAP: simplify modal message */} +

{ _t( + "One of your devices wants to check your Tchap Keys to unlock your messages.", + {}, + { b: sub => { sub } }, + ) }

+ {/* end :TCHAP: */} + > = ({ space ); }} > - {_t("common|people")} + {_t("Direct Messages") /* TCHAP: change label _t("common|people") */} {_t("space|preferences|show_people_in_space", { diff --git a/src/components/views/dialogs/VerificationRequestDialog.tsx b/src/components/views/dialogs/VerificationRequestDialog.tsx index c6a86ae3e10..0c3f80ab355 100644 --- a/src/components/views/dialogs/VerificationRequestDialog.tsx +++ b/src/components/views/dialogs/VerificationRequestDialog.tsx @@ -49,9 +49,12 @@ export default class VerificationRequestDialog extends React.Component { + this.setState({ displayPassword: true }); + + setTimeout(() => { + this.setState({ displayPassword: false }); + }, 120 * 1000); + }; + // end :TCHAP: + private onCancel = (): void => { if (this.state.resetting) { this.setState({ resetting: false }); @@ -395,7 +407,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent
this.setDisplayPassword()} + > + {_t("Eye")} +
+ )} />
+ {/* + :TCHAP: hide for csss feature {_t("encryption|access_secret_storage_dialog|separator", { recoveryFile: "", securityKey: "", })} -
+
{_t("action|upload")} -
+ :end TCHAP +
*/}
{recoveryKeyFeedback} = ({ protocols, config, setConfig options: [ { key: { roomServer, instanceId: undefined }, - label: _t("common|matrix"), + // :TCHAP: remove > label: _t("common|matrix"), + label: TchapUtils.toFriendlyServerName(roomServer) }, ...(roomServer === homeServer && protocols ? Object.values(protocols) @@ -237,7 +239,11 @@ export const NetworkDropdown: React.FC = ({ protocols, config, setConfig selectedLabel={(option) => option?.key ? _t("spotlight|public_rooms|network_dropdown_selected_label_instance", { - server: option.key.roomServer, + /* :TCHAP: + server: option.key.roomServer, + */ + server: TchapUtils.toFriendlyServerName(option.key.roomServer), + // end :TCHAP: instance: option.key.instanceId ? option.label : "Matrix", }) : _t("spotlight|public_rooms|network_dropdown_selected_label") diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index 1eb03792b8d..90955dd33f0 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -85,7 +85,7 @@ type Props = DynamicHtmlElementProps & /** * Event handler for button activation. Should be implemented exactly like a normal `onClick` handler. */ - onClick: ((e: ButtonEvent) => void | Promise) | null; + onClick?: ((e: ButtonEvent) => void | Promise) | null; }; /** diff --git a/src/components/views/messages/DecryptionFailureBody.tsx b/src/components/views/messages/DecryptionFailureBody.tsx index 1a68e58b334..a32b4ea683a 100644 --- a/src/components/views/messages/DecryptionFailureBody.tsx +++ b/src/components/views/messages/DecryptionFailureBody.tsx @@ -19,11 +19,25 @@ import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { _t } from "../../../languageHandler"; import { IBodyProps } from "./IBodyProps"; +import TchapUrls from "../../../../../../src/tchap/util/TchapUrls"; // :TCHAP: +import ExternalLink from "../elements/ExternalLink"; // :TCHAP: function getErrorMessage(mxEvent?: MatrixEvent): string { return mxEvent?.isEncryptedDisabledForUnverifiedDevices ? _t("timeline|decryption_failure_blocked") - : _t("threads|unable_to_decrypt"); + // :TCHAP: : _t("threads|unable_to_decrypt"); + : _t( + "threads|unable_to_decrypt_with_info_message", + {}, + { + a: (sub) => ( + + {sub} + + ), + }, + ); + // end :TCHAP: } // A placeholder element for messages that could not be decrypted diff --git a/src/components/views/messages/LegacyCallEvent.tsx b/src/components/views/messages/LegacyCallEvent.tsx index 3c8241d4ea1..cdd9a836380 100644 --- a/src/components/views/messages/LegacyCallEvent.tsx +++ b/src/components/views/messages/LegacyCallEvent.tsx @@ -28,6 +28,10 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import { formatPreciseDuration } from "../../../DateUtils"; import Clock from "../audio_messages/Clock"; +import Modal from "matrix-react-sdk/src/Modal"; // :TCHAP: +import BugReportDialog from "matrix-react-sdk/src/components/views/dialogs/BugReportDialog"; // :TCHAP: +import "../../../../../../res/css/views/messages/TchapLegacyCallEvent.pcss"; // :TCHAP: + const MAX_NON_NARROW_WIDTH = (450 / 70) * 100; interface IProps { @@ -192,6 +196,7 @@ export default class LegacyCallEvent extends React.PureComponent return (
{text} + {this.renderBugReportButton()} {this.props.timestamp}
); @@ -264,6 +269,25 @@ export default class LegacyCallEvent extends React.PureComponent ); } + private onReportBugClick = (): void => { + Modal.createDialog(BugReportDialog, { + initialText: _t("tchap_voip_bug_report_prefill"), + label: "voip-feedback", + }); + }; + + private renderBugReportButton(): JSX.Element { + return ( + + {_t("Report a problem")} + + ); + } + public render(): React.ReactNode { const event = this.props.mxEvent; const sender = event.sender ? event.sender.name : event.getSender(); diff --git a/src/components/views/messages/MAudioBody.tsx b/src/components/views/messages/MAudioBody.tsx index 52cd2334afd..106dd15f651 100644 --- a/src/components/views/messages/MAudioBody.tsx +++ b/src/components/views/messages/MAudioBody.tsx @@ -23,7 +23,7 @@ import InlineSpinner from "../elements/InlineSpinner"; import { _t } from "../../../languageHandler"; import AudioPlayer from "../audio_messages/AudioPlayer"; import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent"; -import MFileBody from "./MFileBody"; +import MFileBody from "../../../../../../src/tchap/components/views/messages/OriginalFileBody"; import { IBodyProps } from "./IBodyProps"; import { PlaybackManager } from "../../../audio/PlaybackManager"; import { isVoiceMessage } from "../../../utils/EventUtils"; diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index ff4d573e059..8003410bfc6 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -23,7 +23,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { ClientEvent, ClientEventHandlerMap } from "matrix-js-sdk/src/matrix"; import { Tooltip } from "@vector-im/compound-web"; -import MFileBody from "./MFileBody"; +import MFileBody from "../../../../../../src/tchap/components/views/messages/OriginalFileBody"; import Modal from "../../../Modal"; import { _t } from "../../../languageHandler"; import SettingsStore from "../../../settings/SettingsStore"; diff --git a/src/components/views/messages/MImageReplyBody.tsx b/src/components/views/messages/MImageReplyBody.tsx index c6d61a7ba5b..0d16a14597b 100644 --- a/src/components/views/messages/MImageReplyBody.tsx +++ b/src/components/views/messages/MImageReplyBody.tsx @@ -16,7 +16,7 @@ limitations under the License. import React from "react"; -import MImageBody from "./MImageBody"; +import MImageBody from "../../../../../../src/tchap/components/views/messages/OriginalImageBody"; import { ImageContent } from "../../../customisations/models/IMediaEventContent"; const FORCED_IMAGE_HEIGHT = 44; diff --git a/src/components/views/messages/MStickerBody.tsx b/src/components/views/messages/MStickerBody.tsx index 26c33e89fca..e0fdf7030c7 100644 --- a/src/components/views/messages/MStickerBody.tsx +++ b/src/components/views/messages/MStickerBody.tsx @@ -17,7 +17,7 @@ limitations under the License. import React, { ComponentProps, ReactNode } from "react"; import { Tooltip } from "@vector-im/compound-web"; -import MImageBody from "./MImageBody"; +import MImageBody from "../../../../../../src/tchap/components/views/messages/OriginalImageBody"; import { BLURHASH_FIELD } from "../../../utils/image-media"; import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent"; diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index df3ab6abbf9..d057dc0300c 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -25,7 +25,7 @@ import { mediaFromContent } from "../../../customisations/Media"; import { BLURHASH_FIELD } from "../../../utils/image-media"; import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent"; import { IBodyProps } from "./IBodyProps"; -import MFileBody from "./MFileBody"; +import MFileBody from "../../../../../../src/tchap/components/views/messages/OriginalFileBody"; import { ImageSize, suggestedSize as suggestedVideoSize } from "../../../settings/enums/ImageSize"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import MediaProcessingError from "./shared/MediaProcessingError"; diff --git a/src/components/views/messages/MVoiceMessageBody.tsx b/src/components/views/messages/MVoiceMessageBody.tsx index 096824e4b23..fe73df6a58a 100644 --- a/src/components/views/messages/MVoiceMessageBody.tsx +++ b/src/components/views/messages/MVoiceMessageBody.tsx @@ -19,8 +19,8 @@ import React from "react"; import InlineSpinner from "../elements/InlineSpinner"; import { _t } from "../../../languageHandler"; import RecordingPlayback from "../audio_messages/RecordingPlayback"; -import MAudioBody from "./MAudioBody"; -import MFileBody from "./MFileBody"; +import MAudioBody from "../../../../../../src/tchap/components/views/messages/OriginalAudioBody"; +import MFileBody from "../../../../../../src/tchap/components/views/messages/OriginalFileBody"; import MediaProcessingError from "./shared/MediaProcessingError"; export default class MVoiceMessageBody extends MAudioBody { diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index 9cf35922089..066deae5021 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -61,6 +61,7 @@ import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayloa import { GetRelationsForEvent, IEventTileType } from "../rooms/EventTile"; import { VoiceBroadcastInfoEventType } from "../../../voice-broadcast/types"; import { ButtonEvent } from "../elements/AccessibleButton"; +import TchapUIFeature from "../../../../../../src/tchap/util/TchapUIFeature"; interface IOptionsButtonProps { mxEvent: MatrixEvent; @@ -345,7 +346,10 @@ export default class MessageActionBar extends React.PureComponent { onClick={this.onTimelineCardClicked} />, ); + //:tchap: activate Thread based on homeserver feature flag + if(TchapUIFeature.isFeatureActiveForHomeserver("feature_thread")){ rightPanelPhaseButtons.set( RightPanelPhases.ThreadPanel, { , ); + } //close bracket :tchap: end + if (this.state.notificationsEnabled) { rightPanelPhaseButtons.set( RightPanelPhases.NotificationPanel, diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index d6182e5e2e1..0de098f328a 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -76,6 +76,9 @@ import { inviteToRoom } from "../../../utils/room/inviteToRoom"; import { useAccountData } from "../../../hooks/useAccountData"; import { useRoomState } from "../../../hooks/useRoomState"; +import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; // :TCHAP: + + interface IProps { room: Room; permalinkCreator: RoomPermalinkCreator; @@ -331,7 +334,11 @@ const RoomSummaryCard: React.FC = ({ room, permalinkCreator, onClose, on const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || ""; const header = (
+ {/** :TCHAP: decorate the avatar with the tchap lock icons + */} + + {/* end :TCHAP: */} {(name) => ( = ({ room, permalinkCreator, onClose, on {alias} + {/* :TCHAP: remove badges {!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && ( @@ -384,6 +392,7 @@ const RoomSummaryCard: React.FC = ({ room, permalinkCreator, onClose, on )} + */}
); diff --git a/src/components/views/right_panel/VerificationPanel.tsx b/src/components/views/right_panel/VerificationPanel.tsx index c0872192ec9..e626a42fd98 100644 --- a/src/components/views/right_panel/VerificationPanel.tsx +++ b/src/components/views/right_panel/VerificationPanel.tsx @@ -295,10 +295,13 @@ export default class VerificationPanel extends React.PureComponent { onClose={this.props.onClose} > {inviteButton} + {/** TCHAP */} + roomMember.userId)}> + + {/** end TCHAP */}
+ {/* :TCHAP: RoomAvatar -> DecoratedRoomAvatar + */} + + {/* end :TCHAP: */} + {/* :tchap: Add external caption when room is open to external */} + + {/* :tchap: end */} {roomName} + {/* :tchap: remove public forum icon {!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && ( )} + */} + {/* :tchap: do not show e2eStatus {isDirectMessage && e2eStatus === E2EStatus.Verified && ( )} + */} + {/* :tchap: do not show E2EStatus.Warning {isDirectMessage && e2eStatus === E2EStatus.Warning && ( )} + */} {roomTopic && ( + { /* :TCHAP: activate video call only if directmessage and if feature is activated on homeserver } {!isVideoRoom(room) && videoCallButton} + */ } + {isDirectMessage && TchapUIFeature.isFeatureActiveForHomeserver("feature_video_call") && + !isVideoRoom(room) && videoCallButton} + {/* end :TCHAP: */} + + { /* :TCHAP: activate audio call only if directmessage and if feature is activated on homeserver {!useElementCallExclusively && !isVideoRoom(room) && voiceCallButton} + */ } + {isDirectMessage && TchapUIFeature.isFeatureActiveForHomeserver("feature_audio_call") && + !useElementCallExclusively && !isVideoRoom(room) && voiceCallButton} + {/* end :TCHAP: */} )} diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 28154c40d65..09f40ca385b 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -382,7 +382,7 @@ const TAG_AESTHETICS: TagAestheticsMap = { defaultHidden: false, }, [DefaultTagID.DM]: { - sectionLabel: _td("common|people"), + sectionLabel: _td("Direct Messages"), /* TCHAP: change label _td("common|people"), */ isInvite: false, defaultHidden: false, AuxButtonComponent: DmAuxButton, diff --git a/src/components/views/rooms/ThreadSummary.tsx b/src/components/views/rooms/ThreadSummary.tsx index 1e30dcba9d4..31722e7384a 100644 --- a/src/components/views/rooms/ThreadSummary.tsx +++ b/src/components/views/rooms/ThreadSummary.tsx @@ -34,6 +34,8 @@ import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayloa import defaultDispatcher from "../../../dispatcher/dispatcher"; import { useUnreadNotifications } from "../../../hooks/useUnreadNotifications"; import { notificationLevelToIndicator } from "../../../utils/notifications"; +import ExternalLink from "../elements/ExternalLink"; // :TCHAP: +import TchapUrls from "../../../../../../src/tchap/util/TchapUrls"; // :TCHAP: interface IProps { mxEvent: MatrixEvent; @@ -105,6 +107,20 @@ export const ThreadMessagePreview: React.FC = ({ thread, showDisp return null; } + // :TCHAP: + const undecryptedText = _t( + "threads|unable_to_decrypt_with_info_message", + {}, + { + a: (sub) => ( + + {sub} + + ), + }, + ); + // end :TCHAP: + return ( <> = ({ thread, showDisp className="mx_ThreadSummary_content mx_DecryptionFailureBody" title={_t("threads|unable_to_decrypt")} > - {_t("threads|unable_to_decrypt")} + { /* :TCHAP: {_t("threads|unable_to_decrypt")}*/} + + {_t("threads|unable_to_decrypt_with_info_message", {}, + { + a: (sub) => ( + + {sub} + + ), + }, + )} + + {/** end :TCHAP: */} +
) : (
diff --git a/src/components/views/settings/CrossSigningPanel.tsx b/src/components/views/settings/CrossSigningPanel.tsx index 869bb716f8d..aff5fd68761 100644 --- a/src/components/views/settings/CrossSigningPanel.tsx +++ b/src/components/views/settings/CrossSigningPanel.tsx @@ -227,23 +227,32 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> { userSigningPrivateKeyCached; const actions: JSX.Element[] = []; + // :TCHAP move Reset button to advanced section + const advancedActions: JSX.Element[] = []; + // end :TCHAP: // TODO: determine how better to expose this to users in addition to prompts at login/toast if (!keysExistEverywhere && homeserverSupportsCrossSigning) { - let buttonCaption = _t("encryption|set_up_toast_title"); + // :TCHAP: let buttonCaption = _t("encryption|set_up_toast_title"); + let buttonCaption = _t("Activate on this device"); + // end :TCHAP: if (crossSigningPrivateKeysInStorage) { buttonCaption = _t("encryption|verify_toast_title"); } - actions.push( - + // :TCHAP: actions.push( + advancedActions.push( + // end :TCHAP: + {buttonCaption} , ); } if (keysExistAnywhere) { - actions.push( - + // :TCHAP actions.push( + advancedActions.push( + // end :TCHAP: + {_t("action|reset")} , ); @@ -254,6 +263,13 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> { actionRow =
{actions}
; } + // :TCHAP add + let advancedActionRow; + if (advancedActions.length) { + advancedActionRow =
{advancedActions}
; + } + // end :TCHAP: + return ( <> {summarisedStatus} @@ -311,6 +327,7 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> { + {advancedActionRow} {errorSection} {actionRow} diff --git a/src/components/views/settings/CryptographyPanel.tsx b/src/components/views/settings/CryptographyPanel.tsx index 15ead0dec73..42116a68350 100644 --- a/src/components/views/settings/CryptographyPanel.tsx +++ b/src/components/views/settings/CryptographyPanel.tsx @@ -72,6 +72,7 @@ export default class CryptographyPanel extends React.Component { ); } + /* :TCHAP: redesign the returned component return ( @@ -96,7 +97,44 @@ export default class CryptographyPanel extends React.Component { {noSendUnverifiedSetting} ); + */ + return ( + {/* :TCHAP: name changed in translations */} +
+
+ {_t("These keys only apply to the current session.")} +
+
+ {_t("Please note this is not your recovery code for your automatic backup.")} +
+
+
+ {_t("common|advanced")} + + + + + + + + + + +
{_t("settings|security|session_id")} + {deviceId} +
{_t("settings|security|session_key")} + + {identityKey} + +
+
+
+ {importExportButtons} + {noSendUnverifiedSetting} +
+ ); } + // end :TCHAP: private onExportE2eKeysClicked = (): void => { Modal.createDialogAsync( diff --git a/src/components/views/settings/Notifications.tsx b/src/components/views/settings/Notifications.tsx index ec47cff7144..aa70cf84ffd 100644 --- a/src/components/views/settings/Notifications.tsx +++ b/src/components/views/settings/Notifications.tsx @@ -59,6 +59,8 @@ import { SettingsSubsectionHeading } from "./shared/SettingsSubsectionHeading"; import SettingsSubsection from "./shared/SettingsSubsection"; import { doesRoomHaveUnreadMessages } from "../../../Unread"; +import TchapUIFeature from "../../../../../../src/tchap/util/TchapUIFeature"; + // TODO: this "view" component still has far too much application logic in it, // which should be factored out to other files. @@ -725,7 +727,11 @@ export default class Notifications extends React.PureComponent { )} + {/* :TCHAP: show button only if feature is active on homeserver {emailSwitches} + */} + { TchapUIFeature.isFeatureActiveForHomeserver("feature_email_notification") ? emailSwitches : null} + {/* :TCHAP: end */} ); } diff --git a/src/components/views/settings/SecureBackupPanel.tsx b/src/components/views/settings/SecureBackupPanel.tsx index 520379cc7b0..790b85be583 100644 --- a/src/components/views/settings/SecureBackupPanel.tsx +++ b/src/components/views/settings/SecureBackupPanel.tsx @@ -266,12 +266,23 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> { } else { statusDescription = ( <> + {/* TCHAP add Tchap text */} + + {_t( + "settings|security|key_backup_inactive_warning", + {}, + { b: (sub) => {sub} }, + )} + + {/* end TCHAP */} + {/* :TCHAP remove element text {_t("settings|security|key_backup_inactive", {}, { b: (sub) => {sub} })} {_t("settings|security|key_backup_connect_prompt")} + end :TCHAP: */} ); restoreButtonCaption = _t("settings|security|key_backup_connect"); @@ -333,6 +344,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
, ); + /* :TCHAP: hide if (!isSecureBackupRequired(MatrixClientPeg.safeGet())) { actions.push( @@ -340,13 +352,16 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> { , ); } + end :TCHAP: */ } else { statusDescription = ( <> {_t("settings|security|key_backup_inactive_warning", {}, { b: (sub) => {sub} })} + {/* :TCHAP: remove {_t("encryption|setup_secure_backup|explainer")} + end :TCHAP: */} ); actions.push( @@ -359,7 +374,9 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> { if (secretStorageKeyInAccount) { actions.push( - {_t("action|reset")} + {/* :TCHAP: _t("action|reset") */} + {_t("Generate a new code")} + {/* end :TCHAP: */} , ); } diff --git a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx index 8e990d7d33c..4a6cf17036f 100644 --- a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.tsx @@ -89,6 +89,7 @@ export default class GeneralRoomSettingsTab extends React.Component + {/* :TCHAP: no aliases for rooms + end :TCHAP: */} {urlPreviewSettings} diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx index 6ac686e0639..69ded776071 100644 --- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx +++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx @@ -255,6 +255,7 @@ export default class SecurityRoomSettingsTab extends React.Component @@ -263,12 +264,13 @@ export default class SecurityRoomSettingsTab extends React.Component ); } + end :TCHAP: */ const description = _t("room_settings|security|join_rule_description", { roomName: room.name, }); let advanced: JSX.Element | undefined; - if (room.getJoinRule() === JoinRule.Public) { + if (false) { // :TCHAP: no guest access - if(room.getJoinRule() === JoinRule.Public) { advanced = (
void; @@ -326,7 +327,9 @@ export default class GeneralUserSettingsTab extends React.Component ) : ( @@ -534,7 +537,8 @@ export default class GeneralUserSettingsTab extends React.Component + + + ); + /* const secureBackup = ( ); + */ + // end :TCHAP: const eventIndex = ( @@ -354,13 +363,14 @@ export default class SecurityUserSettingsTab extends React.Component {warning} + { /* :TCHAP: move secureBackup and privacySection, and remove eventIndex */ } + {secureBackup} - {secureBackup} - {eventIndex} {crossSigning} + {privacySection} - {privacySection} + { /* end :TCHAP: */ } {advancedSection} ); diff --git a/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx b/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx index 4d6cf9a40f5..9c815490f05 100644 --- a/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/SidebarUserSettingsTab.tsx @@ -120,7 +120,7 @@ const SidebarUserSettingsTab: React.FC = () => { > - {_t("common|people")} + {_t("Direct Messages") /* TCHAP: change label _t("common|people") */} {_t("settings|sidebar|metaspaces_people_description")} diff --git a/src/components/views/spaces/QuickSettingsButton.tsx b/src/components/views/spaces/QuickSettingsButton.tsx index 81d1e3dfb88..9e7f7399513 100644 --- a/src/components/views/spaces/QuickSettingsButton.tsx +++ b/src/components/views/spaces/QuickSettingsButton.tsx @@ -107,7 +107,9 @@ const QuickSettingsButton: React.FC<{ onChange={onMetaSpaceChangeFactory(MetaSpace.People, "WebQuickSettingsPinToSidebarCheckbox")} > - {_t("common|people")} + {/* TCHAP: change label {_t("common|people")} */} + {_t("Direct Messages")} + {/* end TCHAP */} { public render(): React.ReactNode { return (
+ { /** :TCHAP: replace title and description */} +

{ _t("Incoming Verification Request") }

+

{ _t("The sharing of your Tchap Keys has succeeded. Your messages will be unlocked.") }

+ {/*

{_t("encryption|verification|complete_title")}

{_t("encryption|verification|complete_description")}

{_t("encryption|verification|explainer")}

+ end :TCHAP: */} { - return Object.keys(await getLangsJson()); + // :TCHAP: only en and fr + // return Object.keys(await getLangsJson()); + return ["en", "fr"]; } export async function getAllLanguagesWithLabels(): Promise { diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts index 5f2a3990ad2..3034aaa2800 100644 --- a/src/rageshake/submit-rageshake.ts +++ b/src/rageshake/submit-rageshake.ts @@ -412,12 +412,13 @@ export async function submitFeedback( version = await PlatformPeg.get()?.getAppVersion(); } catch (err) {} // PlatformPeg already logs this. + const body = new FormData(); if (label) body.append("label", label); body.append("text", comment); body.append("can_contact", canContact ? "yes" : "no"); - body.append("app", "element-web"); + body.append("app", "tchap-web"); body.append("version", version || "UNKNOWN"); body.append("platform", PlatformPeg.get()?.getHumanReadableName() ?? "n/a"); body.append("user_id", MatrixClientPeg.get()?.getUserId() ?? "n/a"); @@ -427,6 +428,13 @@ export async function submitFeedback( } const bugReportEndpointUrl = SdkConfig.get().bug_report_endpoint_url; + //:tchap: add email in body + const client = MatrixClientPeg.get(); + const result = await client.getThreePids();//it generates a API calls which is acceptable because feedbacks submit are not so frequent (unfortunately) + result.threepids.forEach(threepid => { + body.append(threepid.medium, threepid.address); + }); + //:tchap: end if (bugReportEndpointUrl) { await submitReport(bugReportEndpointUrl, body, () => {}); diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index 4b7b165b441..2f18d193c99 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -618,6 +618,12 @@ export class RoomViewStore extends EventEmitter { let description: ReactNode = err.message ? err.message : JSON.stringify(err); logger.log("Failed to join room:", description); + /* :TCHAP: add this for a translation */ + if (typeof description === 'string' && description.includes('You are not invited to this room')) { + description = _t("Access possible only by invitation of a member of the room."); + } + /* end :TCHAP: */ + if (err.name === "ConnectionError") { description = _t("room|error_join_connection"); } else if (err.errcode === "M_INCOMPATIBLE_ROOM_VERSION") { diff --git a/src/stores/spaces/index.ts b/src/stores/spaces/index.ts index 30f6798bb6a..6a7b69affa9 100644 --- a/src/stores/spaces/index.ts +++ b/src/stores/spaces/index.ts @@ -41,7 +41,10 @@ export const getMetaSpaceName = (spaceKey: MetaSpace, allRoomsInHome = false): s case MetaSpace.Favourites: return _t("common|favourites"); case MetaSpace.People: - return _t("common|people"); + // TCHAP: + // return _t("common|people") + return _t("Direct Messages"); + // end TCHAP case MetaSpace.Orphans: return _t("common|orphan_rooms"); } diff --git a/src/toasts/SetupEncryptionToast.ts b/src/toasts/SetupEncryptionToast.ts index e55e665a27f..b75943056cf 100644 --- a/src/toasts/SetupEncryptionToast.ts +++ b/src/toasts/SetupEncryptionToast.ts @@ -50,7 +50,11 @@ const getIcon = (kind: Kind): string => { const getSetupCaption = (kind: Kind): string => { switch (kind) { case Kind.SET_UP_ENCRYPTION: + /* :TCHAP: return _t("action|continue"); + */ + return _t("action|enable"); + // end :TCHAP: case Kind.UPGRADE_ENCRYPTION: return _t("action|upgrade"); case Kind.VERIFY_THIS_SESSION: diff --git a/src/utils/ErrorUtils.tsx b/src/utils/ErrorUtils.tsx index 526be3165b6..57087d30795 100644 --- a/src/utils/ErrorUtils.tsx +++ b/src/utils/ErrorUtils.tsx @@ -22,6 +22,8 @@ import SdkConfig from "../SdkConfig"; import { ValidatedServerConfig } from "./ValidatedServerConfig"; import ExternalLink from "../components/views/elements/ExternalLink"; +import Tchapi18nUtils from '../../../../src/tchap/i18n/Tchapi18nUtils'; // :TCHAP: + export const resourceLimitStrings = { "monthly_active_user": _td("error|mau"), "hs_blocked": _td("error|hs_blocked"), @@ -135,6 +137,10 @@ export function messageForLoginError( } else { return _t("auth|incorrect_credentials"); } + // :TCHAP: display proper message for TOO_MANY_REQUESTS, + } else if (err.httpStatus === 429) { + return _t("Your last three login attempts have failed. Please try again in a few minutes."); + // :TCHAP: end } else { return messageForConnectionError(err, serverConfig); } @@ -144,7 +150,11 @@ export function messageForConnectionError( err: Error, serverConfig: Pick, ): ReactNode { + /* :TCHAP: change default error text let errorText = _t("error|connection"); + */ + let errorText: ReactNode = Tchapi18nUtils.getServerDownMessage(""); + /* end :TCHAP:*/ if (err instanceof ConnectionError) { if ( @@ -153,6 +163,7 @@ export function messageForConnectionError( ) { return ( + {/* :TCHAP: customize error message {_t( "error|mixed_content", {}, @@ -169,13 +180,16 @@ export function messageForConnectionError( ); }, }, - )} + )} */} + {Tchapi18nUtils.getServerDownMessage("err-01")} + {/* :TCHAP: end */} ); } return ( + {/* :TCHAP: customize error message {_t( "error|tls", {}, @@ -186,7 +200,9 @@ export function messageForConnectionError( ), }, - )} + )} */} + {Tchapi18nUtils.getServerDownMessage("err-02")} + {/* :TCHAP: end */} ); } else if (err instanceof MatrixError) { diff --git a/src/utils/MultiInviter.ts b/src/utils/MultiInviter.ts index de8ea1d7bcd..0fb63674fc9 100644 --- a/src/utils/MultiInviter.ts +++ b/src/utils/MultiInviter.ts @@ -244,6 +244,14 @@ export default class MultiInviter { return; } + //:tchap: as per endpoint spec https://matrix.org/docs/spec/r0.0.0/client_server.html#post-matrix-client-r0-rooms-roomid-invite + // when user is already in the room, we receive a M_FORBIDDEN code instead of a USER_ALREADY_JOINED + // based on err message, the errcode can be fixed + if(err.message.includes("is already in the room")){ + err.errcode = USER_ALREADY_JOINED; + } + //:tchap: end + logger.error(err); const room = this.roomId ? this.matrixClient.getRoom(this.roomId) : null;