mirror of
https://github.com/element-hq/element-web
synced 2024-11-25 10:45:51 +03:00
Merge branch 'develop' into PlaybackContainer
This commit is contained in:
commit
a0f1cb4c47
307 changed files with 4618 additions and 3366 deletions
26
.eslintrc.js
26
.eslintrc.js
|
@ -165,10 +165,31 @@ module.exports = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ["test/**/*.{ts,tsx}", "cypress/**/*.ts"],
|
files: ["test/**/*.{ts,tsx}", "cypress/**/*.ts"],
|
||||||
|
extends: ["plugin:matrix-org/jest"],
|
||||||
rules: {
|
rules: {
|
||||||
// We don't need super strict typing in test utilities
|
// We don't need super strict typing in test utilities
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"@typescript-eslint/explicit-member-accessibility": "off",
|
"@typescript-eslint/explicit-member-accessibility": "off",
|
||||||
|
|
||||||
|
// Jest/Cypress specific
|
||||||
|
|
||||||
|
// Disabled tests are a reality for now but as soon as all of the xits are
|
||||||
|
// eliminated, we should enforce this.
|
||||||
|
"jest/no-disabled-tests": "off",
|
||||||
|
// TODO: There are many tests with invalid expects that should be fixed,
|
||||||
|
// https://github.com/vector-im/element-web/issues/24709
|
||||||
|
"jest/valid-expect": "off",
|
||||||
|
// TODO: There are many cases to refactor away,
|
||||||
|
// https://github.com/vector-im/element-web/issues/24710
|
||||||
|
"jest/no-conditional-expect": "off",
|
||||||
|
// Also treat "oldBackendOnly" as a test function.
|
||||||
|
// Used in some crypto tests.
|
||||||
|
"jest/no-standalone-expect": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
additionalTestBlockFunctions: ["beforeAll", "beforeEach", "oldBackendOnly"],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -176,6 +197,11 @@ module.exports = {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: ["./cypress/tsconfig.json"],
|
project: ["./cypress/tsconfig.json"],
|
||||||
},
|
},
|
||||||
|
rules: {
|
||||||
|
// Cypress "promises" work differently - disable some related rules
|
||||||
|
"jest/valid-expect-in-promise": "off",
|
||||||
|
"jest/no-done-callback": "off",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
settings: {
|
settings: {
|
||||||
|
|
47
CHANGELOG.md
47
CHANGELOG.md
|
@ -1,3 +1,50 @@
|
||||||
|
Changes in [3.67.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.67.0) (2023-02-28)
|
||||||
|
=====================================================================================================
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
* Fix block code styling in rich text editor ([\#10246](https://github.com/matrix-org/matrix-react-sdk/pull/10246)). Contributed by @alunturner.
|
||||||
|
* Poll history: fetch more poll history ([\#10235](https://github.com/matrix-org/matrix-react-sdk/pull/10235)). Contributed by @kerryarchibald.
|
||||||
|
* Sort short/exact emoji matches before longer incomplete matches ([\#10212](https://github.com/matrix-org/matrix-react-sdk/pull/10212)). Fixes vector-im/element-web#23210. Contributed by @grimhilt.
|
||||||
|
* Poll history: detail screen ([\#10172](https://github.com/matrix-org/matrix-react-sdk/pull/10172)). Contributed by @kerryarchibald.
|
||||||
|
* Provide a more detailed error message than "No known servers" ([\#6048](https://github.com/matrix-org/matrix-react-sdk/pull/6048)). Fixes vector-im/element-web#13247. Contributed by @aaronraimist.
|
||||||
|
* Say when a call was answered from a different device ([\#10224](https://github.com/matrix-org/matrix-react-sdk/pull/10224)).
|
||||||
|
* Widget permissions customizations using module api ([\#10121](https://github.com/matrix-org/matrix-react-sdk/pull/10121)). Contributed by @maheichyk.
|
||||||
|
* Fix copy button icon overlapping with copyable text ([\#10227](https://github.com/matrix-org/matrix-react-sdk/pull/10227)). Contributed by @Adesh-Pandey.
|
||||||
|
* Support joining non-peekable rooms via the module API ([\#10154](https://github.com/matrix-org/matrix-react-sdk/pull/10154)). Contributed by @maheichyk.
|
||||||
|
* The "new login" toast does now display the same device information as in the settings. "No" does now open the device settings. "Yes, it was me" dismisses the toast. ([\#10200](https://github.com/matrix-org/matrix-react-sdk/pull/10200)).
|
||||||
|
* Do not prompt for a password when doing a „reset all“ after login ([\#10208](https://github.com/matrix-org/matrix-react-sdk/pull/10208)).
|
||||||
|
* Display "The sender has blocked you from receiving this message" error message instead of "Unable to decrypt message" ([\#10202](https://github.com/matrix-org/matrix-react-sdk/pull/10202)). Contributed by @florianduros.
|
||||||
|
* Polls: show warning about undecryptable relations ([\#10179](https://github.com/matrix-org/matrix-react-sdk/pull/10179)). Contributed by @kerryarchibald.
|
||||||
|
* Poll history: fetch last 30 days of polls ([\#10157](https://github.com/matrix-org/matrix-react-sdk/pull/10157)). Contributed by @kerryarchibald.
|
||||||
|
* Poll history - ended polls list items ([\#10119](https://github.com/matrix-org/matrix-react-sdk/pull/10119)). Contributed by @kerryarchibald.
|
||||||
|
* Remove threads labs flag and the ability to disable threads ([\#9878](https://github.com/matrix-org/matrix-react-sdk/pull/9878)). Fixes vector-im/element-web#24365.
|
||||||
|
* Show a success dialog after setting up the key backup ([\#10177](https://github.com/matrix-org/matrix-react-sdk/pull/10177)). Fixes vector-im/element-web#24487.
|
||||||
|
* Release Sign in with QR out of labs ([\#10066](https://github.com/matrix-org/matrix-react-sdk/pull/10066)). Contributed by @hughns.
|
||||||
|
* Hide indent button in rte ([\#10149](https://github.com/matrix-org/matrix-react-sdk/pull/10149)). Contributed by @alunturner.
|
||||||
|
* Add option to find own location in map views ([\#10083](https://github.com/matrix-org/matrix-react-sdk/pull/10083)).
|
||||||
|
* Render poll end events in timeline ([\#10027](https://github.com/matrix-org/matrix-react-sdk/pull/10027)). Contributed by @kerryarchibald.
|
||||||
|
|
||||||
|
## 🐛 Bug Fixes
|
||||||
|
* Use the room avatar as a placeholder in calls ([\#10231](https://github.com/matrix-org/matrix-react-sdk/pull/10231)).
|
||||||
|
* Fix calls showing as 'connecting' after hangup ([\#10223](https://github.com/matrix-org/matrix-react-sdk/pull/10223)).
|
||||||
|
* Stop access token overflowing the box ([\#10069](https://github.com/matrix-org/matrix-react-sdk/pull/10069)). Fixes vector-im/element-web#24023. Contributed by @sbjaj33.
|
||||||
|
* Prevent multiple Jitsi calls started at the same time ([\#10183](https://github.com/matrix-org/matrix-react-sdk/pull/10183)). Fixes vector-im/element-web#23009.
|
||||||
|
* Make localization keys compatible with agglutinative and/or SOV type languages ([\#10159](https://github.com/matrix-org/matrix-react-sdk/pull/10159)). Contributed by @luixxiul.
|
||||||
|
* Add link to next file in the export ([\#10190](https://github.com/matrix-org/matrix-react-sdk/pull/10190)). Fixes vector-im/element-web#20272. Contributed by @grimhilt.
|
||||||
|
* Ended poll tiles: add ended the poll message ([\#10193](https://github.com/matrix-org/matrix-react-sdk/pull/10193)). Fixes vector-im/element-web#24579. Contributed by @kerryarchibald.
|
||||||
|
* Fix accidentally inverted condition for room ordering ([\#10178](https://github.com/matrix-org/matrix-react-sdk/pull/10178)). Fixes vector-im/element-web#24527. Contributed by @justjanne.
|
||||||
|
* Re-focus the composer on dialogue quit ([\#10007](https://github.com/matrix-org/matrix-react-sdk/pull/10007)). Fixes vector-im/element-web#22832. Contributed by @Ashu999.
|
||||||
|
* Try to resolve emails before creating a DM ([\#10164](https://github.com/matrix-org/matrix-react-sdk/pull/10164)).
|
||||||
|
* Disable poll response loading test ([\#10168](https://github.com/matrix-org/matrix-react-sdk/pull/10168)). Contributed by @justjanne.
|
||||||
|
* Fix email lookup in invite dialog ([\#10150](https://github.com/matrix-org/matrix-react-sdk/pull/10150)). Fixes vector-im/element-web#23353.
|
||||||
|
* Remove duplicate white space characters from translation keys ([\#10152](https://github.com/matrix-org/matrix-react-sdk/pull/10152)). Contributed by @luixxiul.
|
||||||
|
* Fix the caption of new sessions manager on Labs settings page for localization ([\#10143](https://github.com/matrix-org/matrix-react-sdk/pull/10143)). Contributed by @luixxiul.
|
||||||
|
* Prevent start another DM with a user if one already exists ([\#10127](https://github.com/matrix-org/matrix-react-sdk/pull/10127)). Fixes vector-im/element-web#23138.
|
||||||
|
* Remove white space characters before the horizontal ellipsis ([\#10130](https://github.com/matrix-org/matrix-react-sdk/pull/10130)). Contributed by @luixxiul.
|
||||||
|
* Fix Selectable Text on 'Delete All' and 'Retry All' Buttons ([\#10128](https://github.com/matrix-org/matrix-react-sdk/pull/10128)). Fixes vector-im/element-web#23232. Contributed by @akshattchhabra.
|
||||||
|
* Correctly Identify emoticons ([\#10108](https://github.com/matrix-org/matrix-react-sdk/pull/10108)). Fixes vector-im/element-web#19472. Contributed by @adarsh-sgh.
|
||||||
|
* Remove a redundant white space ([\#10129](https://github.com/matrix-org/matrix-react-sdk/pull/10129)). Contributed by @luixxiul.
|
||||||
|
|
||||||
Changes in [3.66.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.66.0) (2023-02-14)
|
Changes in [3.66.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.66.0) (2023-02-14)
|
||||||
=====================================================================================================
|
=====================================================================================================
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
Copyright 2022-2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -61,4 +61,31 @@ describe("Create Room", () => {
|
||||||
cy.contains(".mx_RoomHeader_nametext", name);
|
cy.contains(".mx_RoomHeader_nametext", name);
|
||||||
cy.contains(".mx_RoomHeader_topic", topic);
|
cy.contains(".mx_RoomHeader_topic", topic);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should create a room with a long room name, which is displayed with ellipsis", () => {
|
||||||
|
let roomId: string;
|
||||||
|
const LONG_ROOM_NAME =
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore " +
|
||||||
|
"et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " +
|
||||||
|
"aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum " +
|
||||||
|
"dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui " +
|
||||||
|
"officia deserunt mollit anim id est laborum.";
|
||||||
|
|
||||||
|
cy.createRoom({ name: LONG_ROOM_NAME }).then((_roomId) => {
|
||||||
|
roomId = _roomId;
|
||||||
|
cy.visit("/#/room/" + roomId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait until the room name is set
|
||||||
|
cy.get(".mx_RoomHeader_nametext").contains("Lorem ipsum");
|
||||||
|
|
||||||
|
// Make sure size of buttons on RoomHeader (except .mx_RoomHeader_name) are specified
|
||||||
|
// and the buttons are not compressed
|
||||||
|
// TODO: use a same class name
|
||||||
|
cy.get(".mx_RoomHeader_button").should("have.css", "height", "32px").should("have.css", "width", "32px");
|
||||||
|
cy.get(".mx_HeaderButtons > .mx_RightPanel_headerButton")
|
||||||
|
.should("have.css", "height", "32px")
|
||||||
|
.should("have.css", "width", "32px");
|
||||||
|
cy.get(".mx_RoomHeader").percySnapshotElement("Room header with a long room name");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -164,7 +164,7 @@ describe("Decryption Failure Bar", () => {
|
||||||
cy.contains(".mx_DecryptionFailureBar_button", "Resend key requests").should("not.exist");
|
cy.contains(".mx_DecryptionFailureBar_button", "Resend key requests").should("not.exist");
|
||||||
|
|
||||||
cy.get(".mx_DecryptionFailureBar").percySnapshotElement(
|
cy.get(".mx_DecryptionFailureBar").percySnapshotElement(
|
||||||
"DecryptionFailureBar prompts user to open another device, " + "without Resend Key Requests button",
|
"DecryptionFailureBar prompts user to open another device, without Resend Key Requests button",
|
||||||
{
|
{
|
||||||
widths: [320, 640],
|
widths: [320, 640],
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { HomeserverInstance } from "../../plugins/utils/homeserver";
|
||||||
import { MatrixClient } from "../../global";
|
import { MatrixClient } from "../../global";
|
||||||
import Chainable = Cypress.Chainable;
|
import Chainable = Cypress.Chainable;
|
||||||
|
|
||||||
const hideTimestampCSS = ".mx_MessageTimestamp { visibility: hidden !important; }";
|
const hidePercyCSS = ".mx_MessageTimestamp, .mx_RoomView_myReadMarker { visibility: hidden !important; }";
|
||||||
|
|
||||||
describe("Polls", () => {
|
describe("Polls", () => {
|
||||||
let homeserver: HomeserverInstance;
|
let homeserver: HomeserverInstance;
|
||||||
|
@ -133,7 +133,7 @@ describe("Polls", () => {
|
||||||
.as("pollId");
|
.as("pollId");
|
||||||
|
|
||||||
cy.get<string>("@pollId").then((pollId) => {
|
cy.get<string>("@pollId").then((pollId) => {
|
||||||
getPollTile(pollId).percySnapshotElement("Polls Timeline tile - no votes", { percyCSS: hideTimestampCSS });
|
getPollTile(pollId).percySnapshotElement("Polls Timeline tile - no votes", { percyCSS: hidePercyCSS });
|
||||||
|
|
||||||
// Bot votes 'Maybe' in the poll
|
// Bot votes 'Maybe' in the poll
|
||||||
botVoteForOption(bot, roomId, pollId, pollParams.options[2]);
|
botVoteForOption(bot, roomId, pollId, pollParams.options[2]);
|
||||||
|
|
|
@ -356,7 +356,7 @@ describe("Sliding Sync", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Regression test for https://github.com/vector-im/element-web/issues/21462
|
// Regression test for https://github.com/vector-im/element-web/issues/21462
|
||||||
it("should not cancel replies when permalinks are clicked ", () => {
|
it("should not cancel replies when permalinks are clicked", () => {
|
||||||
cy.get<string>("@roomId").then((roomId) => {
|
cy.get<string>("@roomId").then((roomId) => {
|
||||||
// we require a first message as you cannot click the permalink text with the avatar in the way
|
// we require a first message as you cannot click the permalink text with the avatar in the way
|
||||||
return cy
|
return cy
|
||||||
|
|
|
@ -24,7 +24,7 @@ import Timeoutable = Cypress.Timeoutable;
|
||||||
import Withinable = Cypress.Withinable;
|
import Withinable = Cypress.Withinable;
|
||||||
import Shadow = Cypress.Shadow;
|
import Shadow = Cypress.Shadow;
|
||||||
|
|
||||||
export enum Filter {
|
enum Filter {
|
||||||
People = "people",
|
People = "people",
|
||||||
PublicRooms = "public_rooms",
|
PublicRooms = "public_rooms",
|
||||||
}
|
}
|
||||||
|
@ -297,27 +297,28 @@ describe("Spotlight", () => {
|
||||||
|
|
||||||
// TODO: We currently can’t test finding rooms on other homeservers/other protocols
|
// TODO: We currently can’t test finding rooms on other homeservers/other protocols
|
||||||
// We obviously don’t have federation or bridges in cypress tests
|
// We obviously don’t have federation or bridges in cypress tests
|
||||||
/*
|
it.skip("should find unknown public rooms on other homeservers", () => {
|
||||||
const room3Name = "Matrix HQ";
|
cy.openSpotlightDialog()
|
||||||
const room3Id = "#matrix:matrix.org";
|
.within(() => {
|
||||||
|
cy.spotlightFilter(Filter.PublicRooms);
|
||||||
it("should find unknown public rooms on other homeservers", () => {
|
cy.spotlightSearch().clear().type(room3Name);
|
||||||
cy.openSpotlightDialog().within(() => {
|
cy.get("[aria-haspopup=true][role=button]").click();
|
||||||
cy.spotlightFilter(Filter.PublicRooms);
|
})
|
||||||
cy.spotlightSearch().clear().type(room3Name);
|
.then(() => {
|
||||||
cy.get("[aria-haspopup=true][role=button]").click();
|
cy.contains(".mx_GenericDropdownMenu_Option--header", "matrix.org")
|
||||||
}).then(() => {
|
.next("[role=menuitemradio]")
|
||||||
cy.contains(".mx_GenericDropdownMenu_Option--header", "matrix.org")
|
.click();
|
||||||
.next("[role=menuitemradio]")
|
cy.wait(3_600_000);
|
||||||
.click();
|
})
|
||||||
cy.wait(3_600_000);
|
.then(() =>
|
||||||
}).then(() => cy.spotlightDialog().within(() => {
|
cy.spotlightDialog().within(() => {
|
||||||
cy.spotlightResults().should("have.length", 1);
|
cy.spotlightResults().should("have.length", 1);
|
||||||
cy.spotlightResults().eq(0).should("contain", room3Name);
|
cy.spotlightResults().eq(0).should("contain", room3Name);
|
||||||
cy.spotlightResults().eq(0).should("contain", room3Id);
|
cy.spotlightResults().eq(0).should("contain", room3Id);
|
||||||
}));
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
it("should find known people", () => {
|
it("should find known people", () => {
|
||||||
cy.openSpotlightDialog()
|
cy.openSpotlightDialog()
|
||||||
.within(() => {
|
.within(() => {
|
||||||
|
|
|
@ -54,9 +54,20 @@ describe("Threads", () => {
|
||||||
cy.visit("/#/room/" + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --MessageTimestamp-color = #acacac = rgb(172, 172, 172)
|
||||||
|
// See: _MessageTimestamp.pcss
|
||||||
|
const MessageTimestampColor = "rgb(172, 172, 172)";
|
||||||
|
|
||||||
// User sends message
|
// User sends message
|
||||||
cy.get(".mx_RoomView_body .mx_BasicMessageComposer_input").type("Hello Mr. Bot{enter}");
|
cy.get(".mx_RoomView_body .mx_BasicMessageComposer_input").type("Hello Mr. Bot{enter}");
|
||||||
|
|
||||||
|
// Check the colour of timestamp on the main timeline
|
||||||
|
cy.get(".mx_RoomView_body .mx_EventTile_last .mx_EventTile_line .mx_MessageTimestamp").should(
|
||||||
|
"have.css",
|
||||||
|
"color",
|
||||||
|
MessageTimestampColor,
|
||||||
|
);
|
||||||
|
|
||||||
// Wait for message to send, get its ID and save as @threadId
|
// Wait for message to send, get its ID and save as @threadId
|
||||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "Hello Mr. Bot")
|
||||||
.invoke("attr", "data-scroll-tokens")
|
.invoke("attr", "data-scroll-tokens")
|
||||||
|
@ -78,6 +89,13 @@ describe("Threads", () => {
|
||||||
// User responds in thread
|
// User responds in thread
|
||||||
cy.get(".mx_ThreadView .mx_BasicMessageComposer_input").type("Test{enter}");
|
cy.get(".mx_ThreadView .mx_BasicMessageComposer_input").type("Test{enter}");
|
||||||
|
|
||||||
|
// Check the colour of timestamp on EventTile in a thread (mx_ThreadView)
|
||||||
|
cy.get(".mx_ThreadView .mx_EventTile_last .mx_EventTile_line .mx_MessageTimestamp").should(
|
||||||
|
"have.css",
|
||||||
|
"color",
|
||||||
|
MessageTimestampColor,
|
||||||
|
);
|
||||||
|
|
||||||
// User asserts summary was updated correctly
|
// User asserts summary was updated correctly
|
||||||
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "Tom");
|
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_sender").should("contain", "Tom");
|
||||||
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should("contain", "Test");
|
cy.get(".mx_RoomView_body .mx_ThreadSummary .mx_ThreadSummary_content").should("contain", "Test");
|
||||||
|
@ -130,6 +148,10 @@ describe("Threads", () => {
|
||||||
cy.get(".mx_ThreadPanel .mx_EventTile_last").within(() => {
|
cy.get(".mx_ThreadPanel .mx_EventTile_last").within(() => {
|
||||||
cy.get(".mx_EventTile_body").should("contain", "Hello Mr. Bot");
|
cy.get(".mx_EventTile_body").should("contain", "Hello Mr. Bot");
|
||||||
cy.get(".mx_ThreadSummary_content").should("contain", "How are things?");
|
cy.get(".mx_ThreadSummary_content").should("contain", "How are things?");
|
||||||
|
|
||||||
|
// Check the colour of timestamp on thread list
|
||||||
|
cy.get(".mx_EventTile_details .mx_MessageTimestamp").should("have.css", "color", MessageTimestampColor);
|
||||||
|
|
||||||
// User opens thread via threads list
|
// User opens thread via threads list
|
||||||
cy.get(".mx_EventTile_line").click();
|
cy.get(".mx_EventTile_line").click();
|
||||||
});
|
});
|
||||||
|
|
|
@ -185,9 +185,8 @@ describe("Timeline", () => {
|
||||||
.should("have.css", "margin-inline-start", "104px")
|
.should("have.css", "margin-inline-start", "104px")
|
||||||
.should("have.css", "inset-inline-start", "0px");
|
.should("have.css", "inset-inline-start", "0px");
|
||||||
|
|
||||||
// Exclude timestamp from snapshot
|
// Exclude timestamp and read marker from snapshot
|
||||||
const percyCSS =
|
const percyCSS = ".mx_MessageTimestamp, .mx_RoomView_myReadMarker { visibility: hidden !important; }";
|
||||||
".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp { visibility: hidden !important; }";
|
|
||||||
cy.get(".mx_MainSplit").percySnapshotElement("Event line with inline start margin on IRC layout", {
|
cy.get(".mx_MainSplit").percySnapshotElement("Event line with inline start margin on IRC layout", {
|
||||||
percyCSS,
|
percyCSS,
|
||||||
});
|
});
|
||||||
|
@ -213,8 +212,8 @@ describe("Timeline", () => {
|
||||||
// Click timestamp to highlight hidden event line
|
// Click timestamp to highlight hidden event line
|
||||||
cy.get(".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp").click();
|
cy.get(".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp").click();
|
||||||
|
|
||||||
// Exclude timestamp from snapshot
|
// Exclude timestamp and read marker from snapshot
|
||||||
const percyCSS = ".mx_RoomView_body .mx_EventTile .mx_MessageTimestamp { visibility: hidden !important; }";
|
const percyCSS = ".mx_MessageTimestamp, .mx_RoomView_myReadMarker { visibility: hidden !important; }";
|
||||||
|
|
||||||
// should not add inline start padding to a hidden event line on IRC layout
|
// should not add inline start padding to a hidden event line on IRC layout
|
||||||
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||||
|
@ -239,7 +238,11 @@ describe("Timeline", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should click top left of view source event toggle", () => {
|
it("should click view source event toggle", () => {
|
||||||
|
// This test checks:
|
||||||
|
// 1. clickability of top left of view source event toggle
|
||||||
|
// 2. clickability of view source toggle on IRC layout
|
||||||
|
|
||||||
sendEvent(roomId);
|
sendEvent(roomId);
|
||||||
cy.visit("/#/room/" + roomId);
|
cy.visit("/#/room/" + roomId);
|
||||||
cy.setSettingValue("showHiddenEventsInTimeline", null, SettingLevel.DEVICE, true);
|
cy.setSettingValue("showHiddenEventsInTimeline", null, SettingLevel.DEVICE, true);
|
||||||
|
@ -255,8 +258,10 @@ describe("Timeline", () => {
|
||||||
});
|
});
|
||||||
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "MessageEdit").should("exist");
|
cy.contains(".mx_RoomView_body .mx_EventTile[data-scroll-tokens]", "MessageEdit").should("exist");
|
||||||
|
|
||||||
|
// 1. clickability of top left of view source event toggle
|
||||||
|
|
||||||
// Click top left of the event toggle, which should not be covered by MessageActionBar's safe area
|
// Click top left of the event toggle, which should not be covered by MessageActionBar's safe area
|
||||||
cy.get(".mx_EventTile:not(:first-child) .mx_ViewSourceEvent")
|
cy.get(".mx_EventTile_last[data-layout=group] .mx_ViewSourceEvent")
|
||||||
.should("exist")
|
.should("exist")
|
||||||
.realHover()
|
.realHover()
|
||||||
.within(() => {
|
.within(() => {
|
||||||
|
@ -264,7 +269,41 @@ describe("Timeline", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make sure the expand toggle worked
|
// Make sure the expand toggle worked
|
||||||
cy.get(".mx_EventTile .mx_ViewSourceEvent_expanded .mx_ViewSourceEvent_toggle").should("be.visible");
|
cy.get(".mx_EventTile .mx_ViewSourceEvent_expanded").should("be.visible");
|
||||||
|
|
||||||
|
// Click again to collapse the source
|
||||||
|
cy.get(".mx_EventTile_last[data-layout=group] .mx_ViewSourceEvent")
|
||||||
|
.should("exist")
|
||||||
|
.realHover()
|
||||||
|
.within(() => {
|
||||||
|
cy.get(".mx_ViewSourceEvent_toggle").click("topLeft", { force: false });
|
||||||
|
});
|
||||||
|
cy.get(".mx_EventTile .mx_ViewSourceEvent_expanded").should("not.exist");
|
||||||
|
|
||||||
|
// 2. clickability of view source toggle on IRC layout
|
||||||
|
|
||||||
|
// Enable IRC layout
|
||||||
|
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||||
|
|
||||||
|
// Exclude timestamp from snapshot
|
||||||
|
const percyCSS = ".mx_MessageTimestamp { visibility: hidden !important; }";
|
||||||
|
|
||||||
|
// Hover the view source toggle on IRC layout
|
||||||
|
cy.get(".mx_GenericEventListSummary[data-layout=irc] .mx_EventTile .mx_ViewSourceEvent")
|
||||||
|
.should("exist")
|
||||||
|
.realHover()
|
||||||
|
.percySnapshotElement("Hovered hidden event line on IRC layout", { percyCSS });
|
||||||
|
|
||||||
|
// Click view source event toggle
|
||||||
|
cy.get(".mx_GenericEventListSummary[data-layout=irc] .mx_EventTile .mx_ViewSourceEvent")
|
||||||
|
.should("exist")
|
||||||
|
.realHover()
|
||||||
|
.within(() => {
|
||||||
|
cy.get(".mx_ViewSourceEvent_toggle").click("topLeft", { force: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure the expand toggle worked
|
||||||
|
cy.get(".mx_EventTile[data-layout=irc] .mx_ViewSourceEvent_expanded").should("be.visible");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should click 'collapse' link button on the first hovered info event line on bubble layout", () => {
|
it("should click 'collapse' link button on the first hovered info event line on bubble layout", () => {
|
||||||
|
@ -336,9 +375,8 @@ describe("Timeline", () => {
|
||||||
|
|
||||||
cy.checkA11y();
|
cy.checkA11y();
|
||||||
|
|
||||||
// Exclude timestamp from snapshot
|
// Exclude timestamp and read marker from snapshot
|
||||||
const percyCSS =
|
const percyCSS = ".mx_MessageTimestamp, .mx_RoomView_myReadMarker { visibility: hidden !important; }";
|
||||||
".mx_RoomView_body .mx_EventTile_info .mx_MessageTimestamp { visibility: hidden !important; }";
|
|
||||||
cy.get(".mx_EventTile_last").percySnapshotElement("URL Preview", {
|
cy.get(".mx_EventTile_last").percySnapshotElement("URL Preview", {
|
||||||
percyCSS,
|
percyCSS,
|
||||||
widths: [800, 400],
|
widths: [800, 400],
|
||||||
|
|
|
@ -41,7 +41,9 @@ declare global {
|
||||||
Cypress.Commands.add("percySnapshotElement", { prevSubject: "element" }, (subject, name, options) => {
|
Cypress.Commands.add("percySnapshotElement", { prevSubject: "element" }, (subject, name, options) => {
|
||||||
if (!options?.allowSpinners) {
|
if (!options?.allowSpinners) {
|
||||||
// Await spinners to vanish
|
// Await spinners to vanish
|
||||||
cy.get(".mx_Spinner").should("not.exist");
|
cy.get(".mx_Spinner", { log: false }).should("not.exist");
|
||||||
|
// But like really no more spinners please
|
||||||
|
cy.get(".mx_Spinner", { log: false }).should("not.exist");
|
||||||
}
|
}
|
||||||
cy.percySnapshot(name, {
|
cy.percySnapshot(name, {
|
||||||
domTransformation: (documentClone) => scope(documentClone, subject.selector),
|
domTransformation: (documentClone) => scope(documentClone, subject.selector),
|
||||||
|
|
22
package.json
22
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "3.66.0",
|
"version": "3.67.0",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -54,6 +54,10 @@
|
||||||
"test:cypress:open": "cypress open",
|
"test:cypress:open": "cypress open",
|
||||||
"coverage": "yarn test --coverage"
|
"coverage": "yarn test --coverage"
|
||||||
},
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"@types/react-dom": "17.0.19",
|
||||||
|
"@types/react": "17.0.53"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"@matrix-org/analytics-events": "^0.4.0",
|
"@matrix-org/analytics-events": "^0.4.0",
|
||||||
|
@ -92,7 +96,7 @@
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"maplibre-gl": "^2.0.0",
|
"maplibre-gl": "^2.0.0",
|
||||||
"matrix-encrypt-attachment": "^1.0.3",
|
"matrix-encrypt-attachment": "^1.0.3",
|
||||||
"matrix-events-sdk": "2.0.0",
|
"matrix-events-sdk": "0.0.1",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||||
"matrix-widget-api": "^1.1.1",
|
"matrix-widget-api": "^1.1.1",
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
|
@ -143,7 +147,6 @@
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^12.1.5",
|
"@testing-library/react": "^12.1.5",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.4.3",
|
||||||
"@types/classnames": "^2.2.11",
|
|
||||||
"@types/commonmark": "^0.27.4",
|
"@types/commonmark": "^0.27.4",
|
||||||
"@types/counterpart": "^0.18.1",
|
"@types/counterpart": "^0.18.1",
|
||||||
"@types/css-font-loading-module": "^0.0.7",
|
"@types/css-font-loading-module": "^0.0.7",
|
||||||
|
@ -164,9 +167,9 @@
|
||||||
"@types/pako": "^2.0.0",
|
"@types/pako": "^2.0.0",
|
||||||
"@types/parse5": "^6.0.0",
|
"@types/parse5": "^6.0.0",
|
||||||
"@types/qrcode": "^1.3.5",
|
"@types/qrcode": "^1.3.5",
|
||||||
"@types/react": "17.0.49",
|
"@types/react": "17.0.53",
|
||||||
"@types/react-beautiful-dnd": "^13.0.0",
|
"@types/react-beautiful-dnd": "^13.0.0",
|
||||||
"@types/react-dom": "17.0.17",
|
"@types/react-dom": "17.0.19",
|
||||||
"@types/react-transition-group": "^4.4.0",
|
"@types/react-transition-group": "^4.4.0",
|
||||||
"@types/sanitize-html": "2.8.0",
|
"@types/sanitize-html": "2.8.0",
|
||||||
"@types/tar-js": "^0.3.2",
|
"@types/tar-js": "^0.3.2",
|
||||||
|
@ -191,8 +194,9 @@
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-deprecate": "^0.7.0",
|
"eslint-plugin-deprecate": "^0.7.0",
|
||||||
"eslint-plugin-import": "^2.25.4",
|
"eslint-plugin-import": "^2.25.4",
|
||||||
|
"eslint-plugin-jest": "^27.2.1",
|
||||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||||
"eslint-plugin-matrix-org": "0.10.0",
|
"eslint-plugin-matrix-org": "1.0.0",
|
||||||
"eslint-plugin-react": "^7.28.0",
|
"eslint-plugin-react": "^7.28.0",
|
||||||
"eslint-plugin-react-hooks": "^4.3.0",
|
"eslint-plugin-react-hooks": "^4.3.0",
|
||||||
"eslint-plugin-unicorn": "^45.0.0",
|
"eslint-plugin-unicorn": "^45.0.0",
|
||||||
|
@ -211,10 +215,10 @@
|
||||||
"postcss-scss": "^4.0.4",
|
"postcss-scss": "^4.0.4",
|
||||||
"prettier": "2.8.0",
|
"prettier": "2.8.0",
|
||||||
"raw-loader": "^4.0.2",
|
"raw-loader": "^4.0.2",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^4.0.0",
|
||||||
"stylelint": "^14.9.1",
|
"stylelint": "^15.0.0",
|
||||||
"stylelint-config-prettier": "^9.0.4",
|
"stylelint-config-prettier": "^9.0.4",
|
||||||
"stylelint-config-standard": "^29.0.0",
|
"stylelint-config-standard": "^30.0.0",
|
||||||
"stylelint-scss": "^4.2.0",
|
"stylelint-scss": "^4.2.0",
|
||||||
"typescript": "4.9.3",
|
"typescript": "4.9.3",
|
||||||
"walk": "^2.3.14"
|
"walk": "^2.3.14"
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
@import "./components/views/beacon/_ShareLatestLocation.pcss";
|
@import "./components/views/beacon/_ShareLatestLocation.pcss";
|
||||||
@import "./components/views/beacon/_StyledLiveBeaconIcon.pcss";
|
@import "./components/views/beacon/_StyledLiveBeaconIcon.pcss";
|
||||||
@import "./components/views/context_menus/_KebabContextMenu.pcss";
|
@import "./components/views/context_menus/_KebabContextMenu.pcss";
|
||||||
|
@import "./components/views/dialogs/polls/_PollDetailHeader.pcss";
|
||||||
@import "./components/views/dialogs/polls/_PollListItem.pcss";
|
@import "./components/views/dialogs/polls/_PollListItem.pcss";
|
||||||
@import "./components/views/dialogs/polls/_PollListItemEnded.pcss";
|
@import "./components/views/dialogs/polls/_PollListItemEnded.pcss";
|
||||||
@import "./components/views/elements/_FilterDropdown.pcss";
|
@import "./components/views/elements/_FilterDropdown.pcss";
|
||||||
|
@ -135,7 +136,6 @@
|
||||||
@import "./views/dialogs/_FeedbackDialog.pcss";
|
@import "./views/dialogs/_FeedbackDialog.pcss";
|
||||||
@import "./views/dialogs/_ForwardDialog.pcss";
|
@import "./views/dialogs/_ForwardDialog.pcss";
|
||||||
@import "./views/dialogs/_GenericFeatureFeedbackDialog.pcss";
|
@import "./views/dialogs/_GenericFeatureFeedbackDialog.pcss";
|
||||||
@import "./views/dialogs/_HostSignupDialog.pcss";
|
|
||||||
@import "./views/dialogs/_IncomingSasDialog.pcss";
|
@import "./views/dialogs/_IncomingSasDialog.pcss";
|
||||||
@import "./views/dialogs/_InviteDialog.pcss";
|
@import "./views/dialogs/_InviteDialog.pcss";
|
||||||
@import "./views/dialogs/_JoinRuleDropdown.pcss";
|
@import "./views/dialogs/_JoinRuleDropdown.pcss";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -14,6 +14,14 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface IDialogProps {
|
.mx_PollDetailHeader {
|
||||||
onFinished(...args: any): void;
|
// override accessiblebutton style
|
||||||
|
font-size: $font-15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_PollDetailHeader_icon {
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
margin-right: $spacing-8;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
|
@ -16,12 +16,17 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_PollListItem {
|
.mx_PollListItem {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_PollListItem_content {
|
||||||
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
grid-gap: $spacing-8;
|
grid-gap: $spacing-8;
|
||||||
grid-template-columns: auto auto auto;
|
grid-template-columns: auto auto auto;
|
||||||
grid-template-rows: auto;
|
grid-template-rows: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
color: $primary-content;
|
color: $primary-content;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,14 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_PollListItemEnded {
|
.mx_PollListItemEnded {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_PollListItemEnded_content {
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
color: $primary-content;
|
color: $primary-content;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_PollListItemEnded_title {
|
.mx_PollListItemEnded_title {
|
||||||
|
|
|
@ -48,6 +48,9 @@ limitations under the License.
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* See: mx_RoomHeader_button, of which this is a copy.
|
||||||
|
* TODO: factor out a common component to avoid this duplication.
|
||||||
|
*/
|
||||||
.mx_RightPanel_headerButton {
|
.mx_RightPanel_headerButton {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
|
@ -142,12 +142,8 @@ limitations under the License.
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_UserMenu_contextMenu_guestPrompts,
|
|
||||||
&.mx_UserMenu_contextMenu_hostingLink {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mx_UserMenu_contextMenu_guestPrompts {
|
&.mx_UserMenu_contextMenu_guestPrompts {
|
||||||
|
padding-top: 0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
> span {
|
> span {
|
||||||
|
@ -190,10 +186,6 @@ limitations under the License.
|
||||||
mask-image: url("$(res)/img/element-icons/roomlist/dnd-cross.svg");
|
mask-image: url("$(res)/img/element-icons/roomlist/dnd-cross.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_UserMenu_iconHosting::before {
|
|
||||||
mask-image: url("$(res)/img/element-icons/brands/element.svg");
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_UserMenu_iconBell::before {
|
.mx_UserMenu_iconBell::before {
|
||||||
mask-image: url("$(res)/img/element-icons/notifications.svg");
|
mask-image: url("$(res)/img/element-icons/notifications.svg");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,132 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.mx_HostSignupDialog {
|
|
||||||
width: 90vw;
|
|
||||||
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;
|
|
||||||
|
|
||||||
.mx_HostSignupDialog_content_top {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_HostSignupDialog_paragraphs {
|
|
||||||
text-align: left;
|
|
||||||
padding-left: 25%;
|
|
||||||
padding-right: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_HostSignupDialog_buttons {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 12px;
|
|
||||||
margin: 0 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_HostSignupDialog_footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: baseline;
|
|
||||||
|
|
||||||
img {
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: none;
|
|
||||||
background-color: #fff;
|
|
||||||
min-height: 540px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_HostSignupDialog_text_dark {
|
|
||||||
color: $primary-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_HostSignupDialog_text_light {
|
|
||||||
color: $secondary-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_HostSignup_maximize_button {
|
|
||||||
mask: url("$(res)/img/element-icons/maximise-expand.svg");
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-position: center;
|
|
||||||
mask-size: cover;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
background-color: $dialog-close-fg-color;
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_HostSignup_minimize_button {
|
|
||||||
mask: url("$(res)/img/element-icons/minimise-collapse.svg");
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-position: center;
|
|
||||||
mask-size: cover;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
background-color: $dialog-close-fg-color;
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_HostSignupDialog_minimized {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 80px;
|
|
||||||
right: 26px;
|
|
||||||
width: 314px;
|
|
||||||
height: 217px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&.mx_Dialog {
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_Dialog_title {
|
|
||||||
text-align: left !important;
|
|
||||||
padding-left: 20px;
|
|
||||||
font-size: $font-15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
iframe {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border: none;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -41,10 +41,20 @@ limitations under the License.
|
||||||
.mx_PollHistoryList_noResults {
|
.mx_PollHistoryList_noResults {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 $spacing-64;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
line-height: $font-24px;
|
||||||
color: $secondary-content;
|
color: $secondary-content;
|
||||||
|
|
||||||
|
.mx_PollHistoryList_loadMorePolls {
|
||||||
|
margin-top: $spacing-16;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_PollHistoryList_loading {
|
.mx_PollHistoryList_loading {
|
||||||
|
@ -57,3 +67,7 @@ limitations under the License.
|
||||||
margin: auto auto;
|
margin: auto auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_PollHistoryList_loadMorePolls {
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
|
|
@ -16,8 +16,9 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_MessageTimestamp {
|
.mx_MessageTimestamp {
|
||||||
--MessageTimestamp-max-width: 80px;
|
--MessageTimestamp-max-width: 80px;
|
||||||
|
--MessageTimestamp-color: $event-timestamp-color;
|
||||||
|
|
||||||
color: $event-timestamp-color;
|
color: var(--MessageTimestamp-color);
|
||||||
font-size: $font-10px;
|
font-size: $font-10px;
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
display: block; /* enable the width setting below */
|
display: block; /* enable the width setting below */
|
||||||
|
|
|
@ -130,7 +130,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageTimestamp {
|
.mx_MessageTimestamp {
|
||||||
color: $event-timestamp-color;
|
color: var(--MessageTimestamp-color); /* TODO: check whether needed or not */
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_BaseCard_footer {
|
.mx_BaseCard_footer {
|
||||||
|
|
|
@ -130,12 +130,17 @@ $irc-line-height: $font-18px;
|
||||||
.mx_TextualEvent,
|
.mx_TextualEvent,
|
||||||
.mx_ViewSourceEvent,
|
.mx_ViewSourceEvent,
|
||||||
.mx_MTextBody {
|
.mx_MTextBody {
|
||||||
display: inline-block;
|
|
||||||
/* add a 1px padding top and bottom because our larger
|
/* add a 1px padding top and bottom because our larger
|
||||||
emoji font otherwise gets cropped by anti-zalgo */
|
emoji font otherwise gets cropped by anti-zalgo */
|
||||||
padding: var(--EventTile_irc_line-padding-block) 0;
|
padding: var(--EventTile_irc_line-padding-block) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_e2eIcon,
|
||||||
|
.mx_TextualEvent,
|
||||||
|
.mx_MTextBody {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_ReplyTile {
|
.mx_ReplyTile {
|
||||||
.mx_MTextBody {
|
.mx_MTextBody {
|
||||||
display: -webkit-box; /* Enable -webkit-line-clamp */
|
display: -webkit-box; /* Enable -webkit-line-clamp */
|
||||||
|
|
|
@ -191,12 +191,13 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomHeader_button {
|
.mx_RoomHeader_button {
|
||||||
position: relative;
|
cursor: pointer;
|
||||||
|
flex: 0 0 auto;
|
||||||
margin-left: 1px;
|
margin-left: 1px;
|
||||||
margin-right: 1px;
|
margin-right: 1px;
|
||||||
cursor: pointer;
|
|
||||||
height: 32px;
|
height: 32px;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
|
position: relative;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
|
|
|
@ -88,17 +88,17 @@ limitations under the License.
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code:not(pre *) {
|
||||||
font-family: $monospace-font-family !important;
|
font-family: $monospace-font-family !important;
|
||||||
background-color: $inlinecode-background-color;
|
background-color: $inlinecode-background-color;
|
||||||
border: 1px solid $inlinecode-border-color;
|
border: 1px solid $inlinecode-border-color;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: $spacing-2;
|
padding: $spacing-2;
|
||||||
}
|
|
||||||
|
|
||||||
code:empty {
|
&:empty {
|
||||||
border: unset;
|
border: unset;
|
||||||
padding: unset;
|
padding: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
.mx_IntegrationManager {
|
.mx_IntegrationManager {
|
||||||
.mx_Dialog {
|
.mx_Dialog {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
width: 60%;
|
width: 60%;
|
||||||
height: 70%;
|
height: 70%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -184,7 +184,7 @@ export default class AddThreepid {
|
||||||
* with a "message" property which contains a human-readable message detailing why
|
* with a "message" property which contains a human-readable message detailing why
|
||||||
* the request failed.
|
* the request failed.
|
||||||
*/
|
*/
|
||||||
public async checkEmailLinkClicked(): Promise<[boolean, IAuthData | Error | null] | undefined> {
|
public async checkEmailLinkClicked(): Promise<[success?: boolean, result?: IAuthData | Error | null]> {
|
||||||
try {
|
try {
|
||||||
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
|
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
|
||||||
if (this.bind) {
|
if (this.bind) {
|
||||||
|
@ -202,7 +202,7 @@ export default class AddThreepid {
|
||||||
|
|
||||||
// The spec has always required this to use UI auth but synapse briefly
|
// The spec has always required this to use UI auth but synapse briefly
|
||||||
// implemented it without, so this may just succeed and that's OK.
|
// implemented it without, so this may just succeed and that's OK.
|
||||||
return;
|
return [true];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.httpStatus !== 401 || !e.data || !e.data.flows) {
|
if (e.httpStatus !== 401 || !e.data || !e.data.flows) {
|
||||||
// doesn't look like an interactive-auth failure
|
// doesn't look like an interactive-auth failure
|
||||||
|
@ -213,8 +213,7 @@ export default class AddThreepid {
|
||||||
[SSOAuthEntry.PHASE_PREAUTH]: {
|
[SSOAuthEntry.PHASE_PREAUTH]: {
|
||||||
title: _t("Use Single Sign On to continue"),
|
title: _t("Use Single Sign On to continue"),
|
||||||
body: _t(
|
body: _t(
|
||||||
"Confirm adding this email address by using " +
|
"Confirm adding this email address by using Single Sign On to prove your identity.",
|
||||||
"Single Sign On to prove your identity.",
|
|
||||||
),
|
),
|
||||||
continueText: _t("Single Sign On"),
|
continueText: _t("Single Sign On"),
|
||||||
continueKind: "primary",
|
continueKind: "primary",
|
||||||
|
@ -226,19 +225,16 @@ export default class AddThreepid {
|
||||||
continueKind: "primary",
|
continueKind: "primary",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const { finished } = Modal.createDialog<[boolean, IAuthData | Error | null]>(
|
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
||||||
InteractiveAuthDialog,
|
title: _t("Add Email Address"),
|
||||||
{
|
matrixClient: MatrixClientPeg.get(),
|
||||||
title: _t("Add Email Address"),
|
authData: e.data,
|
||||||
matrixClient: MatrixClientPeg.get(),
|
makeRequest: this.makeAddThreepidOnlyRequest,
|
||||||
authData: e.data,
|
aestheticsForStagePhases: {
|
||||||
makeRequest: this.makeAddThreepidOnlyRequest,
|
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||||
aestheticsForStagePhases: {
|
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
||||||
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
|
||||||
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
return finished;
|
return finished;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -260,6 +256,7 @@ export default class AddThreepid {
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -333,7 +330,7 @@ export default class AddThreepid {
|
||||||
[SSOAuthEntry.PHASE_PREAUTH]: {
|
[SSOAuthEntry.PHASE_PREAUTH]: {
|
||||||
title: _t("Use Single Sign On to continue"),
|
title: _t("Use Single Sign On to continue"),
|
||||||
body: _t(
|
body: _t(
|
||||||
"Confirm adding this phone number by using " + "Single Sign On to prove your identity.",
|
"Confirm adding this phone number by using Single Sign On to prove your identity.",
|
||||||
),
|
),
|
||||||
continueText: _t("Single Sign On"),
|
continueText: _t("Single Sign On"),
|
||||||
continueKind: "primary",
|
continueKind: "primary",
|
||||||
|
|
|
@ -18,16 +18,16 @@ import React, { ComponentType } from "react";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { _t } from "./languageHandler";
|
import { _t } from "./languageHandler";
|
||||||
import { IDialogProps } from "./components/views/dialogs/IDialogProps";
|
|
||||||
import BaseDialog from "./components/views/dialogs/BaseDialog";
|
import BaseDialog from "./components/views/dialogs/BaseDialog";
|
||||||
import DialogButtons from "./components/views/elements/DialogButtons";
|
import DialogButtons from "./components/views/elements/DialogButtons";
|
||||||
import Spinner from "./components/views/elements/Spinner";
|
import Spinner from "./components/views/elements/Spinner";
|
||||||
|
|
||||||
type AsyncImport<T> = { default: T };
|
type AsyncImport<T> = { default: T };
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
// A promise which resolves with the real component
|
// A promise which resolves with the real component
|
||||||
prom: Promise<ComponentType | AsyncImport<ComponentType>>;
|
prom: Promise<ComponentType<any> | AsyncImport<ComponentType<any>>>;
|
||||||
|
onFinished(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -71,7 +71,7 @@ export default class AsyncWrapper extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onWrapperCancelClick = (): void => {
|
private onWrapperCancelClick = (): void => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
|
|
|
@ -139,14 +139,18 @@ export function getInitialLetter(name: string): string | undefined {
|
||||||
|
|
||||||
export function avatarUrlForRoom(
|
export function avatarUrlForRoom(
|
||||||
room: Room | null,
|
room: Room | null,
|
||||||
width: number,
|
width?: number,
|
||||||
height: number,
|
height?: number,
|
||||||
resizeMethod?: ResizeMethod,
|
resizeMethod?: ResizeMethod,
|
||||||
): string | null {
|
): string | null {
|
||||||
if (!room) return null; // null-guard
|
if (!room) return null; // null-guard
|
||||||
|
|
||||||
if (room.getMxcAvatarUrl()) {
|
if (room.getMxcAvatarUrl()) {
|
||||||
return mediaFromMxc(room.getMxcAvatarUrl() || undefined).getThumbnailOfSourceHttp(width, height, resizeMethod);
|
const media = mediaFromMxc(room.getMxcAvatarUrl() ?? undefined);
|
||||||
|
if (width !== undefined && height !== undefined) {
|
||||||
|
return media.getThumbnailOfSourceHttp(width, height, resizeMethod);
|
||||||
|
}
|
||||||
|
return media.srcHttp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// space rooms cannot be DMs so skip the rest
|
// space rooms cannot be DMs so skip the rest
|
||||||
|
@ -160,7 +164,11 @@ export function avatarUrlForRoom(
|
||||||
// If there are only two members in the DM use the avatar of the other member
|
// If there are only two members in the DM use the avatar of the other member
|
||||||
const otherMember = room.getAvatarFallbackMember();
|
const otherMember = room.getAvatarFallbackMember();
|
||||||
if (otherMember?.getMxcAvatarUrl()) {
|
if (otherMember?.getMxcAvatarUrl()) {
|
||||||
return mediaFromMxc(otherMember.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
|
const media = mediaFromMxc(otherMember.getMxcAvatarUrl());
|
||||||
|
if (width !== undefined && height !== undefined) {
|
||||||
|
return media.getThumbnailOfSourceHttp(width, height, resizeMethod);
|
||||||
|
}
|
||||||
|
return media.srcHttp;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -389,7 +389,7 @@ export default class ContentMessages {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tooBigFiles.length > 0) {
|
if (tooBigFiles.length > 0) {
|
||||||
const { finished } = Modal.createDialog<[boolean]>(UploadFailureDialog, {
|
const { finished } = Modal.createDialog(UploadFailureDialog, {
|
||||||
badFiles: tooBigFiles,
|
badFiles: tooBigFiles,
|
||||||
totalFiles: files.length,
|
totalFiles: files.length,
|
||||||
contentMessages: this,
|
contentMessages: this,
|
||||||
|
@ -407,7 +407,7 @@ export default class ContentMessages {
|
||||||
const loopPromiseBefore = promBefore;
|
const loopPromiseBefore = promBefore;
|
||||||
|
|
||||||
if (!uploadAll) {
|
if (!uploadAll) {
|
||||||
const { finished } = Modal.createDialog<[boolean, boolean]>(UploadConfirmDialog, {
|
const { finished } = Modal.createDialog(UploadConfirmDialog, {
|
||||||
file,
|
file,
|
||||||
currentIndex: i,
|
currentIndex: i,
|
||||||
totalFiles: okFiles.length,
|
totalFiles: okFiles.length,
|
||||||
|
|
|
@ -148,19 +148,6 @@ export interface IConfigOptions {
|
||||||
analytics_owner?: string; // defaults to `brand`
|
analytics_owner?: string; // defaults to `brand`
|
||||||
privacy_policy_url?: string; // location for cookie policy
|
privacy_policy_url?: string; // location for cookie policy
|
||||||
|
|
||||||
// Server hosting upsell options
|
|
||||||
hosting_signup_link?: string; // slightly different from `host_signup`
|
|
||||||
host_signup?: {
|
|
||||||
brand?: string; // acts as the enabled flag too (truthy == show)
|
|
||||||
|
|
||||||
// Required-ness denotes when `brand` is truthy
|
|
||||||
cookie_policy_url: string;
|
|
||||||
privacy_policy_url: string;
|
|
||||||
terms_of_service_url: string;
|
|
||||||
url: string;
|
|
||||||
domains?: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
enable_presence_by_hs_url?: Record<string, boolean>; // <HomeserverName, Enabled>
|
enable_presence_by_hs_url?: Record<string, boolean>; // <HomeserverName, Enabled>
|
||||||
|
|
||||||
terms_and_conditions_links?: { url: string; text: string }[];
|
terms_and_conditions_links?: { url: string; text: string }[];
|
||||||
|
|
|
@ -58,7 +58,7 @@ import IncomingLegacyCallToast, { getIncomingLegacyCallToastKey } from "./toasts
|
||||||
import ToastStore from "./stores/ToastStore";
|
import ToastStore from "./stores/ToastStore";
|
||||||
import Resend from "./Resend";
|
import Resend from "./Resend";
|
||||||
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
|
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
|
||||||
import { KIND_CALL_TRANSFER } from "./components/views/dialogs/InviteDialogTypes";
|
import { InviteKind } from "./components/views/dialogs/InviteDialogTypes";
|
||||||
import { OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogPayload";
|
import { OpenInviteDialogPayload } from "./dispatcher/payloads/OpenInviteDialogPayload";
|
||||||
import { findDMForUser } from "./utils/dm/findDMForUser";
|
import { findDMForUser } from "./utils/dm/findDMForUser";
|
||||||
import { getJoinedNonFunctionalMembers } from "./utils/room/getJoinedNonFunctionalMembers";
|
import { getJoinedNonFunctionalMembers } from "./utils/room/getJoinedNonFunctionalMembers";
|
||||||
|
@ -737,7 +737,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
);
|
);
|
||||||
if (!stats) {
|
if (!stats) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Call statistics are undefined. The call has " + "probably failed before a peerConn was established",
|
"Call statistics are undefined. The call has probably failed before a peerConn was established",
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1214,7 +1214,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
||||||
call.setRemoteOnHold(true);
|
call.setRemoteOnHold(true);
|
||||||
dis.dispatch<OpenInviteDialogPayload>({
|
dis.dispatch<OpenInviteDialogPayload>({
|
||||||
action: Action.OpenInviteDialog,
|
action: Action.OpenInviteDialog,
|
||||||
kind: KIND_CALL_TRANSFER,
|
kind: InviteKind.CallTransfer,
|
||||||
call,
|
call,
|
||||||
analyticsName: "Transfer Call",
|
analyticsName: "Transfer Call",
|
||||||
className: "mx_InviteDialog_transferWrapper",
|
className: "mx_InviteDialog_transferWrapper",
|
||||||
|
|
|
@ -265,7 +265,7 @@ export function handleInvalidStoreError(e: InvalidStoreError): Promise<void> {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const lazyLoadEnabled = e.value;
|
const lazyLoadEnabled = e.value;
|
||||||
if (lazyLoadEnabled) {
|
if (lazyLoadEnabled) {
|
||||||
return new Promise((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
Modal.createDialog(LazyLoadingResyncDialog, {
|
Modal.createDialog(LazyLoadingResyncDialog, {
|
||||||
onFinished: resolve,
|
onFinished: resolve,
|
||||||
});
|
});
|
||||||
|
@ -275,7 +275,7 @@ export function handleInvalidStoreError(e: InvalidStoreError): Promise<void> {
|
||||||
// between LL/non-LL version on same host.
|
// between LL/non-LL version on same host.
|
||||||
// as disabling LL when previously enabled
|
// as disabling LL when previously enabled
|
||||||
// is a strong indicator of this (/develop & /app)
|
// is a strong indicator of this (/develop & /app)
|
||||||
return new Promise((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
Modal.createDialog(LazyLoadingDisabledDialog, {
|
Modal.createDialog(LazyLoadingDisabledDialog, {
|
||||||
onFinished: resolve,
|
onFinished: resolve,
|
||||||
host: window.location.host,
|
host: window.location.host,
|
||||||
|
|
111
src/Modal.tsx
111
src/Modal.tsx
|
@ -27,34 +27,35 @@ import AsyncWrapper from "./AsyncWrapper";
|
||||||
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||||
const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer";
|
const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer";
|
||||||
|
|
||||||
export interface IModal<T extends any[]> {
|
// Type which accepts a React Component which looks like a Modal (accepts an onFinished prop)
|
||||||
|
export type ComponentType = React.ComponentType<{
|
||||||
|
onFinished?(...args: any): void;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
// Generic type which returns the props of the Modal component with the onFinished being optional.
|
||||||
|
export type ComponentProps<C extends ComponentType> = Omit<React.ComponentProps<C>, "onFinished"> &
|
||||||
|
Partial<Pick<React.ComponentProps<C>, "onFinished">>;
|
||||||
|
|
||||||
|
export interface IModal<C extends ComponentType> {
|
||||||
elem: React.ReactNode;
|
elem: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
beforeClosePromise?: Promise<boolean>;
|
beforeClosePromise?: Promise<boolean>;
|
||||||
closeReason?: string;
|
closeReason?: string;
|
||||||
onBeforeClose?(reason?: string): Promise<boolean>;
|
onBeforeClose?(reason?: string): Promise<boolean>;
|
||||||
onFinished?(...args: T): void;
|
onFinished: ComponentProps<C>["onFinished"];
|
||||||
close(...args: T): void;
|
close(...args: Parameters<ComponentProps<C>["onFinished"]>): void;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IHandle<T extends any[]> {
|
export interface IHandle<C extends ComponentType> {
|
||||||
finished: Promise<T>;
|
finished: Promise<Parameters<ComponentProps<C>["onFinished"]>>;
|
||||||
close(...args: T): void;
|
close(...args: Parameters<ComponentProps<C>["onFinished"]>): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProps<T extends any[]> {
|
interface IOptions<C extends ComponentType> {
|
||||||
onFinished?(...args: T): void;
|
onBeforeClose?: IModal<C>["onBeforeClose"];
|
||||||
// TODO improve typing here once all Modals are TS and we can exhaustively check the props
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IOptions<T extends any[]> {
|
|
||||||
onBeforeClose?: IModal<T>["onBeforeClose"];
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParametersWithoutFirst<T extends (...args: any) => any> = T extends (a: any, ...args: infer P) => any ? P : never;
|
|
||||||
|
|
||||||
export enum ModalManagerEvent {
|
export enum ModalManagerEvent {
|
||||||
Opened = "opened",
|
Opened = "opened",
|
||||||
}
|
}
|
||||||
|
@ -111,18 +112,30 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||||
return !!this.priorityModal || !!this.staticModal || this.modals.length > 0;
|
return !!this.priorityModal || !!this.staticModal || this.modals.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public createDialog<T extends any[]>(
|
public createDialog<C extends ComponentType>(
|
||||||
Element: React.ComponentType<any>,
|
Element: C,
|
||||||
...rest: ParametersWithoutFirst<ModalManager["createDialogAsync"]>
|
props?: ComponentProps<C>,
|
||||||
): IHandle<T> {
|
className?: string,
|
||||||
return this.createDialogAsync<T>(Promise.resolve(Element), ...rest);
|
isPriorityModal = false,
|
||||||
|
isStaticModal = false,
|
||||||
|
options: IOptions<C> = {},
|
||||||
|
): IHandle<C> {
|
||||||
|
return this.createDialogAsync<C>(
|
||||||
|
Promise.resolve(Element),
|
||||||
|
props,
|
||||||
|
className,
|
||||||
|
isPriorityModal,
|
||||||
|
isStaticModal,
|
||||||
|
options,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public appendDialog<T extends any[]>(
|
public appendDialog<C extends ComponentType>(
|
||||||
Element: React.ComponentType,
|
Element: React.ComponentType,
|
||||||
...rest: ParametersWithoutFirst<ModalManager["appendDialogAsync"]>
|
props?: ComponentProps<C>,
|
||||||
): IHandle<T> {
|
className?: string,
|
||||||
return this.appendDialogAsync<T>(Promise.resolve(Element), ...rest);
|
): IHandle<C> {
|
||||||
|
return this.appendDialogAsync<C>(Promise.resolve(Element), props, className);
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeCurrentModal(reason: string): void {
|
public closeCurrentModal(reason: string): void {
|
||||||
|
@ -134,15 +147,15 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||||
modal.close();
|
modal.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildModal<T extends any[]>(
|
private buildModal<C extends ComponentType>(
|
||||||
prom: Promise<React.ComponentType>,
|
prom: Promise<React.ComponentType>,
|
||||||
props?: IProps<T>,
|
props?: ComponentProps<C>,
|
||||||
className?: string,
|
className?: string,
|
||||||
options?: IOptions<T>,
|
options?: IOptions<C>,
|
||||||
): {
|
): {
|
||||||
modal: IModal<T>;
|
modal: IModal<C>;
|
||||||
closeDialog: IHandle<T>["close"];
|
closeDialog: IHandle<C>["close"];
|
||||||
onFinishedProm: IHandle<T>["finished"];
|
onFinishedProm: IHandle<C>["finished"];
|
||||||
} {
|
} {
|
||||||
const modal = {
|
const modal = {
|
||||||
onFinished: props?.onFinished,
|
onFinished: props?.onFinished,
|
||||||
|
@ -151,10 +164,10 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||||
|
|
||||||
// these will be set below but we need an object reference to pass to getCloseFn before we can do that
|
// these will be set below but we need an object reference to pass to getCloseFn before we can do that
|
||||||
elem: null,
|
elem: null,
|
||||||
} as IModal<T>;
|
} as IModal<C>;
|
||||||
|
|
||||||
// never call this from onFinished() otherwise it will loop
|
// never call this from onFinished() otherwise it will loop
|
||||||
const [closeDialog, onFinishedProm] = this.getCloseFn<T>(modal, props);
|
const [closeDialog, onFinishedProm] = this.getCloseFn<C>(modal, props);
|
||||||
|
|
||||||
// don't attempt to reuse the same AsyncWrapper for different dialogs,
|
// don't attempt to reuse the same AsyncWrapper for different dialogs,
|
||||||
// otherwise we'll get confused.
|
// otherwise we'll get confused.
|
||||||
|
@ -168,13 +181,13 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||||
return { modal, closeDialog, onFinishedProm };
|
return { modal, closeDialog, onFinishedProm };
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCloseFn<T extends any[]>(
|
private getCloseFn<C extends ComponentType>(
|
||||||
modal: IModal<T>,
|
modal: IModal<C>,
|
||||||
props?: IProps<T>,
|
props?: ComponentProps<C>,
|
||||||
): [IHandle<T>["close"], IHandle<T>["finished"]] {
|
): [IHandle<C>["close"], IHandle<C>["finished"]] {
|
||||||
const deferred = defer<T>();
|
const deferred = defer<Parameters<ComponentProps<C>["onFinished"]>>();
|
||||||
return [
|
return [
|
||||||
async (...args: T): Promise<void> => {
|
async (...args: Parameters<ComponentProps<C>["onFinished"]>): Promise<void> => {
|
||||||
if (modal.beforeClosePromise) {
|
if (modal.beforeClosePromise) {
|
||||||
await modal.beforeClosePromise;
|
await modal.beforeClosePromise;
|
||||||
} else if (modal.onBeforeClose) {
|
} else if (modal.onBeforeClose) {
|
||||||
|
@ -249,16 +262,16 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||||
* @param {onBeforeClose} options.onBeforeClose a callback to decide whether to close the dialog
|
* @param {onBeforeClose} options.onBeforeClose a callback to decide whether to close the dialog
|
||||||
* @returns {object} Object with 'close' parameter being a function that will close the dialog
|
* @returns {object} Object with 'close' parameter being a function that will close the dialog
|
||||||
*/
|
*/
|
||||||
public createDialogAsync<T extends any[]>(
|
public createDialogAsync<C extends ComponentType>(
|
||||||
prom: Promise<React.ComponentType>,
|
prom: Promise<C>,
|
||||||
props?: IProps<T>,
|
props?: ComponentProps<C>,
|
||||||
className?: string,
|
className?: string,
|
||||||
isPriorityModal = false,
|
isPriorityModal = false,
|
||||||
isStaticModal = false,
|
isStaticModal = false,
|
||||||
options: IOptions<T> = {},
|
options: IOptions<C> = {},
|
||||||
): IHandle<T> {
|
): IHandle<C> {
|
||||||
const beforeModal = this.getCurrentModal();
|
const beforeModal = this.getCurrentModal();
|
||||||
const { modal, closeDialog, onFinishedProm } = this.buildModal<T>(prom, props, className, options);
|
const { modal, closeDialog, onFinishedProm } = this.buildModal<C>(prom, props, className, options);
|
||||||
if (isPriorityModal) {
|
if (isPriorityModal) {
|
||||||
// XXX: This is destructive
|
// XXX: This is destructive
|
||||||
this.priorityModal = modal;
|
this.priorityModal = modal;
|
||||||
|
@ -278,13 +291,13 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private appendDialogAsync<T extends any[]>(
|
private appendDialogAsync<C extends ComponentType>(
|
||||||
prom: Promise<React.ComponentType>,
|
prom: Promise<React.ComponentType>,
|
||||||
props?: IProps<T>,
|
props?: ComponentProps<C>,
|
||||||
className?: string,
|
className?: string,
|
||||||
): IHandle<T> {
|
): IHandle<C> {
|
||||||
const beforeModal = this.getCurrentModal();
|
const beforeModal = this.getCurrentModal();
|
||||||
const { modal, closeDialog, onFinishedProm } = this.buildModal<T>(prom, props, className, {});
|
const { modal, closeDialog, onFinishedProm } = this.buildModal<C>(prom, props, className, {});
|
||||||
|
|
||||||
this.modals.push(modal);
|
this.modals.push(modal);
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ComponentProps } from "react";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { User } from "matrix-js-sdk/src/models/user";
|
import { User } from "matrix-js-sdk/src/models/user";
|
||||||
|
@ -29,7 +29,7 @@ import InviteDialog from "./components/views/dialogs/InviteDialog";
|
||||||
import BaseAvatar from "./components/views/avatars/BaseAvatar";
|
import BaseAvatar from "./components/views/avatars/BaseAvatar";
|
||||||
import { mediaFromMxc } from "./customisations/Media";
|
import { mediaFromMxc } from "./customisations/Media";
|
||||||
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
||||||
import { KIND_DM, KIND_INVITE } from "./components/views/dialogs/InviteDialogTypes";
|
import { InviteKind } from "./components/views/dialogs/InviteDialogTypes";
|
||||||
import { Member } from "./utils/direct-messages";
|
import { Member } from "./utils/direct-messages";
|
||||||
|
|
||||||
export interface IInviteResult {
|
export interface IInviteResult {
|
||||||
|
@ -64,7 +64,7 @@ export function showStartChatInviteDialog(initialText = ""): void {
|
||||||
// This dialog handles the room creation internally - we don't need to worry about it.
|
// This dialog handles the room creation internally - we don't need to worry about it.
|
||||||
Modal.createDialog(
|
Modal.createDialog(
|
||||||
InviteDialog,
|
InviteDialog,
|
||||||
{ kind: KIND_DM, initialText },
|
{ kind: InviteKind.Dm, initialText },
|
||||||
/*className=*/ "mx_InviteDialog_flexWrapper",
|
/*className=*/ "mx_InviteDialog_flexWrapper",
|
||||||
/*isPriority=*/ false,
|
/*isPriority=*/ false,
|
||||||
/*isStatic=*/ true,
|
/*isStatic=*/ true,
|
||||||
|
@ -76,10 +76,10 @@ export function showRoomInviteDialog(roomId: string, initialText = ""): void {
|
||||||
Modal.createDialog(
|
Modal.createDialog(
|
||||||
InviteDialog,
|
InviteDialog,
|
||||||
{
|
{
|
||||||
kind: KIND_INVITE,
|
kind: InviteKind.Invite,
|
||||||
initialText,
|
initialText,
|
||||||
roomId,
|
roomId,
|
||||||
},
|
} as Omit<ComponentProps<typeof InviteDialog>, "onFinished">,
|
||||||
/*className=*/ "mx_InviteDialog_flexWrapper",
|
/*className=*/ "mx_InviteDialog_flexWrapper",
|
||||||
/*isPriority=*/ false,
|
/*isPriority=*/ false,
|
||||||
/*isStatic=*/ true,
|
/*isStatic=*/ true,
|
||||||
|
|
|
@ -22,13 +22,13 @@ import { decodeRecoveryKey } from "matrix-js-sdk/src/crypto/recoverykey";
|
||||||
import { encodeBase64 } from "matrix-js-sdk/src/crypto/olmlib";
|
import { encodeBase64 } from "matrix-js-sdk/src/crypto/olmlib";
|
||||||
import { DeviceTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
import { DeviceTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { ComponentType } from "react";
|
|
||||||
|
|
||||||
|
import type CreateSecretStorageDialog from "./async-components/views/dialogs/security/CreateSecretStorageDialog";
|
||||||
import Modal from "./Modal";
|
import Modal from "./Modal";
|
||||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||||
import { _t } from "./languageHandler";
|
import { _t } from "./languageHandler";
|
||||||
import { isSecureBackupRequired } from "./utils/WellKnownUtils";
|
import { isSecureBackupRequired } from "./utils/WellKnownUtils";
|
||||||
import AccessSecretStorageDialog from "./components/views/dialogs/security/AccessSecretStorageDialog";
|
import AccessSecretStorageDialog, { KeyParams } from "./components/views/dialogs/security/AccessSecretStorageDialog";
|
||||||
import RestoreKeyBackupDialog from "./components/views/dialogs/security/RestoreKeyBackupDialog";
|
import RestoreKeyBackupDialog from "./components/views/dialogs/security/RestoreKeyBackupDialog";
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import SecurityCustomisations from "./customisations/Security";
|
import SecurityCustomisations from "./customisations/Security";
|
||||||
|
@ -83,8 +83,6 @@ async function confirmToDismiss(): Promise<boolean> {
|
||||||
return !sure;
|
return !sure;
|
||||||
}
|
}
|
||||||
|
|
||||||
type KeyParams = { passphrase: string; recoveryKey: string };
|
|
||||||
|
|
||||||
function makeInputToKey(keyInfo: ISecretStorageKeyInfo): (keyParams: KeyParams) => Promise<Uint8Array> {
|
function makeInputToKey(keyInfo: ISecretStorageKeyInfo): (keyParams: KeyParams) => Promise<Uint8Array> {
|
||||||
return async ({ passphrase, recoveryKey }): Promise<Uint8Array> => {
|
return async ({ passphrase, recoveryKey }): Promise<Uint8Array> => {
|
||||||
if (passphrase) {
|
if (passphrase) {
|
||||||
|
@ -333,7 +331,7 @@ export async function accessSecretStorage(func = async (): Promise<void> => {},
|
||||||
// passphrase creation.
|
// passphrase creation.
|
||||||
const { finished } = Modal.createDialogAsync(
|
const { finished } = Modal.createDialogAsync(
|
||||||
import("./async-components/views/dialogs/security/CreateSecretStorageDialog") as unknown as Promise<
|
import("./async-components/views/dialogs/security/CreateSecretStorageDialog") as unknown as Promise<
|
||||||
ComponentType<{}>
|
typeof CreateSecretStorageDialog
|
||||||
>,
|
>,
|
||||||
{
|
{
|
||||||
forceReset,
|
forceReset,
|
||||||
|
|
|
@ -551,7 +551,7 @@ export const Commands = [
|
||||||
) {
|
) {
|
||||||
const defaultIdentityServerUrl = getDefaultIdentityServerUrl();
|
const defaultIdentityServerUrl = getDefaultIdentityServerUrl();
|
||||||
if (defaultIdentityServerUrl) {
|
if (defaultIdentityServerUrl) {
|
||||||
const { finished } = Modal.createDialog<[boolean]>(QuestionDialog, {
|
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||||
title: _t("Use an identity server"),
|
title: _t("Use an identity server"),
|
||||||
description: (
|
description: (
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -191,7 +191,7 @@ export async function dialogTermsInteractionCallback(
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
logger.log("Terms that need agreement", policiesAndServicePairs);
|
logger.log("Terms that need agreement", policiesAndServicePairs);
|
||||||
|
|
||||||
const { finished } = Modal.createDialog<[boolean, string[]]>(
|
const { finished } = Modal.createDialog(
|
||||||
TermsDialog,
|
TermsDialog,
|
||||||
{
|
{
|
||||||
policiesAndServicePairs,
|
policiesAndServicePairs,
|
||||||
|
|
|
@ -154,7 +154,7 @@ export enum KeyBindingAction {
|
||||||
ToggleHiddenEventVisibility = "KeyBinding.toggleHiddenEventVisibility",
|
ToggleHiddenEventVisibility = "KeyBinding.toggleHiddenEventVisibility",
|
||||||
}
|
}
|
||||||
|
|
||||||
type KeyboardShortcutSetting = IBaseSetting<KeyCombo>;
|
type KeyboardShortcutSetting = Omit<IBaseSetting<KeyCombo>, "supportedLevels">;
|
||||||
|
|
||||||
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
|
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
|
||||||
export type IKeyboardShortcuts = Partial<Record<KeyBindingAction, KeyboardShortcutSetting>>;
|
export type IKeyboardShortcuts = Partial<Record<KeyBindingAction, KeyboardShortcutSetting>>;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { Action } from "../../../../dispatcher/actions";
|
||||||
import { SettingLevel } from "../../../../settings/SettingLevel";
|
import { SettingLevel } from "../../../../settings/SettingLevel";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onFinished: (success: boolean) => void;
|
onFinished: (success?: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
|
|
@ -27,17 +27,18 @@ import { SettingLevel } from "../../../../settings/SettingLevel";
|
||||||
import Field from "../../../../components/views/elements/Field";
|
import Field from "../../../../components/views/elements/Field";
|
||||||
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
||||||
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
|
||||||
import { IIndexStats } from "../../../../indexing/BaseEventIndexManager";
|
import { IIndexStats } from "../../../../indexing/BaseEventIndexManager";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {}
|
interface IProps {
|
||||||
|
onFinished(): void;
|
||||||
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
eventIndexSize: number;
|
eventIndexSize: number;
|
||||||
eventCount: number;
|
eventCount: number;
|
||||||
crawlingRoomsCount: number;
|
crawlingRoomsCount: number;
|
||||||
roomCount: number;
|
roomCount: number;
|
||||||
currentRoom: string;
|
currentRoom: string | null;
|
||||||
crawlerSleepTime: number;
|
crawlerSleepTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +61,8 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
|
||||||
|
|
||||||
public updateCurrentRoom = async (room: Room): Promise<void> => {
|
public updateCurrentRoom = async (room: Room): Promise<void> => {
|
||||||
const eventIndex = EventIndexPeg.get();
|
const eventIndex = EventIndexPeg.get();
|
||||||
let stats: IIndexStats;
|
if (!eventIndex) return;
|
||||||
|
let stats: IIndexStats | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
stats = await eventIndex.getStats();
|
stats = await eventIndex.getStats();
|
||||||
|
@ -70,7 +72,7 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentRoom = null;
|
let currentRoom: string | null = null;
|
||||||
|
|
||||||
if (room) currentRoom = room.name;
|
if (room) currentRoom = room.name;
|
||||||
const roomStats = eventIndex.crawlingRooms();
|
const roomStats = eventIndex.crawlingRooms();
|
||||||
|
@ -78,8 +80,8 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
|
||||||
const roomCount = roomStats.totalRooms.size;
|
const roomCount = roomStats.totalRooms.size;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
eventIndexSize: stats.size,
|
eventIndexSize: stats?.size ?? 0,
|
||||||
eventCount: stats.eventCount,
|
eventCount: stats?.eventCount ?? 0,
|
||||||
crawlingRoomsCount: crawlingRoomsCount,
|
crawlingRoomsCount: crawlingRoomsCount,
|
||||||
roomCount: roomCount,
|
roomCount: roomCount,
|
||||||
currentRoom: currentRoom,
|
currentRoom: currentRoom,
|
||||||
|
@ -99,7 +101,7 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
|
||||||
let crawlingRoomsCount = 0;
|
let crawlingRoomsCount = 0;
|
||||||
let roomCount = 0;
|
let roomCount = 0;
|
||||||
let eventCount = 0;
|
let eventCount = 0;
|
||||||
let currentRoom = null;
|
let currentRoom: string | null = null;
|
||||||
|
|
||||||
const eventIndex = EventIndexPeg.get();
|
const eventIndex = EventIndexPeg.get();
|
||||||
|
|
||||||
|
@ -108,8 +110,10 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stats = await eventIndex.getStats();
|
const stats = await eventIndex.getStats();
|
||||||
eventIndexSize = stats.size;
|
if (stats) {
|
||||||
eventCount = stats.eventCount;
|
eventIndexSize = stats.size;
|
||||||
|
eventCount = stats.eventCount;
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// This call may fail if sporadically, not a huge issue as we
|
// This call may fail if sporadically, not a huge issue as we
|
||||||
// will try later again in the updateCurrentRoom call and
|
// will try later again in the updateCurrentRoom call and
|
||||||
|
@ -135,7 +139,7 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
|
||||||
|
|
||||||
private onDisable = async (): Promise<void> => {
|
private onDisable = async (): Promise<void> => {
|
||||||
const DisableEventIndexDialog = (await import("./DisableEventIndexDialog")).default;
|
const DisableEventIndexDialog = (await import("./DisableEventIndexDialog")).default;
|
||||||
Modal.createDialog(DisableEventIndexDialog, null, null, /* priority = */ false, /* static = */ true);
|
Modal.createDialog(DisableEventIndexDialog, undefined, undefined, /* priority = */ false, /* static = */ true);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCrawlerSleepTimeChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
private onCrawlerSleepTimeChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
@ -157,11 +161,9 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
|
||||||
|
|
||||||
const eventIndexingSettings = (
|
const eventIndexingSettings = (
|
||||||
<div>
|
<div>
|
||||||
{_t(
|
{_t("%(brand)s is securely caching encrypted messages locally for them to appear in search results:", {
|
||||||
"%(brand)s is securely caching encrypted messages locally for them " +
|
brand,
|
||||||
"to appear in search results:",
|
})}
|
||||||
{ brand },
|
|
||||||
)}
|
|
||||||
<div className="mx_SettingsTab_subsectionText">
|
<div className="mx_SettingsTab_subsectionText">
|
||||||
{crawlerState}
|
{crawlerState}
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -26,7 +26,6 @@ import { accessSecretStorage } from "../../../../SecurityManager";
|
||||||
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
|
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
|
||||||
import { copyNode } from "../../../../utils/strings";
|
import { copyNode } from "../../../../utils/strings";
|
||||||
import PassphraseField from "../../../../components/views/auth/PassphraseField";
|
import PassphraseField from "../../../../components/views/auth/PassphraseField";
|
||||||
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
|
||||||
import Field from "../../../../components/views/elements/Field";
|
import Field from "../../../../components/views/elements/Field";
|
||||||
import Spinner from "../../../../components/views/elements/Spinner";
|
import Spinner from "../../../../components/views/elements/Spinner";
|
||||||
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
|
@ -45,10 +44,12 @@ enum Phase {
|
||||||
|
|
||||||
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
||||||
|
|
||||||
interface IProps extends IDialogProps {}
|
interface IProps {
|
||||||
|
onFinished(done?: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
secureSecretStorage: boolean;
|
secureSecretStorage: boolean | null;
|
||||||
phase: Phase;
|
phase: Phase;
|
||||||
passPhrase: string;
|
passPhrase: string;
|
||||||
passPhraseValid: boolean;
|
passPhraseValid: boolean;
|
||||||
|
@ -120,7 +121,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent<IProps, I
|
||||||
const { secureSecretStorage } = this.state;
|
const { secureSecretStorage } = this.state;
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: Phase.BackingUp,
|
phase: Phase.BackingUp,
|
||||||
error: null,
|
error: undefined,
|
||||||
});
|
});
|
||||||
let info;
|
let info;
|
||||||
try {
|
try {
|
||||||
|
@ -218,7 +219,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent<IProps, I
|
||||||
|
|
||||||
private onPassPhraseValidate = (result: IValidationResult): void => {
|
private onPassPhraseValidate = (result: IValidationResult): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhraseValid: result.valid,
|
passPhraseValid: !!result.valid,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -305,7 +306,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent<IProps, I
|
||||||
changeText = _t("Go back to set it again.");
|
changeText = _t("Go back to set it again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let passPhraseMatch = null;
|
let passPhraseMatch: JSX.Element | undefined;
|
||||||
if (matchText) {
|
if (matchText) {
|
||||||
passPhraseMatch = (
|
passPhraseMatch = (
|
||||||
<div className="mx_CreateKeyBackupDialog_passPhraseMatch">
|
<div className="mx_CreateKeyBackupDialog_passPhraseMatch">
|
||||||
|
|
|
@ -43,7 +43,6 @@ import {
|
||||||
SecureBackupSetupMethod,
|
SecureBackupSetupMethod,
|
||||||
} from "../../../../utils/WellKnownUtils";
|
} from "../../../../utils/WellKnownUtils";
|
||||||
import SecurityCustomisations from "../../../../customisations/Security";
|
import SecurityCustomisations from "../../../../customisations/Security";
|
||||||
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
|
||||||
import Field from "../../../../components/views/elements/Field";
|
import Field from "../../../../components/views/elements/Field";
|
||||||
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
import Spinner from "../../../../components/views/elements/Spinner";
|
import Spinner from "../../../../components/views/elements/Spinner";
|
||||||
|
@ -67,10 +66,11 @@ enum Phase {
|
||||||
|
|
||||||
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
hasCancel: boolean;
|
hasCancel?: boolean;
|
||||||
accountPassword: string;
|
accountPassword?: string;
|
||||||
forceReset: boolean;
|
forceReset?: boolean;
|
||||||
|
onFinished(ok?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -81,13 +81,13 @@ interface IState {
|
||||||
copied: boolean;
|
copied: boolean;
|
||||||
downloaded: boolean;
|
downloaded: boolean;
|
||||||
setPassphrase: boolean;
|
setPassphrase: boolean;
|
||||||
backupInfo: IKeyBackupInfo;
|
backupInfo: IKeyBackupInfo | null;
|
||||||
backupSigStatus: TrustInfo;
|
backupSigStatus: TrustInfo | null;
|
||||||
// does the server offer a UI auth flow with just m.login.password
|
// does the server offer a UI auth flow with just m.login.password
|
||||||
// for /keys/device_signing/upload?
|
// for /keys/device_signing/upload?
|
||||||
canUploadKeysWithPasswordOnly: boolean;
|
canUploadKeysWithPasswordOnly: boolean | null;
|
||||||
accountPassword: string;
|
accountPassword: string;
|
||||||
accountPasswordCorrect: boolean;
|
accountPasswordCorrect: boolean | null;
|
||||||
canSkip: boolean;
|
canSkip: boolean;
|
||||||
passPhraseKeySelected: string;
|
passPhraseKeySelected: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
@ -119,7 +119,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountPassword = props.accountPassword || "";
|
const accountPassword = props.accountPassword || "";
|
||||||
let canUploadKeysWithPasswordOnly = null;
|
let canUploadKeysWithPasswordOnly: boolean | null = null;
|
||||||
if (accountPassword) {
|
if (accountPassword) {
|
||||||
// If we have an account password in memory, let's simplify and
|
// If we have an account password in memory, let's simplify and
|
||||||
// assume it means password auth is also supported for device
|
// assume it means password auth is also supported for device
|
||||||
|
@ -172,12 +172,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
this.fetchBackupInfo();
|
this.fetchBackupInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchBackupInfo(): Promise<{ backupInfo: IKeyBackupInfo; backupSigStatus: TrustInfo }> {
|
private async fetchBackupInfo(): Promise<{ backupInfo?: IKeyBackupInfo; backupSigStatus?: TrustInfo }> {
|
||||||
try {
|
try {
|
||||||
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
const backupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||||
const backupSigStatus =
|
const backupSigStatus =
|
||||||
// we may not have started crypto yet, in which case we definitely don't trust the backup
|
// we may not have started crypto yet, in which case we definitely don't trust the backup
|
||||||
MatrixClientPeg.get().isCryptoEnabled() && (await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo));
|
backupInfo && MatrixClientPeg.get().isCryptoEnabled()
|
||||||
|
? await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo)
|
||||||
|
: null;
|
||||||
|
|
||||||
const { forceReset } = this.props;
|
const { forceReset } = this.props;
|
||||||
const phase = backupInfo && !forceReset ? Phase.Migrate : Phase.ChooseKeyPassphrase;
|
const phase = backupInfo && !forceReset ? Phase.Migrate : Phase.ChooseKeyPassphrase;
|
||||||
|
@ -189,17 +191,18 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
backupInfo,
|
backupInfo: backupInfo ?? undefined,
|
||||||
backupSigStatus,
|
backupSigStatus: backupSigStatus ?? undefined,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setState({ phase: Phase.LoadError });
|
this.setState({ phase: Phase.LoadError });
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async queryKeyUploadAuth(): Promise<void> {
|
private async queryKeyUploadAuth(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {} as CrossSigningKeys);
|
await MatrixClientPeg.get().uploadDeviceSigningKeys(undefined, {} as CrossSigningKeys);
|
||||||
// We should never get here: the server should always require
|
// We should never get here: the server should always require
|
||||||
// UI auth to upload device signing keys. If we do, we upload
|
// UI auth to upload device signing keys. If we do, we upload
|
||||||
// no keys which would be a no-op.
|
// no keys which would be a no-op.
|
||||||
|
@ -248,7 +251,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
|
|
||||||
private onMigrateFormSubmit = (e: React.FormEvent): void => {
|
private onMigrateFormSubmit = (e: React.FormEvent): void => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (this.state.backupSigStatus.usable) {
|
if (this.state.backupSigStatus?.usable) {
|
||||||
this.bootstrapSecretStorage();
|
this.bootstrapSecretStorage();
|
||||||
} else {
|
} else {
|
||||||
this.restoreBackup();
|
this.restoreBackup();
|
||||||
|
@ -265,7 +268,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
};
|
};
|
||||||
|
|
||||||
private onDownloadClick = (): void => {
|
private onDownloadClick = (): void => {
|
||||||
const blob = new Blob([this.recoveryKey.encodedPrivateKey], {
|
const blob = new Blob([this.recoveryKey.encodedPrivateKey!], {
|
||||||
type: "text/plain;charset=us-ascii",
|
type: "text/plain;charset=us-ascii",
|
||||||
});
|
});
|
||||||
FileSaver.saveAs(blob, "security-key.txt");
|
FileSaver.saveAs(blob, "security-key.txt");
|
||||||
|
@ -323,7 +326,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
private bootstrapSecretStorage = async (): Promise<void> => {
|
private bootstrapSecretStorage = async (): Promise<void> => {
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: Phase.Storing,
|
phase: Phase.Storing,
|
||||||
error: null,
|
error: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
@ -351,7 +354,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
});
|
});
|
||||||
await cli.bootstrapSecretStorage({
|
await cli.bootstrapSecretStorage({
|
||||||
createSecretStorageKey: async () => this.recoveryKey,
|
createSecretStorageKey: async () => this.recoveryKey,
|
||||||
keyBackupInfo: this.state.backupInfo,
|
keyBackupInfo: this.state.backupInfo!,
|
||||||
setupNewKeyBackup: !this.state.backupInfo,
|
setupNewKeyBackup: !this.state.backupInfo,
|
||||||
getKeyBackupPassphrase: async (): Promise<Uint8Array> => {
|
getKeyBackupPassphrase: async (): Promise<Uint8Array> => {
|
||||||
// We may already have the backup key if we earlier went
|
// We may already have the backup key if we earlier went
|
||||||
|
@ -399,14 +402,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
showSummary: false,
|
showSummary: false,
|
||||||
keyCallback,
|
keyCallback,
|
||||||
},
|
},
|
||||||
null,
|
undefined,
|
||||||
/* priority = */ false,
|
/* priority = */ false,
|
||||||
/* static = */ false,
|
/* static = */ false,
|
||||||
);
|
);
|
||||||
|
|
||||||
await finished;
|
await finished;
|
||||||
const { backupSigStatus } = await this.fetchBackupInfo();
|
const { backupSigStatus } = await this.fetchBackupInfo();
|
||||||
if (backupSigStatus.usable && this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
|
if (backupSigStatus?.usable && this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
|
||||||
this.bootstrapSecretStorage();
|
this.bootstrapSecretStorage();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -467,7 +470,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
|
|
||||||
private onPassPhraseValidate = (result: IValidationResult): void => {
|
private onPassPhraseValidate = (result: IValidationResult): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhraseValid: result.valid,
|
passPhraseValid: !!result.valid,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -581,13 +584,13 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
label={_t("Password")}
|
label={_t("Password")}
|
||||||
value={this.state.accountPassword}
|
value={this.state.accountPassword}
|
||||||
onChange={this.onAccountPasswordChange}
|
onChange={this.onAccountPasswordChange}
|
||||||
forceValidity={this.state.accountPasswordCorrect === false ? false : null}
|
forceValidity={this.state.accountPasswordCorrect === false ? false : undefined}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (!this.state.backupSigStatus.usable) {
|
} else if (!this.state.backupSigStatus?.usable) {
|
||||||
authPrompt = (
|
authPrompt = (
|
||||||
<div>
|
<div>
|
||||||
<div>{_t("Restore your key backup to upgrade your encryption")}</div>
|
<div>{_t("Restore your key backup to upgrade your encryption")}</div>
|
||||||
|
@ -612,7 +615,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
primaryButton={nextCaption}
|
primaryButton={nextCaption}
|
||||||
onPrimaryButtonClick={this.onMigrateFormSubmit}
|
onPrimaryButtonClick={this.onMigrateFormSubmit}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
primaryDisabled={this.state.canUploadKeysWithPasswordOnly && !this.state.accountPassword}
|
primaryDisabled={!!this.state.canUploadKeysWithPasswordOnly && !this.state.accountPassword}
|
||||||
>
|
>
|
||||||
<button type="button" className="danger" onClick={this.onCancelClick}>
|
<button type="button" className="danger" onClick={this.onCancelClick}>
|
||||||
{_t("Skip")}
|
{_t("Skip")}
|
||||||
|
@ -680,7 +683,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
changeText = _t("Go back to set it again.");
|
changeText = _t("Go back to set it again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let passPhraseMatch = null;
|
let passPhraseMatch: JSX.Element | undefined;
|
||||||
if (matchText) {
|
if (matchText) {
|
||||||
passPhraseMatch = (
|
passPhraseMatch = (
|
||||||
<div>
|
<div>
|
||||||
|
@ -721,7 +724,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderPhaseShowKey(): JSX.Element {
|
private renderPhaseShowKey(): JSX.Element {
|
||||||
let continueButton;
|
let continueButton: JSX.Element;
|
||||||
if (this.state.phase === Phase.ShowKey) {
|
if (this.state.phase === Phase.ShowKey) {
|
||||||
continueButton = (
|
continueButton = (
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
|
@ -928,7 +931,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleClass = null;
|
let titleClass: string | string[] | undefined;
|
||||||
switch (this.state.phase) {
|
switch (this.state.phase) {
|
||||||
case Phase.Passphrase:
|
case Phase.Passphrase:
|
||||||
case Phase.PassphraseConfirm:
|
case Phase.PassphraseConfirm:
|
||||||
|
|
|
@ -22,7 +22,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import * as MegolmExportEncryption from "../../../../utils/MegolmExportEncryption";
|
import * as MegolmExportEncryption from "../../../../utils/MegolmExportEncryption";
|
||||||
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
|
||||||
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
import Field from "../../../../components/views/elements/Field";
|
import Field from "../../../../components/views/elements/Field";
|
||||||
import { KeysStartingWith } from "../../../../@types/common";
|
import { KeysStartingWith } from "../../../../@types/common";
|
||||||
|
@ -32,13 +31,14 @@ enum Phase {
|
||||||
Exporting = "exporting",
|
Exporting = "exporting",
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
|
onFinished(doExport?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
phase: Phase;
|
phase: Phase;
|
||||||
errStr: string;
|
errStr: string | null;
|
||||||
passphrase1: string;
|
passphrase1: string;
|
||||||
passphrase2: string;
|
passphrase2: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import * as MegolmExportEncryption from "../../../../utils/MegolmExportEncryption";
|
import * as MegolmExportEncryption from "../../../../utils/MegolmExportEncryption";
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
|
||||||
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
import Field from "../../../../components/views/elements/Field";
|
import Field from "../../../../components/views/elements/Field";
|
||||||
|
|
||||||
|
@ -29,7 +28,11 @@ function readFileAsArrayBuffer(file: File): Promise<ArrayBuffer> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
resolve(e.target.result as ArrayBuffer);
|
if (e.target?.result) {
|
||||||
|
resolve(e.target.result as ArrayBuffer);
|
||||||
|
} else {
|
||||||
|
reject(new Error("Failed to read file due to unknown error"));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
reader.onerror = reject;
|
reader.onerror = reject;
|
||||||
|
|
||||||
|
@ -42,14 +45,15 @@ enum Phase {
|
||||||
Importing = "importing",
|
Importing = "importing",
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
|
onFinished(imported?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
enableSubmit: boolean;
|
enableSubmit: boolean;
|
||||||
phase: Phase;
|
phase: Phase;
|
||||||
errStr: string;
|
errStr: string | null;
|
||||||
passphrase: string;
|
passphrase: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +77,7 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFormChange = (): void => {
|
private onFormChange = (): void => {
|
||||||
const files = this.file.current.files;
|
const files = this.file.current?.files;
|
||||||
this.setState({
|
this.setState({
|
||||||
enableSubmit: this.state.passphrase !== "" && !!files?.length,
|
enableSubmit: this.state.passphrase !== "" && !!files?.length,
|
||||||
});
|
});
|
||||||
|
@ -87,7 +91,10 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
private onFormSubmit = (ev: React.FormEvent): boolean => {
|
private onFormSubmit = (ev: React.FormEvent): boolean => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
// noinspection JSIgnoredPromiseFromCall
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
this.startImport(this.file.current.files[0], this.state.passphrase);
|
const file = this.file.current?.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
this.startImport(file, this.state.passphrase);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -24,12 +24,12 @@ import { _t } from "../../../../languageHandler";
|
||||||
import Modal from "../../../../Modal";
|
import Modal from "../../../../Modal";
|
||||||
import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog";
|
import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog";
|
||||||
import { Action } from "../../../../dispatcher/actions";
|
import { Action } from "../../../../dispatcher/actions";
|
||||||
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
|
||||||
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
||||||
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
newVersionInfo: IKeyBackupInfo;
|
newVersionInfo: IKeyBackupInfo;
|
||||||
|
onFinished(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class NewRecoveryMethodDialog extends React.PureComponent<IProps> {
|
export default class NewRecoveryMethodDialog extends React.PureComponent<IProps> {
|
||||||
|
|
|
@ -21,11 +21,12 @@ import dis from "../../../../dispatcher/dispatcher";
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import Modal from "../../../../Modal";
|
import Modal from "../../../../Modal";
|
||||||
import { Action } from "../../../../dispatcher/actions";
|
import { Action } from "../../../../dispatcher/actions";
|
||||||
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
|
||||||
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {}
|
interface IProps {
|
||||||
|
onFinished(): void;
|
||||||
|
}
|
||||||
|
|
||||||
export default class RecoveryMethodRemovedDialog extends React.PureComponent<IProps> {
|
export default class RecoveryMethodRemovedDialog extends React.PureComponent<IProps> {
|
||||||
private onGoToSettingsClick = (): void => {
|
private onGoToSettingsClick = (): void => {
|
||||||
|
@ -38,7 +39,7 @@ export default class RecoveryMethodRemovedDialog extends React.PureComponent<IPr
|
||||||
Modal.createDialogAsync(
|
Modal.createDialogAsync(
|
||||||
import("./CreateKeyBackupDialog") as unknown as Promise<ComponentType<{}>>,
|
import("./CreateKeyBackupDialog") as unknown as Promise<ComponentType<{}>>,
|
||||||
undefined,
|
undefined,
|
||||||
null,
|
undefined,
|
||||||
/* priority = */ false,
|
/* priority = */ false,
|
||||||
/* static = */ true,
|
/* static = */ true,
|
||||||
);
|
);
|
||||||
|
|
|
@ -19,7 +19,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { uniq, sortBy, ListIteratee } from "lodash";
|
import { uniq, sortBy, uniqBy, ListIteratee } from "lodash";
|
||||||
import EMOTICON_REGEX from "emojibase-regex/emoticon";
|
import EMOTICON_REGEX from "emojibase-regex/emoticon";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
// Do second match with shouldMatchWordsOnly in order to match against 'name'
|
// Do second match with shouldMatchWordsOnly in order to match against 'name'
|
||||||
completions = completions.concat(this.nameMatcher.match(matchedString));
|
completions = completions.concat(this.nameMatcher.match(matchedString));
|
||||||
|
|
||||||
let sorters: ListIteratee<ISortedEmoji>[] = [];
|
const sorters: ListIteratee<ISortedEmoji>[] = [];
|
||||||
// make sure that emoticons come first
|
// make sure that emoticons come first
|
||||||
sorters.push((c) => score(matchedString, c.emoji.emoticon || ""));
|
sorters.push((c) => score(matchedString, c.emoji.emoticon || ""));
|
||||||
|
|
||||||
|
@ -140,11 +140,27 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
completions = completions.slice(0, LIMIT);
|
completions = completions.slice(0, LIMIT);
|
||||||
|
|
||||||
// Do a second sort to place emoji matching with frequently used one on top
|
// Do a second sort to place emoji matching with frequently used one on top
|
||||||
sorters = [];
|
const recentlyUsedAutocomplete: ISortedEmoji[] = [];
|
||||||
this.recentlyUsed.forEach((emoji) => {
|
this.recentlyUsed.forEach((emoji) => {
|
||||||
sorters.push((c) => score(emoji.shortcodes[0], c.emoji.shortcodes[0]));
|
if (emoji.shortcodes[0].indexOf(trimmedMatch) === 0) {
|
||||||
|
recentlyUsedAutocomplete.push({ emoji: emoji, _orderBy: 0 });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
completions = sortBy<ISortedEmoji>(uniq(completions), sorters);
|
|
||||||
|
//if there is an exact shortcode match in the frequently used emojis, it goes before everything
|
||||||
|
for (let i = 0; i < recentlyUsedAutocomplete.length; i++) {
|
||||||
|
if (recentlyUsedAutocomplete[i].emoji.shortcodes[0] === trimmedMatch) {
|
||||||
|
const exactMatchEmoji = recentlyUsedAutocomplete[i];
|
||||||
|
for (let j = i; j > 0; j--) {
|
||||||
|
recentlyUsedAutocomplete[j] = recentlyUsedAutocomplete[j - 1];
|
||||||
|
}
|
||||||
|
recentlyUsedAutocomplete[0] = exactMatchEmoji;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completions = recentlyUsedAutocomplete.concat(completions);
|
||||||
|
completions = uniqBy(completions, "emoji");
|
||||||
|
|
||||||
return completions.map((c) => ({
|
return completions.map((c) => ({
|
||||||
completion: c.emoji.unicode,
|
completion: c.emoji.unicode,
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../views/context_menus/IconizedContextMenu";
|
|
||||||
import { _t } from "../../languageHandler";
|
|
||||||
import { HostSignupStore } from "../../stores/HostSignupStore";
|
|
||||||
import SdkConfig from "../../SdkConfig";
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
onClick?(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IState {}
|
|
||||||
|
|
||||||
export default class HostSignupAction extends React.PureComponent<IProps, IState> {
|
|
||||||
private openDialog = async (): Promise<void> => {
|
|
||||||
this.props.onClick?.();
|
|
||||||
await HostSignupStore.instance.setHostSignupActive(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
|
||||||
const hostSignupConfig = SdkConfig.getObject("host_signup");
|
|
||||||
if (!hostSignupConfig?.get("brand")) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IconizedContextMenuOptionList>
|
|
||||||
<IconizedContextMenuOption
|
|
||||||
iconClassName="mx_UserMenu_iconHosting"
|
|
||||||
label={_t("Upgrade to %(hostSignupBrand)s", {
|
|
||||||
hostSignupBrand: hostSignupConfig.get("brand"),
|
|
||||||
})}
|
|
||||||
onClick={this.openDialog}
|
|
||||||
/>
|
|
||||||
</IconizedContextMenuOptionList>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -47,7 +47,6 @@ import NonUrgentToastContainer from "./NonUrgentToastContainer";
|
||||||
import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
||||||
import Modal from "../../Modal";
|
import Modal from "../../Modal";
|
||||||
import { ICollapseConfig } from "../../resizer/distributors/collapse";
|
import { ICollapseConfig } from "../../resizer/distributors/collapse";
|
||||||
import HostSignupContainer from "../views/host_signup/HostSignupContainer";
|
|
||||||
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
||||||
import { IOpts } from "../../createRoom";
|
import { IOpts } from "../../createRoom";
|
||||||
import SpacePanel from "../views/spaces/SpacePanel";
|
import SpacePanel from "../views/spaces/SpacePanel";
|
||||||
|
@ -695,7 +694,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
</div>
|
</div>
|
||||||
<PipContainer />
|
<PipContainer />
|
||||||
<NonUrgentToastContainer />
|
<NonUrgentToastContainer />
|
||||||
<HostSignupContainer />
|
|
||||||
{audioFeedArraysForCalls}
|
{audioFeedArraysForCalls}
|
||||||
</MatrixClientContext.Provider>
|
</MatrixClientContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ComponentType, createRef } from "react";
|
import React, { createRef } from "react";
|
||||||
import {
|
import {
|
||||||
ClientEvent,
|
ClientEvent,
|
||||||
createClient,
|
createClient,
|
||||||
|
@ -38,6 +38,8 @@ import "focus-visible";
|
||||||
// what-input helps improve keyboard accessibility
|
// what-input helps improve keyboard accessibility
|
||||||
import "what-input";
|
import "what-input";
|
||||||
|
|
||||||
|
import type NewRecoveryMethodDialog from "../../async-components/views/dialogs/security/NewRecoveryMethodDialog";
|
||||||
|
import type RecoveryMethodRemovedDialog from "../../async-components/views/dialogs/security/RecoveryMethodRemovedDialog";
|
||||||
import PosthogTrackers from "../../PosthogTrackers";
|
import PosthogTrackers from "../../PosthogTrackers";
|
||||||
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
|
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
|
||||||
import { IMatrixClientCreds, MatrixClientPeg } from "../../MatrixClientPeg";
|
import { IMatrixClientCreds, MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
|
@ -140,6 +142,7 @@ import RovingSpotlightDialog, { Filter } from "../views/dialogs/spotlight/Spotli
|
||||||
import { findDMForUser } from "../../utils/dm/findDMForUser";
|
import { findDMForUser } from "../../utils/dm/findDMForUser";
|
||||||
import { Linkify } from "../../HtmlUtils";
|
import { Linkify } from "../../HtmlUtils";
|
||||||
import { NotificationColor } from "../../stores/notifications/NotificationColor";
|
import { NotificationColor } from "../../stores/notifications/NotificationColor";
|
||||||
|
import { UserTab } from "../views/dialogs/UserTab";
|
||||||
|
|
||||||
// legacy export
|
// legacy export
|
||||||
export { default as Views } from "../../Views";
|
export { default as Views } from "../../Views";
|
||||||
|
@ -705,7 +708,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
const tabPayload = payload as OpenToTabPayload;
|
const tabPayload = payload as OpenToTabPayload;
|
||||||
Modal.createDialog(
|
Modal.createDialog(
|
||||||
UserSettingsDialog,
|
UserSettingsDialog,
|
||||||
{ initialTabId: tabPayload.initialTabId },
|
{ initialTabId: tabPayload.initialTabId as UserTab },
|
||||||
/*className=*/ null,
|
/*className=*/ null,
|
||||||
/*isPriority=*/ false,
|
/*isPriority=*/ false,
|
||||||
/*isStatic=*/ true,
|
/*isStatic=*/ true,
|
||||||
|
@ -1629,14 +1632,14 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
Modal.createDialogAsync(
|
Modal.createDialogAsync(
|
||||||
import(
|
import(
|
||||||
"../../async-components/views/dialogs/security/NewRecoveryMethodDialog"
|
"../../async-components/views/dialogs/security/NewRecoveryMethodDialog"
|
||||||
) as unknown as Promise<ComponentType<{}>>,
|
) as unknown as Promise<typeof NewRecoveryMethodDialog>,
|
||||||
{ newVersionInfo },
|
{ newVersionInfo },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Modal.createDialogAsync(
|
Modal.createDialogAsync(
|
||||||
import(
|
import(
|
||||||
"../../async-components/views/dialogs/security/RecoveryMethodRemovedDialog"
|
"../../async-components/views/dialogs/security/RecoveryMethodRemovedDialog"
|
||||||
) as unknown as Promise<ComponentType<{}>>,
|
) as unknown as Promise<typeof RecoveryMethodRemovedDialog>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -262,7 +262,14 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.RoomSummary:
|
case RightPanelPhases.RoomSummary:
|
||||||
card = <RoomSummaryCard room={this.props.room} onClose={this.onClose} />;
|
card = (
|
||||||
|
<RoomSummaryCard
|
||||||
|
room={this.props.room}
|
||||||
|
onClose={this.onClose}
|
||||||
|
// whenever RightPanel is passed a room it is passed a permalinkcreator
|
||||||
|
permalinkCreator={this.props.permalinkCreator!}
|
||||||
|
/>
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.Widget:
|
case RightPanelPhases.Widget:
|
||||||
|
|
|
@ -332,6 +332,7 @@ const Tile: React.FC<ITileProps> = ({
|
||||||
<li
|
<li
|
||||||
className="mx_SpaceHierarchy_roomTileWrapper"
|
className="mx_SpaceHierarchy_roomTileWrapper"
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
|
aria-selected={selected}
|
||||||
aria-expanded={children ? showChildren : undefined}
|
aria-expanded={children ? showChildren : undefined}
|
||||||
>
|
>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
|
|
@ -1473,9 +1473,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
"do not have permission to view the message in question.",
|
"do not have permission to view the message in question.",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
description = _t(
|
description = _t("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.",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
|
|
@ -43,7 +43,6 @@ import IconizedContextMenu, {
|
||||||
IconizedContextMenuOptionList,
|
IconizedContextMenuOptionList,
|
||||||
} from "../views/context_menus/IconizedContextMenu";
|
} from "../views/context_menus/IconizedContextMenu";
|
||||||
import { UIFeature } from "../../settings/UIFeature";
|
import { UIFeature } from "../../settings/UIFeature";
|
||||||
import HostSignupAction from "./HostSignupAction";
|
|
||||||
import SpaceStore from "../../stores/spaces/SpaceStore";
|
import SpaceStore from "../../stores/spaces/SpaceStore";
|
||||||
import { UPDATE_SELECTED_SPACE } from "../../stores/spaces";
|
import { UPDATE_SELECTED_SPACE } from "../../stores/spaces";
|
||||||
import UserIdentifierCustomisations from "../../customisations/UserIdentifier";
|
import UserIdentifierCustomisations from "../../customisations/UserIdentifier";
|
||||||
|
@ -290,7 +289,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
if (!this.state.contextMenuPosition) return null;
|
if (!this.state.contextMenuPosition) return null;
|
||||||
|
|
||||||
let topSection;
|
let topSection;
|
||||||
const hostSignupConfig = SdkConfig.getObject("host_signup");
|
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
topSection = (
|
topSection = (
|
||||||
<div className="mx_UserMenu_contextMenu_header mx_UserMenu_contextMenu_guestPrompts">
|
<div className="mx_UserMenu_contextMenu_header mx_UserMenu_contextMenu_guestPrompts">
|
||||||
|
@ -318,15 +316,6 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (hostSignupConfig?.get("url")) {
|
|
||||||
// If hostSignup.domains is set to a non-empty array, only show
|
|
||||||
// dialog if the user is on the domain or a subdomain.
|
|
||||||
const hostSignupDomains = hostSignupConfig.get("domains") || [];
|
|
||||||
const mxDomain = MatrixClientPeg.get().getDomain();
|
|
||||||
const validDomains = hostSignupDomains.filter((d) => d === mxDomain || mxDomain.endsWith(`.${d}`));
|
|
||||||
if (!hostSignupConfig.get("domains") || validDomains.length > 0) {
|
|
||||||
topSection = <HostSignupAction onClick={this.onCloseMenu} />;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let homeButton = null;
|
let homeButton = null;
|
||||||
|
@ -432,7 +421,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const avatarSize = 32; // should match border-radius of the avatar
|
const avatarSize = 32; // should match border-radius of the avatar
|
||||||
|
|
||||||
const userId = MatrixClientPeg.get().getUserId();
|
const userId = MatrixClientPeg.get().getSafeUserId();
|
||||||
const displayName = OwnProfileStore.instance.displayName || userId;
|
const displayName = OwnProfileStore.instance.displayName || userId;
|
||||||
const avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
const avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
||||||
|
|
||||||
|
|
|
@ -23,15 +23,15 @@ import { _t } from "../../languageHandler";
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
import { canEditContent } from "../../utils/EventUtils";
|
import { canEditContent } from "../../utils/EventUtils";
|
||||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||||
import { IDialogProps } from "../views/dialogs/IDialogProps";
|
|
||||||
import BaseDialog from "../views/dialogs/BaseDialog";
|
import BaseDialog from "../views/dialogs/BaseDialog";
|
||||||
import { DevtoolsContext } from "../views/dialogs/devtools/BaseTool";
|
import { DevtoolsContext } from "../views/dialogs/devtools/BaseTool";
|
||||||
import { StateEventEditor } from "../views/dialogs/devtools/RoomState";
|
import { StateEventEditor } from "../views/dialogs/devtools/RoomState";
|
||||||
import { stringify, TimelineEventEditor } from "../views/dialogs/devtools/Event";
|
import { stringify, TimelineEventEditor } from "../views/dialogs/devtools/Event";
|
||||||
import CopyableText from "../views/elements/CopyableText";
|
import CopyableText from "../views/elements/CopyableText";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
mxEvent: MatrixEvent; // the MatrixEvent associated with the context menu
|
mxEvent: MatrixEvent; // the MatrixEvent associated with the context menu
|
||||||
|
onFinished(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
|
|
@ -368,7 +368,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async renderConfirmLogoutDevicesDialog(): Promise<boolean> {
|
public async renderConfirmLogoutDevicesDialog(): Promise<boolean> {
|
||||||
const { finished } = Modal.createDialog<[boolean]>(QuestionDialog, {
|
const { finished } = Modal.createDialog(QuestionDialog, {
|
||||||
title: _t("Warning!"),
|
title: _t("Warning!"),
|
||||||
description: (
|
description: (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -454,7 +454,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
}
|
}
|
||||||
|
|
||||||
let errorText: ReactNode =
|
let errorText: ReactNode =
|
||||||
_t("There was a problem communicating with the homeserver, " + "please try again later.") +
|
_t("There was a problem communicating with the homeserver, please try again later.") +
|
||||||
(errCode ? " (" + errCode + ")" : "");
|
(errCode ? " (" + errCode + ")" : "");
|
||||||
|
|
||||||
if (err instanceof ConnectionError) {
|
if (err instanceof ConnectionError) {
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactNode } from "react";
|
||||||
|
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import AccessibleButton from "../../../views/elements/AccessibleButton";
|
import AccessibleButton from "../../../views/elements/AccessibleButton";
|
||||||
|
@ -26,7 +26,8 @@ import { ErrorMessage } from "../../ErrorMessage";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
email: string;
|
email: string;
|
||||||
errorText: string | null;
|
errorText: ReactNode | null;
|
||||||
|
onFinished(): void; // This modal is weird in that the way you close it signals intent
|
||||||
onCloseClick: () => void;
|
onCloseClick: () => void;
|
||||||
onReEnterEmailClick: () => void;
|
onReEnterEmailClick: () => void;
|
||||||
onResendClick: () => Promise<boolean>;
|
onResendClick: () => Promise<boolean>;
|
||||||
|
|
|
@ -60,7 +60,7 @@ export default class PlayPauseButton extends React.PureComponent<IProps> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
data-test-id="play-pause-button"
|
data-testid="play-pause-button"
|
||||||
className={classes}
|
className={classes}
|
||||||
title={isPlaying ? _t("Pause") : _t("Play")}
|
title={isPlaying ? _t("Pause") : _t("Play")}
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
|
|
|
@ -91,7 +91,7 @@ export default class CaptchaForm extends React.Component<ICaptchaFormProps, ICap
|
||||||
const publicKey = this.props.sitePublicKey;
|
const publicKey = this.props.sitePublicKey;
|
||||||
if (!publicKey) {
|
if (!publicKey) {
|
||||||
logger.error("No public key for recaptcha!");
|
logger.error("No public key for recaptcha!");
|
||||||
throw new Error("This server has not supplied enough information for Recaptcha " + "authentication");
|
throw new Error("This server has not supplied enough information for Recaptcha authentication");
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("Rendering to %s", divId);
|
logger.info("Rendering to %s", divId);
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { MSC3906Rendezvous, MSC3906RendezvousPayload, RendezvousFailureReason } from "matrix-js-sdk/src/rendezvous";
|
import { MSC3906Rendezvous, MSC3906RendezvousPayload, RendezvousFailureReason } from "matrix-js-sdk/src/rendezvous";
|
||||||
import { MSC3886SimpleHttpRendezvousTransport } from "matrix-js-sdk/src/rendezvous/transports";
|
import { MSC3886SimpleHttpRendezvousTransport } from "matrix-js-sdk/src/rendezvous/transports";
|
||||||
import { MSC3903ECDHPayload, MSC3903ECDHv1RendezvousChannel } from "matrix-js-sdk/src/rendezvous/channels";
|
import { MSC3903ECDHPayload, MSC3903ECDHv2RendezvousChannel } from "matrix-js-sdk/src/rendezvous/channels";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||||
client: this.props.client,
|
client: this.props.client,
|
||||||
});
|
});
|
||||||
|
|
||||||
const channel = new MSC3903ECDHv1RendezvousChannel<MSC3906RendezvousPayload>(
|
const channel = new MSC3903ECDHv2RendezvousChannel<MSC3906RendezvousPayload>(
|
||||||
transport,
|
transport,
|
||||||
undefined,
|
undefined,
|
||||||
this.onFailure,
|
this.onFailure,
|
||||||
|
|
|
@ -32,13 +32,13 @@ import { toPx } from "../../../utils/units";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
name: string; // The name (first initial used as default)
|
name?: string; // The name (first initial used as default)
|
||||||
idName?: string; // ID for generating hash colours
|
idName?: string; // ID for generating hash colours
|
||||||
title?: string; // onHover title text
|
title?: string; // onHover title text
|
||||||
url?: string; // highest priority of them all, shortcut to set in urls[0]
|
url?: string | null; // highest priority of them all, shortcut to set in urls[0]
|
||||||
urls?: string[]; // [highest_priority, ... , lowest_priority]
|
urls?: string[]; // [highest_priority, ... , lowest_priority]
|
||||||
width?: number;
|
width: number;
|
||||||
height?: number;
|
height: number;
|
||||||
// XXX: resizeMethod not actually used.
|
// XXX: resizeMethod not actually used.
|
||||||
resizeMethod?: ResizeMethod;
|
resizeMethod?: ResizeMethod;
|
||||||
defaultToInitialLetter?: boolean; // true to add default url
|
defaultToInitialLetter?: boolean; // true to add default url
|
||||||
|
@ -48,7 +48,7 @@ interface IProps {
|
||||||
tabIndex?: number;
|
tabIndex?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const calculateUrls = (url?: string, urls?: string[], lowBandwidth = false): string[] => {
|
const calculateUrls = (url?: string | null, urls?: string[], lowBandwidth = false): string[] => {
|
||||||
// work out the full set of urls to try to load. This is formed like so:
|
// work out the full set of urls to try to load. This is formed like so:
|
||||||
// imageUrls: [ props.url, ...props.urls ]
|
// imageUrls: [ props.url, ...props.urls ]
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ const calculateUrls = (url?: string, urls?: string[], lowBandwidth = false): str
|
||||||
return Array.from(new Set(_urls));
|
return Array.from(new Set(_urls));
|
||||||
};
|
};
|
||||||
|
|
||||||
const useImageUrl = ({ url, urls }: { url?: string; urls?: string[] }): [string, () => void] => {
|
const useImageUrl = ({ url, urls }: { url?: string | null; urls?: string[] }): [string, () => void] => {
|
||||||
// Since this is a hot code path and the settings store can be slow, we
|
// Since this is a hot code path and the settings store can be slow, we
|
||||||
// use the cached lowBandwidth value from the room context if it exists
|
// use the cached lowBandwidth value from the room context if it exists
|
||||||
const roomContext = useContext(RoomContext);
|
const roomContext = useContext(RoomContext);
|
||||||
|
|
|
@ -62,7 +62,7 @@ enum Icon {
|
||||||
PresenceBusy = "BUSY",
|
PresenceBusy = "BUSY",
|
||||||
}
|
}
|
||||||
|
|
||||||
function tooltipText(variant: Icon): string {
|
function tooltipText(variant: Icon): string | undefined {
|
||||||
switch (variant) {
|
switch (variant) {
|
||||||
case Icon.Globe:
|
case Icon.Globe:
|
||||||
return _t("This room is public");
|
return _t("This room is public");
|
||||||
|
@ -78,7 +78,7 @@ function tooltipText(variant: Icon): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class DecoratedRoomAvatar extends React.PureComponent<IProps, IState> {
|
export default class DecoratedRoomAvatar extends React.PureComponent<IProps, IState> {
|
||||||
private _dmUser: User;
|
private _dmUser: User | null;
|
||||||
private isUnmounted = false;
|
private isUnmounted = false;
|
||||||
private isWatchingTimeline = false;
|
private isWatchingTimeline = false;
|
||||||
|
|
||||||
|
@ -103,11 +103,11 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
|
||||||
return joinRule === JoinRule.Public;
|
return joinRule === JoinRule.Public;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get dmUser(): User {
|
private get dmUser(): User | null {
|
||||||
return this._dmUser;
|
return this._dmUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
private set dmUser(val: User) {
|
private set dmUser(val: User | null) {
|
||||||
const oldUser = this._dmUser;
|
const oldUser = this._dmUser;
|
||||||
this._dmUser = val;
|
this._dmUser = val;
|
||||||
if (oldUser && oldUser !== this._dmUser) {
|
if (oldUser && oldUser !== this._dmUser) {
|
||||||
|
@ -120,7 +120,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onRoomTimeline = (ev: MatrixEvent, room: Room | null): void => {
|
private onRoomTimeline = (ev: MatrixEvent, room?: Room): void => {
|
||||||
if (this.isUnmounted) return;
|
if (this.isUnmounted) return;
|
||||||
if (this.props.room.roomId !== room?.roomId) return;
|
if (this.props.room.roomId !== room?.roomId) return;
|
||||||
|
|
||||||
|
@ -182,7 +182,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
let badge: React.ReactNode;
|
let badge: React.ReactNode;
|
||||||
if (this.props.displayBadge) {
|
if (this.props.displayBadge && this.state.notificationState) {
|
||||||
badge = (
|
badge = (
|
||||||
<NotificationBadge
|
<NotificationBadge
|
||||||
notification={this.state.notificationState}
|
notification={this.state.notificationState}
|
||||||
|
@ -192,7 +192,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let icon;
|
let icon: JSX.Element | undefined;
|
||||||
if (this.state.icon !== Icon.None) {
|
if (this.state.icon !== Icon.None) {
|
||||||
icon = (
|
icon = (
|
||||||
<TextWithTooltip
|
<TextWithTooltip
|
||||||
|
|
|
@ -65,7 +65,7 @@ export default function MemberAvatar({
|
||||||
|
|
||||||
const name = member?.name ?? fallbackUserId;
|
const name = member?.name ?? fallbackUserId;
|
||||||
let title: string | undefined = props.title;
|
let title: string | undefined = props.title;
|
||||||
let imageUrl: string | undefined;
|
let imageUrl: string | null | undefined;
|
||||||
if (member?.name) {
|
if (member?.name) {
|
||||||
if (member.getMxcAvatarUrl()) {
|
if (member.getMxcAvatarUrl()) {
|
||||||
imageUrl = mediaFromMxc(member.getMxcAvatarUrl() ?? "").getThumbnailOfSourceHttp(
|
imageUrl = mediaFromMxc(member.getMxcAvatarUrl() ?? "").getThumbnailOfSourceHttp(
|
||||||
|
|
|
@ -30,13 +30,14 @@ import DMRoomMap from "../../../utils/DMRoomMap";
|
||||||
import { mediaFromMxc } from "../../../customisations/Media";
|
import { mediaFromMxc } from "../../../customisations/Media";
|
||||||
import { IOOBData } from "../../../stores/ThreepidInviteStore";
|
import { IOOBData } from "../../../stores/ThreepidInviteStore";
|
||||||
import { LocalRoom } from "../../../models/LocalRoom";
|
import { LocalRoom } from "../../../models/LocalRoom";
|
||||||
|
import { filterBoolean } from "../../../utils/arrays";
|
||||||
|
|
||||||
interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> {
|
interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> {
|
||||||
// Room may be left unset here, but if it is,
|
// Room may be left unset here, but if it is,
|
||||||
// oobData.avatarUrl should be set (else there
|
// oobData.avatarUrl should be set (else there
|
||||||
// would be nowhere to get the avatar from)
|
// would be nowhere to get the avatar from)
|
||||||
room?: Room;
|
room?: Room;
|
||||||
oobData?: IOOBData & {
|
oobData: IOOBData & {
|
||||||
roomId?: string;
|
roomId?: string;
|
||||||
};
|
};
|
||||||
viewAvatarOnClick?: boolean;
|
viewAvatarOnClick?: boolean;
|
||||||
|
@ -86,7 +87,7 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private static getImageUrls(props: IProps): string[] {
|
private static getImageUrls(props: IProps): string[] {
|
||||||
let oobAvatar = null;
|
let oobAvatar: string | null = null;
|
||||||
if (props.oobData.avatarUrl) {
|
if (props.oobData.avatarUrl) {
|
||||||
oobAvatar = mediaFromMxc(props.oobData.avatarUrl).getThumbnailOfSourceHttp(
|
oobAvatar = mediaFromMxc(props.oobData.avatarUrl).getThumbnailOfSourceHttp(
|
||||||
props.width,
|
props.width,
|
||||||
|
@ -94,28 +95,27 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
|
||||||
props.resizeMethod,
|
props.resizeMethod,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return [
|
|
||||||
|
return filterBoolean([
|
||||||
oobAvatar, // highest priority
|
oobAvatar, // highest priority
|
||||||
RoomAvatar.getRoomAvatarUrl(props),
|
RoomAvatar.getRoomAvatarUrl(props),
|
||||||
].filter(function (url) {
|
]);
|
||||||
return url !== null && url !== "";
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getRoomAvatarUrl(props: IProps): string {
|
private static getRoomAvatarUrl(props: IProps): string | null {
|
||||||
if (!props.room) return null;
|
if (!props.room) return null;
|
||||||
|
|
||||||
return Avatar.avatarUrlForRoom(props.room, props.width, props.height, props.resizeMethod);
|
return Avatar.avatarUrlForRoom(props.room, props.width, props.height, props.resizeMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onRoomAvatarClick = (): void => {
|
private onRoomAvatarClick = (): void => {
|
||||||
const avatarUrl = Avatar.avatarUrlForRoom(this.props.room, null, null, null);
|
const avatarUrl = Avatar.avatarUrlForRoom(this.props.room ?? null, undefined, undefined, undefined);
|
||||||
const params = {
|
const params = {
|
||||||
src: avatarUrl,
|
src: avatarUrl,
|
||||||
name: this.props.room.name,
|
name: this.props.room?.name,
|
||||||
};
|
};
|
||||||
|
|
||||||
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true);
|
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", undefined, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
private get roomIdName(): string | undefined {
|
private get roomIdName(): string | undefined {
|
||||||
|
@ -137,7 +137,7 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const { room, oobData, viewAvatarOnClick, onClick, className, ...otherProps } = this.props;
|
const { room, oobData, viewAvatarOnClick, onClick, className, ...otherProps } = this.props;
|
||||||
const roomName = room?.name ?? oobData.name;
|
const roomName = room?.name ?? oobData.name ?? "?";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
|
|
|
@ -21,8 +21,10 @@ import { IApp } from "../../../stores/WidgetStore";
|
||||||
import BaseAvatar, { BaseAvatarType } from "./BaseAvatar";
|
import BaseAvatar, { BaseAvatarType } from "./BaseAvatar";
|
||||||
import { mediaFromMxc } from "../../../customisations/Media";
|
import { mediaFromMxc } from "../../../customisations/Media";
|
||||||
|
|
||||||
interface IProps extends Omit<ComponentProps<BaseAvatarType>, "name" | "url" | "urls"> {
|
interface IProps extends Omit<ComponentProps<BaseAvatarType>, "name" | "url" | "urls" | "height" | "width"> {
|
||||||
app: IApp;
|
app: IApp;
|
||||||
|
height?: number;
|
||||||
|
width?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WidgetAvatar: React.FC<IProps> = ({ app, className, width = 20, height = 20, ...props }) => {
|
const WidgetAvatar: React.FC<IProps> = ({ app, className, width = 20, height = 20, ...props }) => {
|
||||||
|
@ -44,7 +46,7 @@ const WidgetAvatar: React.FC<IProps> = ({ app, className, width = 20, height = 2
|
||||||
name={app.id}
|
name={app.id}
|
||||||
className={classNames("mx_WidgetAvatar", className)}
|
className={classNames("mx_WidgetAvatar", className)}
|
||||||
// MSC2765
|
// MSC2765
|
||||||
url={app.avatar_url ? mediaFromMxc(app.avatar_url).getSquareThumbnailHttp(20) : undefined}
|
url={app.avatar_url ? mediaFromMxc(app.avatar_url).getSquareThumbnailHttp(20) : null}
|
||||||
urls={iconUrls}
|
urls={iconUrls}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
|
|
|
@ -42,14 +42,14 @@ const BeaconListItem: React.FC<Props & HTMLProps<HTMLLIElement>> = ({ beacon, ..
|
||||||
const matrixClient = useContext(MatrixClientContext);
|
const matrixClient = useContext(MatrixClientContext);
|
||||||
const room = matrixClient.getRoom(beacon.roomId);
|
const room = matrixClient.getRoom(beacon.roomId);
|
||||||
|
|
||||||
if (!latestLocationState || !beacon.isLive) {
|
if (!latestLocationState || !beacon.isLive || !room) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSelfLocation = beacon.beaconInfo.assetType === LocationAssetType.Self;
|
const isSelfLocation = beacon.beaconInfo?.assetType === LocationAssetType.Self;
|
||||||
const beaconMember = isSelfLocation ? room.getMember(beacon.beaconInfoOwner) : undefined;
|
const beaconMember = isSelfLocation ? room.getMember(beacon.beaconInfoOwner) : null;
|
||||||
|
|
||||||
const humanizedUpdateTime = humanizeTime(latestLocationState.timestamp);
|
const humanizedUpdateTime = (latestLocationState.timestamp && humanizeTime(latestLocationState.timestamp)) || "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="mx_BeaconListItem" {...rest}>
|
<li className="mx_BeaconListItem" {...rest}>
|
||||||
|
@ -62,7 +62,7 @@ const BeaconListItem: React.FC<Props & HTMLProps<HTMLLIElement>> = ({ beacon, ..
|
||||||
<BeaconStatus
|
<BeaconStatus
|
||||||
className="mx_BeaconListItem_status"
|
className="mx_BeaconListItem_status"
|
||||||
beacon={beacon}
|
beacon={beacon}
|
||||||
label={beaconMember?.name || beacon.beaconInfo.description || beacon.beaconInfoOwner}
|
label={beaconMember?.name || beacon.beaconInfo?.description || beacon.beaconInfoOwner}
|
||||||
displayStatus={BeaconDisplayStatus.Active}
|
displayStatus={BeaconDisplayStatus.Active}
|
||||||
>
|
>
|
||||||
{/* eat events from interactive share buttons
|
{/* eat events from interactive share buttons
|
||||||
|
|
|
@ -27,11 +27,11 @@ interface Props {
|
||||||
beacon: Beacon;
|
beacon: Beacon;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useBeaconName = (beacon: Beacon): string => {
|
const useBeaconName = (beacon: Beacon): string | undefined => {
|
||||||
const matrixClient = useContext(MatrixClientContext);
|
const matrixClient = useContext(MatrixClientContext);
|
||||||
|
|
||||||
if (beacon.beaconInfo.assetType !== LocationAssetType.Self) {
|
if (beacon.beaconInfo?.assetType !== LocationAssetType.Self) {
|
||||||
return beacon.beaconInfo.description;
|
return beacon.beaconInfo?.description;
|
||||||
}
|
}
|
||||||
const room = matrixClient.getRoom(beacon.roomId);
|
const room = matrixClient.getRoom(beacon.roomId);
|
||||||
const member = room?.getMember(beacon.beaconInfoOwner);
|
const member = room?.getMember(beacon.beaconInfoOwner);
|
||||||
|
|
|
@ -23,7 +23,6 @@ import { Icon as LiveLocationIcon } from "../../../../res/img/location/live-loca
|
||||||
import { useLiveBeacons } from "../../../utils/beacon/useLiveBeacons";
|
import { useLiveBeacons } from "../../../utils/beacon/useLiveBeacons";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import BaseDialog from "../dialogs/BaseDialog";
|
import BaseDialog from "../dialogs/BaseDialog";
|
||||||
import { IDialogProps } from "../dialogs/IDialogProps";
|
|
||||||
import Map from "../location/Map";
|
import Map from "../location/Map";
|
||||||
import ZoomButtons from "../location/ZoomButtons";
|
import ZoomButtons from "../location/ZoomButtons";
|
||||||
import BeaconMarker from "./BeaconMarker";
|
import BeaconMarker from "./BeaconMarker";
|
||||||
|
@ -38,11 +37,12 @@ import MapFallback from "../location/MapFallback";
|
||||||
import { MapError } from "../location/MapError";
|
import { MapError } from "../location/MapError";
|
||||||
import { LocationShareError } from "../../../utils/location";
|
import { LocationShareError } from "../../../utils/location";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
roomId: Room["roomId"];
|
roomId: Room["roomId"];
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
// open the map centered on this beacon's location
|
// open the map centered on this beacon's location
|
||||||
initialFocusedBeacon?: Beacon;
|
initialFocusedBeacon?: Beacon;
|
||||||
|
onFinished(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// track the 'focused time' as ts
|
// track the 'focused time' as ts
|
||||||
|
@ -54,7 +54,7 @@ interface FocusedBeaconState {
|
||||||
beacon?: Beacon;
|
beacon?: Beacon;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBoundsCenter = (bounds: Bounds): string | undefined => {
|
const getBoundsCenter = (bounds?: Bounds): string | undefined => {
|
||||||
if (!bounds) {
|
if (!bounds) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -70,10 +70,10 @@ const useMapPosition = (
|
||||||
{ beacon, ts }: FocusedBeaconState,
|
{ beacon, ts }: FocusedBeaconState,
|
||||||
): {
|
): {
|
||||||
bounds?: Bounds;
|
bounds?: Bounds;
|
||||||
centerGeoUri: string;
|
centerGeoUri?: string;
|
||||||
} => {
|
} => {
|
||||||
const [bounds, setBounds] = useState<Bounds | undefined>(getBeaconBounds(liveBeacons));
|
const [bounds, setBounds] = useState<Bounds | undefined>(getBeaconBounds(liveBeacons));
|
||||||
const [centerGeoUri, setCenterGeoUri] = useState<string>(
|
const [centerGeoUri, setCenterGeoUri] = useState<string | undefined>(
|
||||||
beacon?.latestLocationState?.uri || getBoundsCenter(bounds),
|
beacon?.latestLocationState?.uri || getBoundsCenter(bounds),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -45,12 +45,12 @@ const DialogOwnBeaconStatus: React.FC<Props> = ({ roomId }) => {
|
||||||
const matrixClient = useContext(MatrixClientContext);
|
const matrixClient = useContext(MatrixClientContext);
|
||||||
const room = matrixClient.getRoom(roomId);
|
const room = matrixClient.getRoom(roomId);
|
||||||
|
|
||||||
if (!beacon?.isLive) {
|
if (!beacon?.isLive || !room) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSelfLocation = beacon.beaconInfo.assetType === LocationAssetType.Self;
|
const isSelfLocation = beacon.beaconInfo?.assetType === LocationAssetType.Self;
|
||||||
const beaconMember = isSelfLocation ? room.getMember(beacon.beaconInfoOwner) : undefined;
|
const beaconMember = isSelfLocation ? room.getMember(beacon.beaconInfoOwner) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_DialogOwnBeaconStatus">
|
<div className="mx_DialogOwnBeaconStatus">
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { Icon as LiveLocationIcon } from "../../../../res/img/location/live-loca
|
||||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import dispatcher from "../../../dispatcher/dispatcher";
|
import dispatcher from "../../../dispatcher/dispatcher";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isMinimized?: boolean;
|
isMinimized?: boolean;
|
||||||
|
@ -121,7 +121,7 @@ const LeftPanelLiveShareWarning: React.FC<Props> = ({ isMinimized }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const onWarningClick = relevantBeacon
|
const onWarningClick = relevantBeacon
|
||||||
? () => {
|
? (_e: ButtonEvent) => {
|
||||||
dispatcher.dispatch<ViewRoomPayload>({
|
dispatcher.dispatch<ViewRoomPayload>({
|
||||||
action: Action.ViewRoom,
|
action: Action.ViewRoom,
|
||||||
room_id: relevantBeacon.roomId,
|
room_id: relevantBeacon.roomId,
|
||||||
|
@ -131,7 +131,7 @@ const LeftPanelLiveShareWarning: React.FC<Props> = ({ isMinimized }) => {
|
||||||
highlighted: true,
|
highlighted: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
: undefined;
|
: null;
|
||||||
|
|
||||||
const label = getLabel(hasStoppingErrors, hasLocationPublishErrors);
|
const label = getLabel(hasStoppingErrors, hasLocationPublishErrors);
|
||||||
|
|
||||||
|
|
|
@ -40,13 +40,19 @@ const getUpdateInterval = (ms: number): number => {
|
||||||
const useMsRemaining = (beacon: Beacon): number => {
|
const useMsRemaining = (beacon: Beacon): number => {
|
||||||
const beaconInfo = useEventEmitterState(beacon, BeaconEvent.Update, () => beacon.beaconInfo);
|
const beaconInfo = useEventEmitterState(beacon, BeaconEvent.Update, () => beacon.beaconInfo);
|
||||||
|
|
||||||
const [msRemaining, setMsRemaining] = useState(() => getBeaconMsUntilExpiry(beaconInfo));
|
const [msRemaining, setMsRemaining] = useState(() => (beaconInfo ? getBeaconMsUntilExpiry(beaconInfo) : 0));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!beaconInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setMsRemaining(getBeaconMsUntilExpiry(beaconInfo));
|
setMsRemaining(getBeaconMsUntilExpiry(beaconInfo));
|
||||||
}, [beaconInfo]);
|
}, [beaconInfo]);
|
||||||
|
|
||||||
const updateMsRemaining = useCallback(() => {
|
const updateMsRemaining = useCallback(() => {
|
||||||
|
if (!beaconInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const ms = getBeaconMsUntilExpiry(beaconInfo);
|
const ms = getBeaconMsUntilExpiry(beaconInfo);
|
||||||
setMsRemaining(ms);
|
setMsRemaining(ms);
|
||||||
}, [beaconInfo]);
|
}, [beaconInfo]);
|
||||||
|
|
|
@ -42,7 +42,7 @@ const OwnBeaconStatus: React.FC<Props & HTMLProps<HTMLDivElement>> = ({ beacon,
|
||||||
stoppingInProgress,
|
stoppingInProgress,
|
||||||
onStopSharing,
|
onStopSharing,
|
||||||
onResetLocationPublishError,
|
onResetLocationPublishError,
|
||||||
} = useOwnLiveBeacons([beacon?.identifier]);
|
} = useOwnLiveBeacons(beacon?.identifier ? [beacon?.identifier] : []);
|
||||||
|
|
||||||
// combine display status with errors that only occur for user's own beacons
|
// combine display status with errors that only occur for user's own beacons
|
||||||
const ownDisplayStatus = hasLocationPublishError || hasStopSharingError ? BeaconDisplayStatus.Error : displayStatus;
|
const ownDisplayStatus = hasLocationPublishError || hasStopSharingError ? BeaconDisplayStatus.Error : displayStatus;
|
||||||
|
|
|
@ -28,9 +28,9 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShareLatestLocation: React.FC<Props> = ({ latestLocationState }) => {
|
const ShareLatestLocation: React.FC<Props> = ({ latestLocationState }) => {
|
||||||
const [coords, setCoords] = useState(null);
|
const [coords, setCoords] = useState<GeolocationCoordinates | undefined>();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!latestLocationState) {
|
if (!latestLocationState?.uri) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const coords = parseGeoUri(latestLocationState.uri);
|
const coords = parseGeoUri(latestLocationState.uri);
|
||||||
|
|
|
@ -40,7 +40,5 @@ export const getBeaconDisplayStatus = (
|
||||||
if (!latestLocationState) {
|
if (!latestLocationState) {
|
||||||
return BeaconDisplayStatus.Loading;
|
return BeaconDisplayStatus.Loading;
|
||||||
}
|
}
|
||||||
if (latestLocationState) {
|
return BeaconDisplayStatus.Active;
|
||||||
return BeaconDisplayStatus.Active;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,13 +25,16 @@ import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import QRCode from "../elements/QRCode";
|
import QRCode from "../elements/QRCode";
|
||||||
import Heading from "../typography/Heading";
|
import Heading from "../typography/Heading";
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
|
|
||||||
const fallbackAppStore = "https://apps.apple.com/app/vector/id1083446067";
|
const fallbackAppStore = "https://apps.apple.com/app/vector/id1083446067";
|
||||||
const fallbackGooglePlay = "https://play.google.com/store/apps/details?id=im.vector.app";
|
const fallbackGooglePlay = "https://play.google.com/store/apps/details?id=im.vector.app";
|
||||||
const fallbackFDroid = "https://f-droid.org/repository/browse/?fdid=im.vector.app";
|
const fallbackFDroid = "https://f-droid.org/repository/browse/?fdid=im.vector.app";
|
||||||
|
|
||||||
export const AppDownloadDialog: FC<IDialogProps> = ({ onFinished }: IDialogProps) => {
|
interface Props {
|
||||||
|
onFinished(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AppDownloadDialog: FC<Props> = ({ onFinished }) => {
|
||||||
const brand = SdkConfig.get("brand");
|
const brand = SdkConfig.get("brand");
|
||||||
const desktopBuilds = SdkConfig.getObject("desktop_builds");
|
const desktopBuilds = SdkConfig.getObject("desktop_builds");
|
||||||
const mobileBuilds = SdkConfig.getObject("mobile_builds");
|
const mobileBuilds = SdkConfig.getObject("mobile_builds");
|
||||||
|
|
|
@ -21,17 +21,16 @@ import FocusLock from "react-focus-lock";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import Heading from "../typography/Heading";
|
import Heading from "../typography/Heading";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
import { PosthogScreenTracker, ScreenName } from "../../../PosthogTrackers";
|
import { PosthogScreenTracker, ScreenName } from "../../../PosthogTrackers";
|
||||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
// Whether the dialog should have a 'close' button that will
|
// Whether the dialog should have a 'close' button that will
|
||||||
// cause the dialog to be cancelled. This should only be set
|
// cause the dialog to be cancelled. This should only be set
|
||||||
// to false if there is nothing the app can sensibly do if the
|
// to false if there is nothing the app can sensibly do if the
|
||||||
|
@ -75,6 +74,7 @@ interface IProps extends IDialogProps {
|
||||||
|
|
||||||
// optional Posthog ScreenName to supply during the lifetime of this dialog
|
// optional Posthog ScreenName to supply during the lifetime of this dialog
|
||||||
"screenName"?: ScreenName;
|
"screenName"?: ScreenName;
|
||||||
|
onFinished(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -86,7 +86,7 @@ interface IProps extends IDialogProps {
|
||||||
export default class BaseDialog extends React.Component<IProps> {
|
export default class BaseDialog extends React.Component<IProps> {
|
||||||
private matrixClient: MatrixClient;
|
private matrixClient: MatrixClient;
|
||||||
|
|
||||||
public static defaultProps = {
|
public static defaultProps: Partial<IProps> = {
|
||||||
hasCancel: true,
|
hasCancel: true,
|
||||||
fixedWidth: true,
|
fixedWidth: true,
|
||||||
};
|
};
|
||||||
|
@ -98,9 +98,7 @@ export default class BaseDialog extends React.Component<IProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onKeyDown = (e: KeyboardEvent | React.KeyboardEvent): void => {
|
private onKeyDown = (e: KeyboardEvent | React.KeyboardEvent): void => {
|
||||||
if (this.props.onKeyDown) {
|
this.props.onKeyDown?.(e);
|
||||||
this.props.onKeyDown(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
@ -109,13 +107,13 @@ export default class BaseDialog extends React.Component<IProps> {
|
||||||
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.props.onFinished(false);
|
this.props.onFinished();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCancelClick = (e: ButtonEvent): void => {
|
private onCancelClick = (): void => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
|
@ -27,8 +26,9 @@ import GenericFeatureFeedbackDialog from "./GenericFeatureFeedbackDialog";
|
||||||
|
|
||||||
// XXX: Keep this around for re-use in future Betas
|
// XXX: Keep this around for re-use in future Betas
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
featureId: string;
|
featureId: string;
|
||||||
|
onFinished(sendFeedback?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
|
const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
|
||||||
|
@ -49,7 +49,7 @@ const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
kind="link_inline"
|
kind="link_inline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onFinished(false);
|
onFinished();
|
||||||
defaultDispatcher.dispatch({
|
defaultDispatcher.dispatch({
|
||||||
action: Action.ViewUserSettings,
|
action: Action.ViewUserSettings,
|
||||||
initialTabId: UserTab.Labs,
|
initialTabId: UserTab.Labs,
|
||||||
|
|
|
@ -26,19 +26,19 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
import BaseDialog from "../dialogs/BaseDialog";
|
import BaseDialog from "../dialogs/BaseDialog";
|
||||||
import InfoDialog from "../dialogs/InfoDialog";
|
import InfoDialog from "../dialogs/InfoDialog";
|
||||||
import DialogButtons from "../elements/DialogButtons";
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||||
|
|
||||||
interface IBulkRedactDialogProps extends IDialogProps {
|
interface Props {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
room: Room;
|
room: Room;
|
||||||
member: RoomMember;
|
member: RoomMember;
|
||||||
|
onFinished(redact?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BulkRedactDialog: React.FC<IBulkRedactDialogProps> = (props) => {
|
const BulkRedactDialog: React.FC<Props> = (props) => {
|
||||||
const { matrixClient: cli, room, member, onFinished } = props;
|
const { matrixClient: cli, room, member, onFinished } = props;
|
||||||
const [keepStateEvents, setKeepStateEvents] = useState(true);
|
const [keepStateEvents, setKeepStateEvents] = useState(true);
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import Spinner from "../elements/Spinner";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
redact: () => Promise<void>;
|
redact: () => Promise<void>;
|
||||||
onFinished: (success: boolean) => void;
|
onFinished: (success?: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
|
|
@ -39,7 +39,7 @@ interface IProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
roomId?: string;
|
roomId?: string;
|
||||||
onFinished: (success: boolean, reason?: string) => void;
|
onFinished: (success?: boolean, reason?: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -55,7 +55,7 @@ interface IState {
|
||||||
* Also tweaks the style for 'dangerous' actions (albeit only with colour)
|
* Also tweaks the style for 'dangerous' actions (albeit only with colour)
|
||||||
*/
|
*/
|
||||||
export default class ConfirmUserActionDialog extends React.Component<IProps, IState> {
|
export default class ConfirmUserActionDialog extends React.Component<IProps, IState> {
|
||||||
public static defaultProps = {
|
public static defaultProps: Partial<IProps> = {
|
||||||
danger: false,
|
danger: false,
|
||||||
askReason: false,
|
askReason: false,
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,7 +21,7 @@ import BaseDialog from "./BaseDialog";
|
||||||
import DialogButtons from "../elements/DialogButtons";
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onFinished: (success: boolean) => void;
|
onFinished: (success?: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ConfirmWipeDeviceDialog extends React.Component<IProps> {
|
export default class ConfirmWipeDeviceDialog extends React.Component<IProps> {
|
||||||
|
|
|
@ -41,7 +41,7 @@ interface IProps {
|
||||||
defaultName?: string;
|
defaultName?: string;
|
||||||
parentSpace?: Room;
|
parentSpace?: Room;
|
||||||
defaultEncrypted?: boolean;
|
defaultEncrypted?: boolean;
|
||||||
onFinished(proceed: boolean, opts?: IOpts): void;
|
onFinished(proceed?: boolean, opts?: IOpts): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
|
|
@ -24,9 +24,10 @@ import Modal from "../../../Modal";
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import DialogButtons from "../elements/DialogButtons";
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
import QuestionDialog from "./QuestionDialog";
|
import QuestionDialog from "./QuestionDialog";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
|
|
||||||
interface IProps extends IDialogProps {}
|
interface IProps {
|
||||||
|
onFinished(logout?: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
const CryptoStoreTooNewDialog: React.FC<IProps> = (props: IProps) => {
|
const CryptoStoreTooNewDialog: React.FC<IProps> = (props: IProps) => {
|
||||||
const brand = SdkConfig.get().brand;
|
const brand = SdkConfig.get().brand;
|
||||||
|
@ -72,7 +73,7 @@ const CryptoStoreTooNewDialog: React.FC<IProps> = (props: IProps) => {
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("Continue With Encryption Disabled")}
|
primaryButton={_t("Continue With Encryption Disabled")}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
onPrimaryButtonClick={props.onFinished}
|
onPrimaryButtonClick={() => props.onFinished(false)}
|
||||||
>
|
>
|
||||||
<button onClick={_onLogoutClicked}>{_t("Sign out")}</button>
|
<button onClick={_onLogoutClicked}>{_t("Sign out")}</button>
|
||||||
</DialogButtons>
|
</DialogButtons>
|
||||||
|
|
|
@ -39,7 +39,7 @@ type DialogAesthetics = Partial<{
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onFinished: (success: boolean) => void;
|
onFinished: (success?: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
|
|
@ -65,7 +65,7 @@ const Tools: Record<Category, [label: string, tool: Tool][]> = {
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
onFinished(finished: boolean): void;
|
onFinished(finished?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ToolInfo = [label: string, tool: Tool];
|
type ToolInfo = [label: string, tool: Tool];
|
||||||
|
|
|
@ -20,17 +20,16 @@ import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { PollEndEvent } from "matrix-js-sdk/src/extensible_events_v1/PollEndEvent";
|
import { PollEndEvent } from "matrix-js-sdk/src/extensible_events_v1/PollEndEvent";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
import QuestionDialog from "./QuestionDialog";
|
import QuestionDialog from "./QuestionDialog";
|
||||||
import { findTopAnswer } from "../messages/MPollBody";
|
import { findTopAnswer } from "../messages/MPollBody";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import ErrorDialog from "./ErrorDialog";
|
import ErrorDialog from "./ErrorDialog";
|
||||||
import { GetRelationsForEvent } from "../rooms/EventTile";
|
import { GetRelationsForEvent } from "../rooms/EventTile";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
event: MatrixEvent;
|
event: MatrixEvent;
|
||||||
onFinished: (success: boolean) => void;
|
onFinished: (success?: boolean) => void;
|
||||||
getRelationsForEvent?: GetRelationsForEvent;
|
getRelationsForEvent?: GetRelationsForEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ import { _t } from "../../../languageHandler";
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onFinished: (success: boolean) => void;
|
onFinished: (success?: boolean) => void;
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
button?: string;
|
button?: string;
|
||||||
|
@ -44,7 +44,7 @@ interface IState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ErrorDialog extends React.Component<IProps, IState> {
|
export default class ErrorDialog extends React.Component<IProps, IState> {
|
||||||
public static defaultProps = {
|
public static defaultProps: Partial<IProps> = {
|
||||||
focus: true,
|
focus: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ import { Room } from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import DialogButtons from "../elements/DialogButtons";
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
|
@ -44,8 +43,9 @@ import InfoDialog from "./InfoDialog";
|
||||||
import ChatExport from "../../../customisations/ChatExport";
|
import ChatExport from "../../../customisations/ChatExport";
|
||||||
import { validateNumberInRange } from "../../../utils/validate";
|
import { validateNumberInRange } from "../../../utils/validate";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
onFinished(doExport?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExportConfig {
|
interface ExportConfig {
|
||||||
|
|
|
@ -24,7 +24,6 @@ import SdkConfig from "../../../SdkConfig";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import BugReportDialog from "./BugReportDialog";
|
import BugReportDialog from "./BugReportDialog";
|
||||||
import InfoDialog from "./InfoDialog";
|
import InfoDialog from "./InfoDialog";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
import { submitFeedback } from "../../../rageshake/submit-rageshake";
|
import { submitFeedback } from "../../../rageshake/submit-rageshake";
|
||||||
import { useStateToggle } from "../../../hooks/useStateToggle";
|
import { useStateToggle } from "../../../hooks/useStateToggle";
|
||||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||||
|
@ -33,8 +32,9 @@ const existingIssuesUrl =
|
||||||
"https://github.com/vector-im/element-web/issues" + "?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc";
|
"https://github.com/vector-im/element-web/issues" + "?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc";
|
||||||
const newIssueUrl = "https://github.com/vector-im/element-web/issues/new/choose";
|
const newIssueUrl = "https://github.com/vector-im/element-web/issues/new/choose";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
feature?: string;
|
feature?: string;
|
||||||
|
onFinished(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
|
const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
|
||||||
|
|
|
@ -29,7 +29,6 @@ import { _t } from "../../../languageHandler";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import { useSettingValue } from "../../../hooks/useSettings";
|
import { useSettingValue } from "../../../hooks/useSettings";
|
||||||
import { Layout } from "../../../settings/enums/Layout";
|
import { Layout } from "../../../settings/enums/Layout";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import { avatarUrlForUser } from "../../../Avatar";
|
import { avatarUrlForUser } from "../../../Avatar";
|
||||||
import EventTile from "../rooms/EventTile";
|
import EventTile from "../rooms/EventTile";
|
||||||
|
@ -55,13 +54,14 @@ import { RoomContextDetails } from "../rooms/RoomContextDetails";
|
||||||
|
|
||||||
const AVATAR_SIZE = 30;
|
const AVATAR_SIZE = 30;
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
// The event to forward
|
// The event to forward
|
||||||
event: MatrixEvent;
|
event: MatrixEvent;
|
||||||
// We need a permalink creator for the source room to pass through to EventTile
|
// We need a permalink creator for the source room to pass through to EventTile
|
||||||
// in case the event is a reply (even though the user can't get at the link)
|
// in case the event is a reply (even though the user can't get at the link)
|
||||||
permalinkCreator: RoomPermalinkCreator;
|
permalinkCreator: RoomPermalinkCreator;
|
||||||
|
onFinished(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IEntryProps {
|
interface IEntryProps {
|
||||||
|
|
|
@ -20,17 +20,17 @@ import QuestionDialog from "./QuestionDialog";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
import { submitFeedback } from "../../../rageshake/submit-rageshake";
|
import { submitFeedback } from "../../../rageshake/submit-rageshake";
|
||||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import InfoDialog from "./InfoDialog";
|
import InfoDialog from "./InfoDialog";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
title: string;
|
title: string;
|
||||||
subheading: string;
|
subheading: string;
|
||||||
rageshakeLabel: string;
|
rageshakeLabel: string;
|
||||||
rageshakeData?: Record<string, string>;
|
rageshakeData?: Record<string, string>;
|
||||||
|
onFinished(sendFeedback?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GenericFeatureFeedbackDialog: React.FC<IProps> = ({
|
const GenericFeatureFeedbackDialog: React.FC<IProps> = ({
|
||||||
|
|
|
@ -1,280 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import classNames from "classnames";
|
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
|
||||||
|
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
|
||||||
import Modal from "../../../Modal";
|
|
||||||
import QuestionDialog from "./QuestionDialog";
|
|
||||||
import SdkConfig from "../../../SdkConfig";
|
|
||||||
import { _t } from "../../../languageHandler";
|
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
|
||||||
import { HostSignupStore } from "../../../stores/HostSignupStore";
|
|
||||||
import { OwnProfileStore } from "../../../stores/OwnProfileStore";
|
|
||||||
import { IPostmessage, IPostmessageResponseData, PostmessageAction } from "./HostSignupDialogTypes";
|
|
||||||
import { IConfigOptions } from "../../../IConfigOptions";
|
|
||||||
import { SnakedObject } from "../../../utils/SnakedObject";
|
|
||||||
|
|
||||||
interface IProps {}
|
|
||||||
|
|
||||||
interface IState {
|
|
||||||
completed: boolean;
|
|
||||||
error: string;
|
|
||||||
minimized: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class HostSignupDialog extends React.PureComponent<IProps, IState> {
|
|
||||||
private iframeRef: React.RefObject<HTMLIFrameElement> = React.createRef();
|
|
||||||
private readonly config: SnakedObject<IConfigOptions["host_signup"]>;
|
|
||||||
|
|
||||||
public constructor(props: IProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
completed: false,
|
|
||||||
error: null,
|
|
||||||
minimized: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.config = SdkConfig.getObject("host_signup");
|
|
||||||
}
|
|
||||||
|
|
||||||
private messageHandler = async (message: IPostmessage): Promise<void> => {
|
|
||||||
if (!this.config.get("url").startsWith(message.origin)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (message.data.action) {
|
|
||||||
case PostmessageAction.HostSignupAccountDetailsRequest:
|
|
||||||
this.onAccountDetailsRequest();
|
|
||||||
break;
|
|
||||||
case PostmessageAction.Maximize:
|
|
||||||
this.setState({
|
|
||||||
minimized: false,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case PostmessageAction.Minimize:
|
|
||||||
this.setState({
|
|
||||||
minimized: true,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case PostmessageAction.SetupComplete:
|
|
||||||
this.setState({
|
|
||||||
completed: true,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case PostmessageAction.CloseDialog:
|
|
||||||
return this.closeDialog();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private maximizeDialog = (): void => {
|
|
||||||
this.setState({
|
|
||||||
minimized: false,
|
|
||||||
});
|
|
||||||
// Send this action to the iframe so it can act accordingly
|
|
||||||
this.sendMessage({
|
|
||||||
action: PostmessageAction.Maximize,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
private minimizeDialog = (): void => {
|
|
||||||
this.setState({
|
|
||||||
minimized: true,
|
|
||||||
});
|
|
||||||
// Send this action to the iframe so it can act accordingly
|
|
||||||
this.sendMessage({
|
|
||||||
action: PostmessageAction.Minimize,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
private closeDialog = async (): Promise<void> => {
|
|
||||||
window.removeEventListener("message", this.messageHandler);
|
|
||||||
// Finally clear the flag in
|
|
||||||
return HostSignupStore.instance.setHostSignupActive(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
private onCloseClick = async (): Promise<void> => {
|
|
||||||
if (this.state.completed) {
|
|
||||||
// We're done, close
|
|
||||||
return this.closeDialog();
|
|
||||||
} else {
|
|
||||||
Modal.createDialog(QuestionDialog, {
|
|
||||||
title: _t("Confirm abort of host creation"),
|
|
||||||
description: _t(
|
|
||||||
"Are you sure you wish to abort creation of the host? The process cannot be continued.",
|
|
||||||
),
|
|
||||||
button: _t("Abort"),
|
|
||||||
onFinished: (result) => {
|
|
||||||
if (result) {
|
|
||||||
return this.closeDialog();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private sendMessage = (message: IPostmessageResponseData): void => {
|
|
||||||
this.iframeRef.current.contentWindow.postMessage(message, this.config.get("url"));
|
|
||||||
};
|
|
||||||
|
|
||||||
private async sendAccountDetails(): Promise<void> {
|
|
||||||
const openIdToken = await MatrixClientPeg.get().getOpenIdToken();
|
|
||||||
if (!openIdToken || !openIdToken.access_token) {
|
|
||||||
logger.warn("Failed to connect to homeserver for OpenID token.");
|
|
||||||
this.setState({
|
|
||||||
completed: true,
|
|
||||||
error: _t("Failed to connect to your homeserver. Please close this dialog and try again."),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.sendMessage({
|
|
||||||
action: PostmessageAction.HostSignupAccountDetails,
|
|
||||||
account: {
|
|
||||||
accessToken: await MatrixClientPeg.get().getAccessToken(),
|
|
||||||
name: OwnProfileStore.instance.displayName,
|
|
||||||
openIdToken: openIdToken.access_token,
|
|
||||||
serverName: await MatrixClientPeg.get().getDomain(),
|
|
||||||
userLocalpart: await MatrixClientPeg.get().getUserIdLocalpart(),
|
|
||||||
termsAccepted: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private onAccountDetailsDialogFinished = async (result: boolean): Promise<void> => {
|
|
||||||
if (result) {
|
|
||||||
return this.sendAccountDetails();
|
|
||||||
}
|
|
||||||
return this.closeDialog();
|
|
||||||
};
|
|
||||||
|
|
||||||
private onAccountDetailsRequest = (): void => {
|
|
||||||
const cookiePolicyUrl = this.config.get("cookie_policy_url");
|
|
||||||
const privacyPolicyUrl = this.config.get("privacy_policy_url");
|
|
||||||
const tosUrl = this.config.get("terms_of_service_url");
|
|
||||||
|
|
||||||
const textComponent = (
|
|
||||||
<>
|
|
||||||
<p>
|
|
||||||
{_t(
|
|
||||||
"Continuing temporarily allows the %(hostSignupBrand)s setup process to access your " +
|
|
||||||
"account to fetch verified email addresses. This data is not stored.",
|
|
||||||
{
|
|
||||||
hostSignupBrand: this.config.get("brand"),
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{_t(
|
|
||||||
"Learn more in our <privacyPolicyLink />, <termsOfServiceLink /> and <cookiePolicyLink />.",
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
cookiePolicyLink: () => (
|
|
||||||
<a href={cookiePolicyUrl} target="_blank" rel="noreferrer noopener">
|
|
||||||
{_t("Cookie Policy")}
|
|
||||||
</a>
|
|
||||||
),
|
|
||||||
privacyPolicyLink: () => (
|
|
||||||
<a href={privacyPolicyUrl} target="_blank" rel="noreferrer noopener">
|
|
||||||
{_t("Privacy Policy")}
|
|
||||||
</a>
|
|
||||||
),
|
|
||||||
termsOfServiceLink: () => (
|
|
||||||
<a href={tosUrl} target="_blank" rel="noreferrer noopener">
|
|
||||||
{_t("Terms of Service")}
|
|
||||||
</a>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
Modal.createDialog(QuestionDialog, {
|
|
||||||
title: _t("You should know"),
|
|
||||||
description: textComponent,
|
|
||||||
button: _t("Continue"),
|
|
||||||
onFinished: this.onAccountDetailsDialogFinished,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
public componentDidMount(): void {
|
|
||||||
window.addEventListener("message", this.messageHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentWillUnmount(): void {
|
|
||||||
if (HostSignupStore.instance.isHostSignupActive) {
|
|
||||||
// Run the close dialog actions if we're still active, otherwise good to go
|
|
||||||
this.closeDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
|
||||||
return (
|
|
||||||
<div className={classNames({ mx_Dialog_wrapper: !this.state.minimized })}>
|
|
||||||
<div
|
|
||||||
className={classNames("mx_Dialog", {
|
|
||||||
mx_HostSignupDialog_minimized: this.state.minimized,
|
|
||||||
mx_HostSignupDialog: !this.state.minimized,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{this.state.minimized && (
|
|
||||||
<div className="mx_Dialog_header mx_Dialog_headerWithButton">
|
|
||||||
<div className="mx_Dialog_title">
|
|
||||||
{_t("%(hostSignupBrand)s Setup", {
|
|
||||||
hostSignupBrand: this.config.get("brand"),
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<AccessibleButton
|
|
||||||
className="mx_HostSignup_maximize_button"
|
|
||||||
onClick={this.maximizeDialog}
|
|
||||||
aria-label={_t("Maximise dialog")}
|
|
||||||
title={_t("Maximise dialog")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!this.state.minimized && (
|
|
||||||
<div className="mx_Dialog_header mx_Dialog_headerWithCancel">
|
|
||||||
<AccessibleButton
|
|
||||||
onClick={this.minimizeDialog}
|
|
||||||
className="mx_HostSignup_minimize_button"
|
|
||||||
aria-label={_t("Minimise dialog")}
|
|
||||||
title={_t("Minimise dialog")}
|
|
||||||
/>
|
|
||||||
<AccessibleButton
|
|
||||||
onClick={this.onCloseClick}
|
|
||||||
className="mx_Dialog_cancelButton"
|
|
||||||
aria-label={_t("Close dialog")}
|
|
||||||
title={_t("Close dialog")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{this.state.error && <div>{this.state.error}</div>}
|
|
||||||
{!this.state.error && (
|
|
||||||
<iframe
|
|
||||||
title={_t("Upgrade to %(hostSignupBrand)s", {
|
|
||||||
hostSignupBrand: this.config.get("brand"),
|
|
||||||
})}
|
|
||||||
src={this.config.get("url")}
|
|
||||||
ref={this.iframeRef}
|
|
||||||
sandbox="allow-forms allow-scripts allow-same-origin allow-popups"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export enum PostmessageAction {
|
|
||||||
CloseDialog = "close_dialog",
|
|
||||||
HostSignupAccountDetails = "host_signup_account_details",
|
|
||||||
HostSignupAccountDetailsRequest = "host_signup_account_details_request",
|
|
||||||
Minimize = "host_signup_minimize",
|
|
||||||
Maximize = "host_signup_maximize",
|
|
||||||
SetupComplete = "setup_complete",
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IAccountData {
|
|
||||||
accessToken: string;
|
|
||||||
name: string;
|
|
||||||
openIdToken: string;
|
|
||||||
serverName: string;
|
|
||||||
userLocalpart: string;
|
|
||||||
termsAccepted: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPostmessageRequestData {
|
|
||||||
action: PostmessageAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPostmessageResponseData {
|
|
||||||
action: PostmessageAction;
|
|
||||||
account?: IAccountData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPostmessage {
|
|
||||||
data: IPostmessageRequestData;
|
|
||||||
origin: string;
|
|
||||||
}
|
|
|
@ -29,7 +29,6 @@ import Spinner from "../elements/Spinner";
|
||||||
import VerificationShowSas from "../verification/VerificationShowSas";
|
import VerificationShowSas from "../verification/VerificationShowSas";
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import DialogButtons from "../elements/DialogButtons";
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
|
|
||||||
const PHASE_START = 0;
|
const PHASE_START = 0;
|
||||||
const PHASE_SHOW_SAS = 1;
|
const PHASE_SHOW_SAS = 1;
|
||||||
|
@ -37,8 +36,9 @@ const PHASE_WAIT_FOR_PARTNER_TO_CONFIRM = 2;
|
||||||
const PHASE_VERIFIED = 3;
|
const PHASE_VERIFIED = 3;
|
||||||
const PHASE_CANCELLED = 4;
|
const PHASE_CANCELLED = 4;
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
verifier: VerificationBase<SasEvent, any>;
|
verifier: VerificationBase<SasEvent, any>;
|
||||||
|
onFinished(verified?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
|
|
@ -19,11 +19,10 @@ import React, { ReactNode, KeyboardEvent } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import DialogButtons from "../elements/DialogButtons";
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps {
|
||||||
top?: ReactNode;
|
top?: ReactNode;
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: ReactNode;
|
description?: ReactNode;
|
||||||
|
@ -32,10 +31,11 @@ interface IProps extends IDialogProps {
|
||||||
hasCloseButton?: boolean;
|
hasCloseButton?: boolean;
|
||||||
fixedWidth?: boolean;
|
fixedWidth?: boolean;
|
||||||
onKeyDown?(event: KeyboardEvent): void;
|
onKeyDown?(event: KeyboardEvent): void;
|
||||||
|
onFinished(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class InfoDialog extends React.Component<IProps> {
|
export default class InfoDialog extends React.Component<IProps> {
|
||||||
public static defaultProps = {
|
public static defaultProps: Partial<IProps> = {
|
||||||
title: "",
|
title: "",
|
||||||
description: "",
|
description: "",
|
||||||
hasCloseButton: false,
|
hasCloseButton: false,
|
||||||
|
|
|
@ -21,9 +21,10 @@ import dis from "../../../dispatcher/dispatcher";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import DialogButtons from "../elements/DialogButtons";
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
|
|
||||||
interface IProps extends IDialogProps {}
|
interface IProps {
|
||||||
|
onFinished(): void;
|
||||||
|
}
|
||||||
|
|
||||||
export default class IntegrationsDisabledDialog extends React.Component<IProps> {
|
export default class IntegrationsDisabledDialog extends React.Component<IProps> {
|
||||||
private onAcknowledgeClick = (): void => {
|
private onAcknowledgeClick = (): void => {
|
||||||
|
|
|
@ -18,11 +18,12 @@ import React from "react";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import DialogButtons from "../elements/DialogButtons";
|
import DialogButtons from "../elements/DialogButtons";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {}
|
interface IProps {
|
||||||
|
onFinished(): void;
|
||||||
|
}
|
||||||
|
|
||||||
export default class IntegrationsImpossibleDialog extends React.Component<IProps> {
|
export default class IntegrationsImpossibleDialog extends React.Component<IProps> {
|
||||||
private onAcknowledgeClick = (): void => {
|
private onAcknowledgeClick = (): void => {
|
||||||
|
|
|
@ -25,7 +25,6 @@ import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import InteractiveAuth, { ERROR_USER_CANCELLED, InteractiveAuthCallback } from "../../structures/InteractiveAuth";
|
import InteractiveAuth, { ERROR_USER_CANCELLED, InteractiveAuthCallback } from "../../structures/InteractiveAuth";
|
||||||
import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
|
import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import { IDialogProps } from "./IDialogProps";
|
|
||||||
|
|
||||||
type DialogAesthetics = Partial<{
|
type DialogAesthetics = Partial<{
|
||||||
[x in AuthType]: {
|
[x in AuthType]: {
|
||||||
|
@ -38,7 +37,7 @@ type DialogAesthetics = Partial<{
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export interface InteractiveAuthDialogProps extends IDialogProps {
|
export interface InteractiveAuthDialogProps {
|
||||||
// matrix client to use for UI auth requests
|
// matrix client to use for UI auth requests
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
|
|
||||||
|
@ -72,6 +71,8 @@ export interface InteractiveAuthDialogProps extends IDialogProps {
|
||||||
//
|
//
|
||||||
// Default is defined in _getDefaultDialogAesthetics()
|
// Default is defined in _getDefaultDialogAesthetics()
|
||||||
aestheticsForStagePhases?: DialogAesthetics;
|
aestheticsForStagePhases?: DialogAesthetics;
|
||||||
|
|
||||||
|
onFinished(success?: boolean, result?: IAuthData | Error | null): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -152,14 +153,14 @@ export default class InteractiveAuthDialog extends React.Component<InteractiveAu
|
||||||
if (!this.state.authError && dialogAesthetics) {
|
if (!this.state.authError && dialogAesthetics) {
|
||||||
if (dialogAesthetics[this.state.uiaStage]) {
|
if (dialogAesthetics[this.state.uiaStage]) {
|
||||||
const aesthetics = dialogAesthetics[this.state.uiaStage][this.state.uiaStagePhase];
|
const aesthetics = dialogAesthetics[this.state.uiaStage][this.state.uiaStagePhase];
|
||||||
if (aesthetics && aesthetics.title) title = aesthetics.title;
|
if (aesthetics?.title) title = aesthetics.title;
|
||||||
if (aesthetics && aesthetics.body) body = aesthetics.body;
|
if (aesthetics?.body) body = aesthetics.body;
|
||||||
if (aesthetics && aesthetics.continueText) continueText = aesthetics.continueText;
|
if (aesthetics?.continueText) continueText = aesthetics.continueText;
|
||||||
if (aesthetics && aesthetics.continueKind) continueKind = aesthetics.continueKind;
|
if (aesthetics?.continueKind) continueKind = aesthetics.continueKind;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let content;
|
let content: JSX.Element;
|
||||||
if (this.state.authError) {
|
if (this.state.authError) {
|
||||||
content = (
|
content = (
|
||||||
<div id="mx_Dialog_content">
|
<div id="mx_Dialog_content">
|
||||||
|
|
|
@ -70,7 +70,7 @@ import {
|
||||||
startDmOnFirstMessage,
|
startDmOnFirstMessage,
|
||||||
ThreepidMember,
|
ThreepidMember,
|
||||||
} from "../../../utils/direct-messages";
|
} from "../../../utils/direct-messages";
|
||||||
import { KIND_CALL_TRANSFER, KIND_DM, KIND_INVITE } from "./InviteDialogTypes";
|
import { InviteKind } from "./InviteDialogTypes";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
|
|
||||||
|
@ -253,32 +253,32 @@ interface BaseProps {
|
||||||
// Takes a boolean which is true if a user / users were invited /
|
// Takes a boolean which is true if a user / users were invited /
|
||||||
// a call transfer was initiated or false if the dialog was cancelled
|
// a call transfer was initiated or false if the dialog was cancelled
|
||||||
// with no action taken.
|
// with no action taken.
|
||||||
onFinished: (success: boolean) => void;
|
onFinished: (success?: boolean) => void;
|
||||||
|
|
||||||
// Initial value to populate the filter with
|
// Initial value to populate the filter with
|
||||||
initialText?: string;
|
initialText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InviteDMProps extends BaseProps {
|
interface InviteDMProps extends BaseProps {
|
||||||
// The kind of invite being performed. Assumed to be KIND_DM if not provided.
|
// The kind of invite being performed. Assumed to be InviteKind.Dm if not provided.
|
||||||
kind?: typeof KIND_DM;
|
kind?: InviteKind.Dm;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InviteRoomProps extends BaseProps {
|
interface InviteRoomProps extends BaseProps {
|
||||||
kind: typeof KIND_INVITE;
|
kind: InviteKind.Invite;
|
||||||
|
|
||||||
// The room ID this dialog is for. Only required for KIND_INVITE.
|
// The room ID this dialog is for. Only required for InviteKind.Invite.
|
||||||
roomId: string;
|
roomId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRoomInvite(props: Props): props is InviteRoomProps {
|
function isRoomInvite(props: Props): props is InviteRoomProps {
|
||||||
return props.kind === KIND_INVITE;
|
return props.kind === InviteKind.Invite;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InviteCallProps extends BaseProps {
|
interface InviteCallProps extends BaseProps {
|
||||||
kind: typeof KIND_CALL_TRANSFER;
|
kind: InviteKind.CallTransfer;
|
||||||
|
|
||||||
// The call to transfer. Only required for KIND_CALL_TRANSFER.
|
// The call to transfer. Only required for InviteKind.CallTransfer.
|
||||||
call: MatrixCall;
|
call: MatrixCall;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,8 +305,8 @@ interface IInviteDialogState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class InviteDialog extends React.PureComponent<Props, IInviteDialogState> {
|
export default class InviteDialog extends React.PureComponent<Props, IInviteDialogState> {
|
||||||
public static defaultProps = {
|
public static defaultProps: Partial<Props> = {
|
||||||
kind: KIND_DM,
|
kind: InviteKind.Dm,
|
||||||
initialText: "",
|
initialText: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -318,10 +318,10 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
public constructor(props: Props) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
if (props.kind === KIND_INVITE && !props.roomId) {
|
if (props.kind === InviteKind.Invite && !props.roomId) {
|
||||||
throw new Error("When using KIND_INVITE a roomId is required for an InviteDialog");
|
throw new Error("When using InviteKind.Invite a roomId is required for an InviteDialog");
|
||||||
} else if (props.kind === KIND_CALL_TRANSFER && !props.call) {
|
} else if (props.kind === InviteKind.CallTransfer && !props.call) {
|
||||||
throw new Error("When using KIND_CALL_TRANSFER a call is required for an InviteDialog");
|
throw new Error("When using InviteKind.CallTransfer a call is required for an InviteDialog");
|
||||||
}
|
}
|
||||||
|
|
||||||
const alreadyInvited = new Set([MatrixClientPeg.get().getUserId()!, SdkConfig.get("welcome_user_id")]);
|
const alreadyInvited = new Set([MatrixClientPeg.get().getUserId()!, SdkConfig.get("welcome_user_id")]);
|
||||||
|
@ -493,7 +493,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
};
|
};
|
||||||
|
|
||||||
private inviteUsers = async (): Promise<void> => {
|
private inviteUsers = async (): Promise<void> => {
|
||||||
if (this.props.kind !== KIND_INVITE) return;
|
if (this.props.kind !== InviteKind.Invite) return;
|
||||||
this.setState({ busy: true });
|
this.setState({ busy: true });
|
||||||
this.convertFilter();
|
this.convertFilter();
|
||||||
const targets = this.convertFilter();
|
const targets = this.convertFilter();
|
||||||
|
@ -528,7 +528,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
};
|
};
|
||||||
|
|
||||||
private transferCall = async (): Promise<void> => {
|
private transferCall = async (): Promise<void> => {
|
||||||
if (this.props.kind !== KIND_CALL_TRANSFER) return;
|
if (this.props.kind !== InviteKind.CallTransfer) return;
|
||||||
if (this.state.currentTabId == TabId.UserDirectory) {
|
if (this.state.currentTabId == TabId.UserDirectory) {
|
||||||
this.convertFilter();
|
this.convertFilter();
|
||||||
const targets = this.convertFilter();
|
const targets = this.convertFilter();
|
||||||
|
@ -657,7 +657,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
this.setState({ tryingIdentityServer: true });
|
this.setState({ tryingIdentityServer: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (term.indexOf("@") > 0 && Email.looksValid(term) && SettingsStore.getValue(UIFeature.IdentityServer)) {
|
if (Email.looksValid(term) && SettingsStore.getValue(UIFeature.IdentityServer)) {
|
||||||
// Start off by suggesting the plain email while we try and resolve it
|
// Start off by suggesting the plain email while we try and resolve it
|
||||||
// to a real account.
|
// to a real account.
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -737,7 +737,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
targets.splice(idx, 1);
|
targets.splice(idx, 1);
|
||||||
} else {
|
} else {
|
||||||
if (this.props.kind === KIND_CALL_TRANSFER && targets.length > 0) {
|
if (this.props.kind === InviteKind.CallTransfer && targets.length > 0) {
|
||||||
targets = [];
|
targets = [];
|
||||||
}
|
}
|
||||||
targets.push(member);
|
targets.push(member);
|
||||||
|
@ -796,7 +796,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (address.indexOf("@") > 0 && Email.looksValid(address)) {
|
if (Email.looksValid(address)) {
|
||||||
toAdd.push(new ThreepidMember(address));
|
toAdd.push(new ThreepidMember(address));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -869,7 +869,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
const lastActive = (m: Result): number | undefined => (kind === "recents" ? m.lastActive : undefined);
|
const lastActive = (m: Result): number | undefined => (kind === "recents" ? m.lastActive : undefined);
|
||||||
let sectionName = kind === "recents" ? _t("Recent Conversations") : _t("Suggestions");
|
let sectionName = kind === "recents" ? _t("Recent Conversations") : _t("Suggestions");
|
||||||
|
|
||||||
if (this.props.kind === KIND_INVITE) {
|
if (this.props.kind === InviteKind.Invite) {
|
||||||
sectionName = kind === "recents" ? _t("Recently Direct Messaged") : _t("Suggestions");
|
sectionName = kind === "recents" ? _t("Recently Direct Messaged") : _t("Suggestions");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -959,7 +959,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
|
|
||||||
private renderEditor(): JSX.Element {
|
private renderEditor(): JSX.Element {
|
||||||
const hasPlaceholder =
|
const hasPlaceholder =
|
||||||
this.props.kind == KIND_CALL_TRANSFER &&
|
this.props.kind == InviteKind.CallTransfer &&
|
||||||
this.state.targets.length === 0 &&
|
this.state.targets.length === 0 &&
|
||||||
this.state.filterText.length === 0;
|
this.state.filterText.length === 0;
|
||||||
const targets = this.state.targets.map((t) => (
|
const targets = this.state.targets.map((t) => (
|
||||||
|
@ -974,7 +974,9 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
ref={this.editorRef}
|
ref={this.editorRef}
|
||||||
onPaste={this.onPaste}
|
onPaste={this.onPaste}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
disabled={this.state.busy || (this.props.kind == KIND_CALL_TRANSFER && this.state.targets.length > 0)}
|
disabled={
|
||||||
|
this.state.busy || (this.props.kind == InviteKind.CallTransfer && this.state.targets.length > 0)
|
||||||
|
}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder={hasPlaceholder ? _t("Search") : undefined}
|
placeholder={hasPlaceholder ? _t("Search") : undefined}
|
||||||
data-testid="invite-dialog-input"
|
data-testid="invite-dialog-input"
|
||||||
|
@ -1085,7 +1087,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
|
|
||||||
private get screenName(): ScreenName | undefined {
|
private get screenName(): ScreenName | undefined {
|
||||||
switch (this.props.kind) {
|
switch (this.props.kind) {
|
||||||
case KIND_DM:
|
case InviteKind.Dm:
|
||||||
return "StartChat";
|
return "StartChat";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1112,7 +1114,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const userId = cli.getUserId()!;
|
const userId = cli.getUserId()!;
|
||||||
if (this.props.kind === KIND_DM) {
|
if (this.props.kind === InviteKind.Dm) {
|
||||||
title = _t("Direct Messages");
|
title = _t("Direct Messages");
|
||||||
|
|
||||||
if (identityServersEnabled) {
|
if (identityServersEnabled) {
|
||||||
|
@ -1164,7 +1166,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
</CopyableText>
|
</CopyableText>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (this.props.kind === KIND_INVITE) {
|
} else if (this.props.kind === InviteKind.Invite) {
|
||||||
const roomId = this.props.roomId;
|
const roomId = this.props.roomId;
|
||||||
const room = MatrixClientPeg.get()?.getRoom(roomId);
|
const room = MatrixClientPeg.get()?.getRoom(roomId);
|
||||||
const isSpace = room?.isSpaceRoom();
|
const isSpace = room?.isSpaceRoom();
|
||||||
|
@ -1235,7 +1237,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (this.props.kind === KIND_CALL_TRANSFER) {
|
} else if (this.props.kind === InviteKind.CallTransfer) {
|
||||||
title = _t("Transfer");
|
title = _t("Transfer");
|
||||||
|
|
||||||
consultConnectSection = (
|
consultConnectSection = (
|
||||||
|
@ -1264,7 +1266,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
}
|
}
|
||||||
|
|
||||||
const goButton =
|
const goButton =
|
||||||
this.props.kind == KIND_CALL_TRANSFER ? null : (
|
this.props.kind == InviteKind.CallTransfer ? null : (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
kind="primary"
|
kind="primary"
|
||||||
onClick={goButtonFn}
|
onClick={goButtonFn}
|
||||||
|
@ -1298,7 +1300,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
);
|
);
|
||||||
|
|
||||||
let dialogContent;
|
let dialogContent;
|
||||||
if (this.props.kind === KIND_CALL_TRANSFER) {
|
if (this.props.kind === InviteKind.CallTransfer) {
|
||||||
const tabs: Tab[] = [];
|
const tabs: Tab[] = [];
|
||||||
tabs.push(
|
tabs.push(
|
||||||
new Tab(TabId.UserDirectory, _td("User Directory"), "mx_InviteDialog_userDirectoryIcon", usersSection),
|
new Tab(TabId.UserDirectory, _td("User Directory"), "mx_InviteDialog_userDirectoryIcon", usersSection),
|
||||||
|
@ -1363,8 +1365,8 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
return (
|
return (
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
className={classNames({
|
className={classNames({
|
||||||
mx_InviteDialog_transfer: this.props.kind === KIND_CALL_TRANSFER,
|
mx_InviteDialog_transfer: this.props.kind === InviteKind.CallTransfer,
|
||||||
mx_InviteDialog_other: this.props.kind !== KIND_CALL_TRANSFER,
|
mx_InviteDialog_other: this.props.kind !== InviteKind.CallTransfer,
|
||||||
mx_InviteDialog_hasFooter: !!footer,
|
mx_InviteDialog_hasFooter: !!footer,
|
||||||
})}
|
})}
|
||||||
hasCancel={true}
|
hasCancel={true}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue