diff --git a/.eslintrc.js b/.eslintrc.js
index bc2a142c2d..99695b7a03 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -22,6 +22,8 @@ module.exports = {
"files": ["src/**/*.{ts,tsx}"],
"extends": ["matrix-org/ts"],
"rules": {
+ // We're okay being explicit at the moment
+ "@typescript-eslint/no-empty-interface": "off",
// We disable this while we're transitioning
"@typescript-eslint/no-explicit-any": "off",
// We'd rather not do this but we do
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c460b4f81..43a1494497 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,132 @@
+Changes in [3.13.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.13.1) (2021-02-04)
+=====================================================================================================
+[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.13.0...v3.13.1)
+
+ * [Release] Fix z-index of stickerpicker
+ [\#5618](https://github.com/matrix-org/matrix-react-sdk/pull/5618)
+
+Changes in [3.13.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.13.0) (2021-02-03)
+=====================================================================================================
+[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.13.0-rc.1...v3.13.0)
+
+ * Upgrade to JS SDK 9.6.0
+ * [Release] Fix flair height after accent changes
+ [\#5612](https://github.com/matrix-org/matrix-react-sdk/pull/5612)
+ * [Release] Iterate Social Logins work around edge cases and branding
+ [\#5610](https://github.com/matrix-org/matrix-react-sdk/pull/5610)
+ * [Release] Lock widget room ID when added
+ [\#5608](https://github.com/matrix-org/matrix-react-sdk/pull/5608)
+ * [Release] Better errors for SSO failures
+ [\#5606](https://github.com/matrix-org/matrix-react-sdk/pull/5606)
+ * [Release] Fix RoomView re-mounting breaking peeking
+ [\#5603](https://github.com/matrix-org/matrix-react-sdk/pull/5603)
+
+Changes in [3.13.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.13.0-rc.1) (2021-01-29)
+===============================================================================================================
+[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.12.1...v3.13.0-rc.1)
+
+ * Upgrade to JS SDK 9.6.0-rc.1
+ * Translations update from Weblate
+ [\#5597](https://github.com/matrix-org/matrix-react-sdk/pull/5597)
+ * Support managed hybrid widgets from config
+ [\#5596](https://github.com/matrix-org/matrix-react-sdk/pull/5596)
+ * Add managed hybrid call widgets when supported
+ [\#5594](https://github.com/matrix-org/matrix-react-sdk/pull/5594)
+ * Tweak mobile guide toast copy
+ [\#5595](https://github.com/matrix-org/matrix-react-sdk/pull/5595)
+ * Improve SSO auth flow
+ [\#5578](https://github.com/matrix-org/matrix-react-sdk/pull/5578)
+ * Add optional mobile guide toast
+ [\#5586](https://github.com/matrix-org/matrix-react-sdk/pull/5586)
+ * Fix invisible text after logging out in the dark theme
+ [\#5588](https://github.com/matrix-org/matrix-react-sdk/pull/5588)
+ * Fix escape for cancelling replies
+ [\#5591](https://github.com/matrix-org/matrix-react-sdk/pull/5591)
+ * Update widget-api to beta.12
+ [\#5589](https://github.com/matrix-org/matrix-react-sdk/pull/5589)
+ * Add commands for DM conversion
+ [\#5540](https://github.com/matrix-org/matrix-react-sdk/pull/5540)
+ * Run a UI refresh over the OIDC Exchange confirmation dialog
+ [\#5580](https://github.com/matrix-org/matrix-react-sdk/pull/5580)
+ * Allow stickerpickers the legacy "visibility" capability
+ [\#5581](https://github.com/matrix-org/matrix-react-sdk/pull/5581)
+ * Hide local video if it is muted
+ [\#5529](https://github.com/matrix-org/matrix-react-sdk/pull/5529)
+ * Don't use name width in reply thread for IRC layout
+ [\#5518](https://github.com/matrix-org/matrix-react-sdk/pull/5518)
+ * Update code_style.md
+ [\#5554](https://github.com/matrix-org/matrix-react-sdk/pull/5554)
+ * Fix Czech capital letters like ŠČŘ...
+ [\#5569](https://github.com/matrix-org/matrix-react-sdk/pull/5569)
+ * Add optional search shortcut
+ [\#5548](https://github.com/matrix-org/matrix-react-sdk/pull/5548)
+ * Fix Sudden 'find a room' UI shows up when the only room moves to favourites
+ [\#5584](https://github.com/matrix-org/matrix-react-sdk/pull/5584)
+ * Increase PersistedElement's z-index
+ [\#5568](https://github.com/matrix-org/matrix-react-sdk/pull/5568)
+ * Remove check that prevents Jitsi widgets from being unpinned
+ [\#5582](https://github.com/matrix-org/matrix-react-sdk/pull/5582)
+ * Fix Jitsi widgets causing localized tile crashes
+ [\#5583](https://github.com/matrix-org/matrix-react-sdk/pull/5583)
+ * Log candidates for calls
+ [\#5573](https://github.com/matrix-org/matrix-react-sdk/pull/5573)
+ * Upgrade deps 2021-01
+ [\#5579](https://github.com/matrix-org/matrix-react-sdk/pull/5579)
+ * Fix "Continuing without email" dialog bug
+ [\#5566](https://github.com/matrix-org/matrix-react-sdk/pull/5566)
+ * Require registration for verification actions
+ [\#5574](https://github.com/matrix-org/matrix-react-sdk/pull/5574)
+ * Don't play the hangup sound when the call is answered from elsewhere
+ [\#5572](https://github.com/matrix-org/matrix-react-sdk/pull/5572)
+ * Move to newer base image for end-to-end tests
+ [\#5570](https://github.com/matrix-org/matrix-react-sdk/pull/5570)
+ * Update widgets in the room upon join
+ [\#5564](https://github.com/matrix-org/matrix-react-sdk/pull/5564)
+ * Update AuxPanel and related buttons when widgets change or on reload
+ [\#5563](https://github.com/matrix-org/matrix-react-sdk/pull/5563)
+ * Add VoIP user mapper
+ [\#5560](https://github.com/matrix-org/matrix-react-sdk/pull/5560)
+ * Improve styling of SSO Buttons for multiple IdPs
+ [\#5558](https://github.com/matrix-org/matrix-react-sdk/pull/5558)
+ * Fixes for the general tab in the room dialog
+ [\#5522](https://github.com/matrix-org/matrix-react-sdk/pull/5522)
+ * fix issue 16226 to allow switching back to default HS.
+ [\#5561](https://github.com/matrix-org/matrix-react-sdk/pull/5561)
+ * Support room-defined widget layouts
+ [\#5553](https://github.com/matrix-org/matrix-react-sdk/pull/5553)
+ * Change a bunch of strings from Recovery Key/Phrase to Security Key/Phrase
+ [\#5533](https://github.com/matrix-org/matrix-react-sdk/pull/5533)
+ * Give a bigger target area to AppsDrawer vertical resizer
+ [\#5557](https://github.com/matrix-org/matrix-react-sdk/pull/5557)
+ * Fix minimized left panel avatar alignment
+ [\#5493](https://github.com/matrix-org/matrix-react-sdk/pull/5493)
+ * Ensure component index has been written before renaming
+ [\#5556](https://github.com/matrix-org/matrix-react-sdk/pull/5556)
+ * Fixed continue button while selecting home-server
+ [\#5552](https://github.com/matrix-org/matrix-react-sdk/pull/5552)
+ * Wire up MSC2931 widget navigation
+ [\#5527](https://github.com/matrix-org/matrix-react-sdk/pull/5527)
+ * Various fixes for Bridge Info page (MSC2346)
+ [\#5454](https://github.com/matrix-org/matrix-react-sdk/pull/5454)
+ * Use room-specific listeners for message preview and community prototype
+ [\#5547](https://github.com/matrix-org/matrix-react-sdk/pull/5547)
+ * Fix some misc. React warnings when viewing timeline
+ [\#5546](https://github.com/matrix-org/matrix-react-sdk/pull/5546)
+ * Use device storage for allowed widgets if account data not supported
+ [\#5544](https://github.com/matrix-org/matrix-react-sdk/pull/5544)
+ * Fix incoming call box on dark theme
+ [\#5542](https://github.com/matrix-org/matrix-react-sdk/pull/5542)
+ * Convert DMRoomMap to typescript
+ [\#5541](https://github.com/matrix-org/matrix-react-sdk/pull/5541)
+ * Add in-call dialpad for DTMF sending
+ [\#5532](https://github.com/matrix-org/matrix-react-sdk/pull/5532)
+
+Changes in [3.12.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.12.1) (2021-01-26)
+=====================================================================================================
+[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.12.0...v3.12.1)
+
+ * Upgrade to JS SDK 9.5.1
+
Changes in [3.12.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.12.0) (2021-01-18)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.12.0-rc.1...v3.12.0)
diff --git a/code_style.md b/code_style.md
index fe04d2cc3d..5747540a76 100644
--- a/code_style.md
+++ b/code_style.md
@@ -35,12 +35,6 @@ General Style
- lowerCamelCase for functions and variables.
- Single line ternary operators are fine.
- UPPER_SNAKE_CASE for constants
-- Single quotes for strings by default, for consistency with most JavaScript styles:
-
- ```javascript
- "bad" // Bad
- 'good' // Good
- ```
- Use parentheses or `` ` `` instead of `\` for line continuation where ever possible
- Open braces on the same line (consistent with Node):
@@ -162,7 +156,14 @@ ECMAScript
- Be careful mixing arrow functions and regular functions, eg. if one function in a promise chain is an
arrow function, they probably all should be.
- Apart from that, newer ES features should be used whenever the author deems them to be appropriate.
-- Flow annotations are welcome and encouraged.
+
+TypeScript
+----------
+- TypeScript is preferred over the use of JavaScript
+- It's desirable to convert existing JavaScript files to TypeScript. TypeScript conversions should be done in small
+ chunks without functional changes to ease the review process.
+- Use full type definitions for function parameters and return values.
+- Avoid `any` types and `any` casts
React
-----
@@ -201,6 +202,8 @@ React
this.state = { counter: 0 };
}
```
+- Prefer class components over function components and hooks (not a strict rule though)
+
- Think about whether your component really needs state: are you duplicating
information in component state that could be derived from the model?
diff --git a/docs/widget-layouts.md b/docs/widget-layouts.md
new file mode 100644
index 0000000000..e7f72e2001
--- /dev/null
+++ b/docs/widget-layouts.md
@@ -0,0 +1,60 @@
+# Widget layout support
+
+Rooms can have a default widget layout to auto-pin certain widgets, make the container different
+sizes, etc. These are defined through the `io.element.widgets.layout` state event (empty state key).
+
+Full example content:
+```json5
+{
+ "widgets": {
+ "first-widget-id": {
+ "container": "top",
+ "index": 0,
+ "width": 60,
+ "height": 40
+ },
+ "second-widget-id": {
+ "container": "right"
+ }
+ }
+}
+```
+
+As shown, there are two containers possible for widgets. These containers have different behaviour
+and interpret the other options differently.
+
+## `top` container
+
+This is the "App Drawer" or any pinned widgets in a room. This is by far the most versatile container
+though does introduce potential usability issues upon members of the room (widgets take up space and
+therefore fewer messages can be shown).
+
+The `index` for a widget determines which order the widgets show up in from left to right. Widgets
+without an `index` will show up as the rightmost widgets. Tiebreaks (same `index` or multiple defined
+without an `index`) are resolved by comparing widget IDs. A maximum of 3 widgets can be in the top
+container - any which exceed this will be ignored (placed into the `right` container). Smaller numbers
+represent leftmost widgets.
+
+The `width` is relative width within the container in percentage points. This will be clamped to a
+range of 0-100 (inclusive). The widgets will attempt to scale to relative proportions when more than
+100% space is allocated. For example, if 3 widgets are defined at 40% width each then the client will
+attempt to show them at 33% width each.
+
+Note that the client may impose minimum widths on the widgets, such as a 10% minimum to avoid pinning
+hidden widgets. In general, widgets defined in the 30-70% range each will be free of these restrictions.
+
+The `height` is not in fact applied per-widget but is recorded per-widget for potential future
+capabilities in future containers. The top container will take the tallest `height` and use that for
+the height of the whole container, and thus all widgets in that container. The `height` is relative
+to the container, like with `width`, meaning that 100% will consume as much space as the client is
+willing to sacrifice to the widget container. Like with `width`, the client may impose minimums to avoid
+the container being uselessly small. Heights in the 30-100% range are generally acceptable. The height
+is also clamped to be within 0-100, inclusive.
+
+## `right` container
+
+This is the default container and has no special configuration. Widgets which overflow from the top
+container will be put in this container instead. Putting a widget in the right container does not
+automatically show it - it only mentions that widgets should not be in another container.
+
+The behaviour of this container may change in the future.
diff --git a/package.json b/package.json
index 1316b26030..2263c7de32 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
- "version": "3.12.0",
+ "version": "3.13.1",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@@ -54,48 +54,47 @@
"test:e2e": "./test/end-to-end-tests/run.sh --app-url http://localhost:8080"
},
"dependencies": {
- "@babel/runtime": "^7.10.5",
- "await-lock": "^2.0.1",
- "blueimp-canvas-to-blob": "^3.27.0",
+ "@babel/runtime": "^7.12.5",
+ "await-lock": "^2.1.0",
+ "blueimp-canvas-to-blob": "^3.28.0",
"browser-encrypt-attachment": "^0.3.0",
"browser-request": "^0.3.3",
- "cheerio": "^1.0.0-rc.3",
+ "cheerio": "^1.0.0-rc.5",
"classnames": "^2.2.6",
- "commonmark": "^0.29.1",
+ "commonmark": "^0.29.3",
"counterpart": "^0.18.6",
- "diff-dom": "^4.1.6",
+ "diff-dom": "^4.2.2",
"diff-match-patch": "^1.0.5",
- "emojibase-data": "^5.0.1",
- "emojibase-regex": "^4.0.1",
+ "emojibase-data": "^5.1.1",
+ "emojibase-regex": "^4.1.1",
"escape-html": "^1.0.3",
- "file-saver": "^1.3.8",
- "filesize": "3.6.1",
+ "file-saver": "^2.0.5",
+ "filesize": "6.1.0",
"flux": "2.1.1",
- "focus-visible": "^5.1.0",
- "fuse.js": "^2.7.4",
+ "focus-visible": "^5.2.0",
"gfm.css": "^1.1.2",
"glob-to-regexp": "^0.4.1",
- "highlight.js": "^10.1.2",
- "html-entities": "^1.3.1",
- "is-ip": "^2.0.0",
+ "highlight.js": "^10.5.0",
+ "html-entities": "^1.4.0",
+ "is-ip": "^3.1.0",
"katex": "^0.12.0",
"linkifyjs": "^2.1.9",
- "lodash": "^4.17.19",
+ "lodash": "^4.17.20",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
- "matrix-widget-api": "0.1.0-beta.11",
+ "matrix-widget-api": "^0.1.0-beta.13",
"minimist": "^1.2.5",
- "pako": "^1.0.11",
- "parse5": "^5.1.1",
+ "pako": "^2.0.3",
+ "parse5": "^6.0.1",
"png-chunks-extract": "^1.0.0",
- "project-name-generator": "^2.1.7",
+ "project-name-generator": "^2.1.9",
"prop-types": "^15.7.2",
"qrcode": "^1.4.4",
- "qs": "^6.9.4",
- "re-resizable": "^6.5.4",
- "react": "^16.13.1",
+ "qs": "^6.9.6",
+ "re-resizable": "^6.9.0",
+ "react": "^16.14.0",
"react-beautiful-dnd": "^4.0.1",
- "react-dom": "^16.13.1",
- "react-focus-lock": "^2.4.1",
+ "react-dom": "^16.14.0",
+ "react-focus-lock": "^2.5.0",
"react-transition-group": "^4.4.1",
"resize-observer-polyfill": "^1.5.1",
"rfc4648": "^1.4.0",
@@ -108,68 +107,71 @@
"zxcvbn": "^4.4.2"
},
"devDependencies": {
- "@babel/cli": "^7.10.5",
- "@babel/core": "^7.10.5",
- "@babel/parser": "^7.11.0",
- "@babel/plugin-proposal-class-properties": "^7.10.4",
- "@babel/plugin-proposal-decorators": "^7.10.5",
- "@babel/plugin-proposal-export-default-from": "^7.10.4",
- "@babel/plugin-proposal-numeric-separator": "^7.10.4",
- "@babel/plugin-proposal-object-rest-spread": "^7.10.4",
- "@babel/plugin-transform-flow-comments": "^7.10.4",
- "@babel/plugin-transform-runtime": "^7.10.5",
- "@babel/preset-env": "^7.10.4",
- "@babel/preset-flow": "^7.10.4",
- "@babel/preset-react": "^7.10.4",
- "@babel/preset-typescript": "^7.10.4",
- "@babel/register": "^7.10.5",
- "@babel/traverse": "^7.11.0",
- "@peculiar/webcrypto": "^1.1.3",
- "@types/classnames": "^2.2.10",
+ "@babel/cli": "^7.12.10",
+ "@babel/core": "^7.12.10",
+ "@babel/parser": "^7.12.11",
+ "@babel/plugin-proposal-class-properties": "^7.12.1",
+ "@babel/plugin-proposal-decorators": "^7.12.12",
+ "@babel/plugin-proposal-export-default-from": "^7.12.1",
+ "@babel/plugin-proposal-numeric-separator": "^7.12.7",
+ "@babel/plugin-proposal-object-rest-spread": "^7.12.1",
+ "@babel/plugin-transform-flow-comments": "^7.12.1",
+ "@babel/plugin-transform-runtime": "^7.12.10",
+ "@babel/preset-env": "^7.12.11",
+ "@babel/preset-flow": "^7.12.1",
+ "@babel/preset-react": "^7.12.10",
+ "@babel/preset-typescript": "^7.12.7",
+ "@babel/register": "^7.12.10",
+ "@babel/traverse": "^7.12.12",
+ "@peculiar/webcrypto": "^1.1.4",
+ "@sinonjs/fake-timers": "^7.0.2",
+ "@types/classnames": "^2.2.11",
"@types/counterpart": "^0.18.1",
"@types/flux": "^3.1.9",
"@types/jest": "^26.0.20",
"@types/linkifyjs": "^2.1.3",
- "@types/lodash": "^4.14.158",
+ "@types/lodash": "^4.14.168",
"@types/modernizr": "^3.5.3",
- "@types/node": "^12.12.51",
+ "@types/node": "^14.14.22",
"@types/pako": "^1.0.1",
- "@types/qrcode": "^1.3.4",
+ "@types/qrcode": "^1.3.5",
"@types/react": "^16.9",
- "@types/react-dom": "^16.9.8",
+ "@types/react-dom": "^16.9.10",
"@types/react-transition-group": "^4.4.0",
- "@types/sanitize-html": "^1.23.3",
+ "@types/sanitize-html": "^1.27.0",
"@types/zxcvbn": "^4.4.0",
- "@typescript-eslint/eslint-plugin": "^3.7.0",
- "@typescript-eslint/parser": "^3.7.0",
+ "@typescript-eslint/eslint-plugin": "^4.14.0",
+ "@typescript-eslint/parser": "^4.14.0",
"babel-eslint": "^10.1.0",
- "babel-jest": "^24.9.0",
- "chokidar": "^3.4.1",
- "concurrently": "^4.1.2",
+ "babel-jest": "^26.6.3",
+ "chokidar": "^3.5.1",
+ "concurrently": "^5.3.0",
"enzyme": "^3.11.0",
- "enzyme-adapter-react-16": "^1.15.2",
- "eslint": "7.5.0",
- "eslint-config-matrix-org": "^0.1.2",
+ "enzyme-adapter-react-16": "^1.15.6",
+ "eslint": "7.18.0",
+ "eslint-config-matrix-org": "^0.2.0",
"eslint-plugin-babel": "^5.3.1",
- "eslint-plugin-flowtype": "^2.50.3",
- "eslint-plugin-react": "^7.20.3",
- "eslint-plugin-react-hooks": "^2.5.1",
- "glob": "^5.0.15",
- "jest": "^26.5.2",
+ "eslint-plugin-flowtype": "^5.2.0",
+ "eslint-plugin-react": "^7.22.0",
+ "eslint-plugin-react-hooks": "^4.2.0",
+ "glob": "^7.1.6",
+ "jest": "^26.6.3",
"jest-canvas-mock": "^2.3.0",
"jest-environment-jsdom-sixteen": "^1.0.3",
- "lolex": "^5.1.2",
"matrix-mock-request": "^1.2.3",
"matrix-react-test-utils": "^0.2.2",
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
- "react-test-renderer": "^16.13.1",
- "rimraf": "^2.7.1",
- "stylelint": "^9.10.1",
- "stylelint-config-standard": "^18.3.0",
+ "react-test-renderer": "^16.14.0",
+ "rimraf": "^3.0.2",
+ "stylelint": "^13.9.0",
+ "stylelint-config-standard": "^20.0.0",
"stylelint-scss": "^3.18.0",
- "typescript": "^3.9.7",
+ "typescript": "^4.1.3",
"walk": "^2.3.14"
},
+ "resolutions": {
+ "**/@types/react": "^16.14"
+ },
"jest": {
"testEnvironment": "./__test-utils__/environment.js",
"testMatch": [
diff --git a/res/css/_common.scss b/res/css/_common.scss
index 87336a1c03..6e9d252659 100644
--- a/res/css/_common.scss
+++ b/res/css/_common.scss
@@ -21,6 +21,11 @@ limitations under the License.
$hover-transition: 0.08s cubic-bezier(.46, .03, .52, .96); // quadratic
+$EventTile_e2e_state_indicator_width: 4px;
+
+$MessageTimestamp_width: 46px; /* 8 + 30 (avatar) + 8 */
+$MessageTimestamp_width_hover: calc($MessageTimestamp_width - 2 * $EventTile_e2e_state_indicator_width);
+
:root {
font-size: 10px;
}
diff --git a/res/css/_components.scss b/res/css/_components.scss
index a6e1f81583..3fc7468539 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -106,6 +106,7 @@
@import "./views/elements/_AddressTile.scss";
@import "./views/elements/_DesktopBuildsNotice.scss";
@import "./views/elements/_DirectorySearchBox.scss";
+@import "./views/elements/_DesktopCapturerSourcePicker.scss";
@import "./views/elements/_Dropdown.scss";
@import "./views/elements/_EditableItemList.scss";
@import "./views/elements/_ErrorBoundary.scss";
diff --git a/res/css/structures/_RoomDirectory.scss b/res/css/structures/_RoomDirectory.scss
index 29e6fecd34..89cb21b7a6 100644
--- a/res/css/structures/_RoomDirectory.scss
+++ b/res/css/structures/_RoomDirectory.scss
@@ -64,28 +64,23 @@ limitations under the License.
}
.mx_RoomDirectory_table {
- font-size: $font-12px;
color: $primary-fg-color;
- width: 100%;
+ display: grid;
+ font-size: $font-12px;
+ grid-template-columns: max-content auto max-content max-content max-content;
+ row-gap: 24px;
text-align: left;
- table-layout: fixed;
+ width: 100%;
}
.mx_RoomDirectory_roomAvatar {
- width: 32px;
- padding-right: 14px;
- vertical-align: top;
-}
-
-.mx_RoomDirectory_roomDescription {
- padding-bottom: 16px;
+ padding: 2px 14px 0 0;
}
.mx_RoomDirectory_roomMemberCount {
+ align-self: center;
color: $light-fg-color;
- width: 60px;
- padding: 0 10px;
- text-align: center;
+ padding: 3px 10px 0;
&::before {
background-color: $light-fg-color;
@@ -105,8 +100,7 @@ limitations under the License.
}
.mx_RoomDirectory_join, .mx_RoomDirectory_preview {
- width: 80px;
- text-align: center;
+ align-self: center;
white-space: nowrap;
}
diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss
index 572c7166d2..36bf96359b 100644
--- a/res/css/structures/_RoomView.scss
+++ b/res/css/structures/_RoomView.scss
@@ -219,7 +219,7 @@ hr.mx_RoomView_myReadMarker {
position: relative;
top: -1px;
z-index: 1;
- transition: width 400ms easeInSine 1s, opacity 400ms easeInSine 1s;
+ transition: width 400ms easeinsine 1s, opacity 400ms easeinsine 1s;
width: 99%;
opacity: 1;
}
diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss
index 8f0c758e7a..90dca32e48 100644
--- a/res/css/views/auth/_AuthBody.scss
+++ b/res/css/views/auth/_AuthBody.scss
@@ -34,7 +34,7 @@ limitations under the License.
h3 {
font-size: $font-14px;
font-weight: 600;
- color: $authpage-primary-color;
+ color: $authpage-secondary-color;
}
h3.mx_AuthBody_centered {
diff --git a/res/css/views/auth/_AuthHeader.scss b/res/css/views/auth/_AuthHeader.scss
index b1372affee..13d5195160 100644
--- a/res/css/views/auth/_AuthHeader.scss
+++ b/res/css/views/auth/_AuthHeader.scss
@@ -18,7 +18,7 @@ limitations under the License.
display: flex;
flex-direction: column;
width: 206px;
- padding: 25px 40px;
+ padding: 25px 25px;
box-sizing: border-box;
}
diff --git a/res/css/views/auth/_AuthHeaderLogo.scss b/res/css/views/auth/_AuthHeaderLogo.scss
index 917dcabf67..86f0313b68 100644
--- a/res/css/views/auth/_AuthHeaderLogo.scss
+++ b/res/css/views/auth/_AuthHeaderLogo.scss
@@ -17,7 +17,7 @@ limitations under the License.
.mx_AuthHeaderLogo {
margin-top: 15px;
flex: 1;
- padding: 0 10px;
+ padding: 0 25px;
}
.mx_AuthHeaderLogo img {
diff --git a/res/css/views/auth/_InteractiveAuthEntryComponents.scss b/res/css/views/auth/_InteractiveAuthEntryComponents.scss
index 5df73b139f..ffaad3cd7a 100644
--- a/res/css/views/auth/_InteractiveAuthEntryComponents.scss
+++ b/res/css/views/auth/_InteractiveAuthEntryComponents.scss
@@ -83,7 +83,10 @@ limitations under the License.
}
.mx_InteractiveAuthEntryComponents_termsPolicy {
- display: block;
+ display: flex;
+ flex-direction: row;
+ justify-content: start;
+ align-items: center;
}
.mx_InteractiveAuthEntryComponents_passwordSection {
diff --git a/res/css/views/auth/_LanguageSelector.scss b/res/css/views/auth/_LanguageSelector.scss
index 781561f876..885ee7f30d 100644
--- a/res/css/views/auth/_LanguageSelector.scss
+++ b/res/css/views/auth/_LanguageSelector.scss
@@ -23,6 +23,7 @@ limitations under the License.
font-size: $font-14px;
font-weight: 600;
color: $authpage-lang-color;
+ width: auto;
}
.mx_AuthBody_language .mx_Dropdown_arrow {
diff --git a/res/css/views/auth/_Welcome.scss b/res/css/views/auth/_Welcome.scss
index f0e2b3de33..894174d6e2 100644
--- a/res/css/views/auth/_Welcome.scss
+++ b/res/css/views/auth/_Welcome.scss
@@ -18,7 +18,6 @@ limitations under the License.
display: flex;
flex-direction: column;
align-items: center;
-
&.mx_WelcomePage_registrationDisabled {
.mx_ButtonCreateAccount {
display: none;
@@ -27,6 +26,6 @@ limitations under the License.
}
.mx_Welcome .mx_AuthBody_language {
- width: 120px;
+ width: 160px;
margin-bottom: 10px;
}
diff --git a/res/css/views/elements/_DesktopCapturerSourcePicker.scss b/res/css/views/elements/_DesktopCapturerSourcePicker.scss
new file mode 100644
index 0000000000..69dde5925e
--- /dev/null
+++ b/res/css/views/elements/_DesktopCapturerSourcePicker.scss
@@ -0,0 +1,72 @@
+/*
+Copyright 2021 Šimon Brandner
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_desktopCapturerSourcePicker {
+ overflow: hidden;
+}
+
+.mx_desktopCapturerSourcePicker_tabLabels {
+ display: flex;
+ padding: 0 0 8px 0;
+}
+
+.mx_desktopCapturerSourcePicker_tabLabel,
+.mx_desktopCapturerSourcePicker_tabLabel_selected {
+ width: 100%;
+ text-align: center;
+ border-radius: 8px;
+ padding: 8px 0;
+ font-size: $font-13px;
+}
+
+.mx_desktopCapturerSourcePicker_tabLabel_selected {
+ background-color: $tab-label-active-bg-color;
+ color: $tab-label-active-fg-color;
+}
+
+.mx_desktopCapturerSourcePicker_panel {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: flex-start;
+ height: 500px;
+ overflow: overlay;
+}
+
+.mx_desktopCapturerSourcePicker_stream_button {
+ display: flex;
+ flex-direction: column;
+ margin: 8px;
+ border-radius: 4px;
+}
+
+.mx_desktopCapturerSourcePicker_stream_button:hover,
+.mx_desktopCapturerSourcePicker_stream_button:focus {
+ background: $roomtile-selected-bg-color;
+}
+
+.mx_desktopCapturerSourcePicker_stream_thumbnail {
+ margin: 4px;
+ width: 312px;
+}
+
+.mx_desktopCapturerSourcePicker_stream_name {
+ margin: 0 4px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ width: 312px;
+}
diff --git a/res/css/views/elements/_SSOButtons.scss b/res/css/views/elements/_SSOButtons.scss
index f762468c7f..e02816780f 100644
--- a/res/css/views/elements/_SSOButtons.scss
+++ b/res/css/views/elements/_SSOButtons.scss
@@ -16,13 +16,26 @@ limitations under the License.
.mx_SSOButtons {
display: flex;
+ flex-wrap: wrap;
justify-content: center;
+ .mx_SSOButtons_row {
+ & + .mx_SSOButtons_row {
+ margin-top: 16px;
+ }
+ }
+
.mx_SSOButton {
position: relative;
width: 100%;
- padding-left: 32px;
- padding-right: 32px;
+ padding: 7px 32px;
+ text-align: center;
+ border-radius: 8px;
+ display: inline-block;
+ font-size: $font-14px;
+ font-weight: $font-semi-bold;
+ border: 1px solid $input-border-color;
+ color: $primary-fg-color;
> img {
object-fit: contain;
@@ -32,10 +45,22 @@ limitations under the License.
}
}
+ .mx_SSOButton_default {
+ color: $button-primary-bg-color;
+ background-color: $button-secondary-bg-color;
+ border-color: $button-primary-bg-color;
+ }
+ .mx_SSOButton_default.mx_SSOButton_primary {
+ color: $button-primary-fg-color;
+ background-color: $button-primary-bg-color;
+ }
+
.mx_SSOButton_mini {
box-sizing: border-box;
width: 50px; // 48px + 1px border on all sides
height: 50px; // 48px + 1px border on all sides
+ min-width: 50px; // prevent crushing by the flexbox
+ padding: 12px;
> img {
left: 12px;
@@ -43,7 +68,7 @@ limitations under the License.
}
& + .mx_SSOButton_mini {
- margin-left: 24px;
+ margin-left: 16px;
}
}
}
diff --git a/res/css/views/elements/_ServerPicker.scss b/res/css/views/elements/_ServerPicker.scss
index ae1e445a9f..188eb5d655 100644
--- a/res/css/views/elements/_ServerPicker.scss
+++ b/res/css/views/elements/_ServerPicker.scss
@@ -59,7 +59,7 @@ limitations under the License.
}
.mx_ServerPicker_server {
- color: $primary-fg-color;
+ color: $authpage-primary-color;
grid-column: 1;
grid-row: 2;
margin-bottom: 16px;
diff --git a/res/css/views/messages/_RedactedBody.scss b/res/css/views/messages/_RedactedBody.scss
index e4ab0c0835..600ac0c6b7 100644
--- a/res/css/views/messages/_RedactedBody.scss
+++ b/res/css/views/messages/_RedactedBody.scss
@@ -30,7 +30,7 @@ limitations under the License.
mask-size: contain;
content: '';
position: absolute;
- top: 2px;
+ top: 1px;
left: 0;
}
}
diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index 5a69fa4b1a..da94f914f7 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -26,7 +26,7 @@ $left-gutter: 64px;
}
.mx_EventTile.mx_EventTile_info {
- padding-top: 0px;
+ padding-top: 1px;
}
.mx_EventTile_avatar {
@@ -37,7 +37,7 @@ $left-gutter: 64px;
}
.mx_EventTile.mx_EventTile_info .mx_EventTile_avatar {
- top: $font-8px;
+ top: $font-6px;
left: $left-gutter;
}
@@ -74,7 +74,6 @@ $left-gutter: 64px;
margin-left: 5px;
display: inline-block;
vertical-align: top;
- height: 16px;
overflow: hidden;
user-select: none;
@@ -421,15 +420,15 @@ $left-gutter: 64px;
}
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line {
- border-left: $e2e-verified-color 4px solid;
+ border-left: $e2e-verified-color $EventTile_e2e_state_indicator_width solid;
}
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line {
- border-left: $e2e-unverified-color 4px solid;
+ border-left: $e2e-unverified-color $EventTile_e2e_state_indicator_width solid;
}
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line {
- border-left: $e2e-unknown-color 4px solid;
+ border-left: $e2e-unknown-color $EventTile_e2e_state_indicator_width solid;
}
.mx_EventTile:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line,
@@ -447,8 +446,7 @@ $left-gutter: 64px;
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > a > .mx_MessageTimestamp,
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp,
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line > a > .mx_MessageTimestamp {
- left: 3px;
- width: auto;
+ width: $MessageTimestamp_width_hover;
}
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
diff --git a/res/css/views/rooms/_GroupLayout.scss b/res/css/views/rooms/_GroupLayout.scss
index 2b447be44a..543e6ed685 100644
--- a/res/css/views/rooms/_GroupLayout.scss
+++ b/res/css/views/rooms/_GroupLayout.scss
@@ -20,7 +20,7 @@ $left-gutter: 64px;
.mx_GroupLayout {
.mx_EventTile {
> .mx_SenderProfile {
- line-height: $font-17px;
+ line-height: $font-20px;
padding-left: $left-gutter;
}
@@ -34,11 +34,11 @@ $left-gutter: 64px;
.mx_MessageTimestamp {
position: absolute;
- width: 46px; /* 8 + 30 (avatar) + 8 */
+ width: $MessageTimestamp_width;
}
.mx_EventTile_line, .mx_EventTile_reply {
- padding-top: 3px;
+ padding-top: 1px;
padding-bottom: 3px;
line-height: $font-22px;
}
diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss
index ece547d02b..792c2f1f58 100644
--- a/res/css/views/rooms/_IRCLayout.scss
+++ b/res/css/views/rooms/_IRCLayout.scss
@@ -207,6 +207,17 @@ $irc-line-height: $font-18px;
width: unset;
max-width: var(--name-width);
}
+
+ .mx_SenderProfile_hover {
+ background: transparent;
+
+ > span {
+ > .mx_SenderProfile_name,
+ > .mx_SenderProfile_aux {
+ min-width: inherit;
+ }
+ }
+ }
}
.mx_ProfileResizer {
diff --git a/res/css/views/settings/_ProfileSettings.scss b/res/css/views/settings/_ProfileSettings.scss
index 732cbedf02..4cbcb8e708 100644
--- a/res/css/views/settings/_ProfileSettings.scss
+++ b/res/css/views/settings/_ProfileSettings.scss
@@ -14,6 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+.mx_ProfileSettings_controls_topic {
+ & > textarea {
+ resize: vertical;
+ }
+}
+
.mx_ProfileSettings_profile {
display: flex;
}
diff --git a/res/img/element-icons/brands/apple.svg b/res/img/element-icons/brands/apple.svg
new file mode 100644
index 0000000000..308c3c5d5a
--- /dev/null
+++ b/res/img/element-icons/brands/apple.svg
@@ -0,0 +1,3 @@
+
diff --git a/res/img/element-icons/brands/facebook.svg b/res/img/element-icons/brands/facebook.svg
new file mode 100644
index 0000000000..2742785424
--- /dev/null
+++ b/res/img/element-icons/brands/facebook.svg
@@ -0,0 +1,9 @@
+
diff --git a/res/img/element-icons/brands/github.svg b/res/img/element-icons/brands/github.svg
new file mode 100644
index 0000000000..503719520b
--- /dev/null
+++ b/res/img/element-icons/brands/github.svg
@@ -0,0 +1,3 @@
+
diff --git a/res/img/element-icons/brands/gitlab.svg b/res/img/element-icons/brands/gitlab.svg
new file mode 100644
index 0000000000..df84c41e21
--- /dev/null
+++ b/res/img/element-icons/brands/gitlab.svg
@@ -0,0 +1,9 @@
+
diff --git a/res/img/element-icons/brands/google.svg b/res/img/element-icons/brands/google.svg
new file mode 100644
index 0000000000..1b0b19ae5b
--- /dev/null
+++ b/res/img/element-icons/brands/google.svg
@@ -0,0 +1,6 @@
+
diff --git a/res/img/element-icons/brands/twitter.svg b/res/img/element-icons/brands/twitter.svg
new file mode 100644
index 0000000000..43eb825a59
--- /dev/null
+++ b/res/img/element-icons/brands/twitter.svg
@@ -0,0 +1,3 @@
+
diff --git a/scripts/ci/Dockerfile b/scripts/ci/Dockerfile
index 5351291f29..3fdd0d7bf6 100644
--- a/scripts/ci/Dockerfile
+++ b/scripts/ci/Dockerfile
@@ -1,8 +1,7 @@
# Update on docker hub with the following commands in the directory of this file:
# docker build -t vectorim/element-web-ci-e2etests-env:latest .
-# docker log
# docker push vectorim/element-web-ci-e2etests-env:latest
-FROM node:10
+FROM node:14-buster
RUN apt-get update
RUN apt-get -y install build-essential python3-dev libffi-dev python-pip python-setuptools sqlite3 libssl-dev python-virtualenv libjpeg-dev libxslt1-dev uuid-runtime
# dependencies for chrome (installed by puppeteer)
diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index 741798761f..2a28c8e43f 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -36,6 +36,7 @@ import {Analytics} from "../Analytics";
import CountlyAnalytics from "../CountlyAnalytics";
import UserActivity from "../UserActivity";
import {ModalWidgetStore} from "../stores/ModalWidgetStore";
+import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
declare global {
interface Window {
@@ -59,6 +60,7 @@ declare global {
mxNotifier: typeof Notifier;
mxRightPanelStore: RightPanelStore;
mxWidgetStore: WidgetStore;
+ mxWidgetLayoutStore: WidgetLayoutStore;
mxCallHandler: CallHandler;
mxAnalytics: Analytics;
mxCountlyAnalytics: typeof CountlyAnalytics;
diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts
index c301aa6a10..d0d5e60ce8 100644
--- a/src/BasePlatform.ts
+++ b/src/BasePlatform.ts
@@ -30,6 +30,7 @@ import {idbLoad, idbSave, idbDelete} from "./utils/StorageManager";
export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url";
export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url";
+export const SSO_IDP_ID_KEY = "mx_sso_idp_id";
export enum UpdateCheckStatus {
Checking = "CHECKING",
@@ -56,7 +57,7 @@ export default abstract class BasePlatform {
this.startUpdateCheck = this.startUpdateCheck.bind(this);
}
- abstract async getConfig(): Promise<{}>;
+ abstract getConfig(): Promise<{}>;
abstract getDefaultDeviceDisplayName(): string;
@@ -258,6 +259,9 @@ export default abstract class BasePlatform {
if (mxClient.getIdentityServerUrl()) {
localStorage.setItem(SSO_ID_SERVER_URL_KEY, mxClient.getIdentityServerUrl());
}
+ if (idpId) {
+ localStorage.setItem(SSO_IDP_ID_KEY, idpId);
+ }
const callbackUrl = this.getSSOCallbackUrl(fragmentAfterLogin);
window.location.href = mxClient.getSsoLoginUrl(callbackUrl.toString(), loginType, idpId); // redirect to SSO
}
diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx
index bcb2042f84..b5d4ed155f 100644
--- a/src/CallHandler.tsx
+++ b/src/CallHandler.tsx
@@ -82,7 +82,10 @@ import CountlyAnalytics from "./CountlyAnalytics";
import {UIFeature} from "./settings/UIFeature";
import { CallError } from "matrix-js-sdk/src/webrtc/call";
import { logger } from 'matrix-js-sdk/src/logger';
+import DesktopCapturerSourcePicker from "./components/views/elements/DesktopCapturerSourcePicker"
import { Action } from './dispatcher/actions';
+import { roomForVirtualRoom, getOrCreateVirtualRoomForRoom } from './VoipUserMapper';
+import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
const CHECK_PSTN_SUPPORT_ATTEMPTS = 3;
@@ -133,6 +136,15 @@ export default class CallHandler {
return window.mxCallHandler;
}
+ /*
+ * Gets the user-facing room associated with a call (call.roomId may be the call "virtual room"
+ * if a voip_mxid_translate_pattern is set in the config)
+ */
+ public static roomIdForCall(call: MatrixCall) {
+ if (!call) return null;
+ return roomForVirtualRoom(call.roomId) || call.roomId;
+ }
+
start() {
this.dispatcherRef = dis.register(this.onAction);
// add empty handlers for media actions, otherwise the media keys
@@ -284,11 +296,15 @@ export default class CallHandler {
// We don't allow placing more than one call per room, but that doesn't mean there
// can't be more than one, eg. in a glare situation. This checks that the given call
// is the call we consider 'the' call for its room.
- const callForThisRoom = this.getCallForRoom(call.roomId);
+ const mappedRoomId = CallHandler.roomIdForCall(call);
+
+ const callForThisRoom = this.getCallForRoom(mappedRoomId);
return callForThisRoom && call.callId === callForThisRoom.callId;
}
private setCallListeners(call: MatrixCall) {
+ const mappedRoomId = CallHandler.roomIdForCall(call);
+
call.on(CallEvent.Error, (err: CallError) => {
if (!this.matchesCallForThisRoom(call)) return;
@@ -318,7 +334,7 @@ export default class CallHandler {
Analytics.trackEvent('voip', 'callHangup');
- this.removeCallForRoom(call.roomId);
+ this.removeCallForRoom(mappedRoomId);
});
call.on(CallEvent.State, (newState: CallState, oldState: CallState) => {
if (!this.matchesCallForThisRoom(call)) return;
@@ -342,8 +358,9 @@ export default class CallHandler {
this.play(AudioID.Ringback);
break;
case CallState.Ended:
+ {
Analytics.trackEvent('voip', 'callEnded', 'hangupReason', call.hangupReason);
- this.removeCallForRoom(call.roomId);
+ this.removeCallForRoom(mappedRoomId);
if (oldState === CallState.InviteSent && (
call.hangupParty === CallParty.Remote ||
(call.hangupParty === CallParty.Local && call.hangupReason === CallErrorCode.InviteTimeout)
@@ -375,10 +392,14 @@ export default class CallHandler {
title: _t("Answered Elsewhere"),
description: _t("The call was answered on another device."),
});
- } else if (oldState !== CallState.Fledgling) {
+ } else if (oldState !== CallState.Fledgling && oldState !== CallState.Ringing) {
// don't play the end-call sound for calls that never got off the ground
this.play(AudioID.CallEnd);
}
+
+ this.logCallStats(call, mappedRoomId);
+ break;
+ }
}
});
call.on(CallEvent.Replaced, (newCall: MatrixCall) => {
@@ -392,25 +413,63 @@ export default class CallHandler {
this.pause(AudioID.Ringback);
}
- this.calls.set(newCall.roomId, newCall);
+ this.calls.set(mappedRoomId, newCall);
this.setCallListeners(newCall);
this.setCallState(newCall, newCall.state);
});
}
+ private async logCallStats(call: MatrixCall, mappedRoomId: string) {
+ const stats = await call.getCurrentCallStats();
+ logger.debug(
+ `Call completed. Call ID: ${call.callId}, virtual room ID: ${call.roomId}, ` +
+ `user-facing room ID: ${mappedRoomId}, direction: ${call.direction}, ` +
+ `our Party ID: ${call.ourPartyId}, hangup party: ${call.hangupParty}, ` +
+ `hangup reason: ${call.hangupReason}`,
+ );
+ logger.debug("Local candidates:");
+ for (const cand of stats.filter(item => item.type === 'local-candidate')) {
+ const address = cand.address || cand.ip; // firefox uses 'address', chrome uses 'ip'
+ logger.debug(
+ `${cand.id} - type: ${cand.candidateType}, address: ${address}, port: ${cand.port}, ` +
+ `protocol: ${cand.protocol}, relay protocol: ${cand.relayProtocol}, network type: ${cand.networkType}`,
+ );
+ }
+ logger.debug("Remote candidates:");
+ for (const cand of stats.filter(item => item.type === 'remote-candidate')) {
+ const address = cand.address || cand.ip; // firefox uses 'address', chrome uses 'ip'
+ logger.debug(
+ `${cand.id} - type: ${cand.candidateType}, address: ${address}, port: ${cand.port}, ` +
+ `protocol: ${cand.protocol}`,
+ );
+ }
+ logger.debug("Candidate pairs:");
+ for (const pair of stats.filter(item => item.type === 'candidate-pair')) {
+ logger.debug(
+ `${pair.localCandidateId} / ${pair.remoteCandidateId} - state: ${pair.state}, ` +
+ `nominated: ${pair.nominated}, ` +
+ `requests sent ${pair.requestsSent}, requests received ${pair.requestsReceived}, ` +
+ `responses received: ${pair.responsesReceived}, responses sent: ${pair.responsesSent}, ` +
+ `bytes received: ${pair.bytesReceived}, bytes sent: ${pair.bytesSent}, `,
+ );
+ }
+ }
+
private setCallAudioElement(call: MatrixCall) {
const audioElement = getRemoteAudioElement();
if (audioElement) call.setRemoteAudioElement(audioElement);
}
private setCallState(call: MatrixCall, status: CallState) {
+ const mappedRoomId = CallHandler.roomIdForCall(call);
+
console.log(
- `Call state in ${call.roomId} changed to ${status}`,
+ `Call state in ${mappedRoomId} changed to ${status}`,
);
dis.dispatch({
action: 'call_state',
- room_id: call.roomId,
+ room_id: mappedRoomId,
state: status,
});
}
@@ -477,14 +536,20 @@ export default class CallHandler {
}, null, true);
}
- private placeCall(
+ private async placeCall(
roomId: string, type: PlaceCallType,
localElement: HTMLVideoElement, remoteElement: HTMLVideoElement,
) {
Analytics.trackEvent('voip', 'placeCall', 'type', type);
CountlyAnalytics.instance.trackStartCall(roomId, type === PlaceCallType.Video, false);
- const call = createNewMatrixCall(MatrixClientPeg.get(), roomId);
+
+ const mappedRoomId = (await getOrCreateVirtualRoomForRoom(roomId)) || roomId;
+ logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId);
+
+ const call = createNewMatrixCall(MatrixClientPeg.get(), mappedRoomId);
+
this.calls.set(roomId, call);
+
this.setCallListeners(call);
this.setCallAudioElement(call);
@@ -508,7 +573,15 @@ export default class CallHandler {
});
return;
}
- call.placeScreenSharingCall(remoteElement, localElement);
+
+ call.placeScreenSharingCall(
+ remoteElement,
+ localElement,
+ async () : Promise => {
+ const {finished} = Modal.createDialog(DesktopCapturerSourcePicker);
+ const [source] = await finished;
+ return source;
+ });
} else {
console.error("Unknown conf call type: %s", type);
}
@@ -518,6 +591,12 @@ export default class CallHandler {
switch (payload.action) {
case 'place_call':
{
+ // We might be using managed hybrid widgets
+ if (isManagedHybridWidgetEnabled()) {
+ addManagedHybridWidget(payload.room_id);
+ return;
+ }
+
// if the runtime env doesn't do VoIP, whine.
if (!MatrixClientPeg.get().supportsVoip()) {
Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, {
@@ -586,13 +665,14 @@ export default class CallHandler {
const call = payload.call as MatrixCall;
- if (this.getCallForRoom(call.roomId)) {
+ const mappedRoomId = CallHandler.roomIdForCall(call);
+ if (this.getCallForRoom(mappedRoomId)) {
// ignore multiple incoming calls to the same room
return;
}
Analytics.trackEvent('voip', 'receiveCall', 'type', call.type);
- this.calls.set(call.roomId, call)
+ this.calls.set(mappedRoomId, call)
this.setCallListeners(call);
}
break;
diff --git a/src/ContentMessages.tsx b/src/ContentMessages.tsx
index 5409a606de..bec36d49f6 100644
--- a/src/ContentMessages.tsx
+++ b/src/ContentMessages.tsx
@@ -497,7 +497,7 @@ export default class ContentMessages {
content.info.mimetype = file.type;
}
- const prom = new Promise((resolve) => {
+ const prom = new Promise((resolve) => {
if (file.type.indexOf('image/') === 0) {
content.msgtype = 'm.image';
infoForImageFile(matrixClient, roomId, file).then((imageInfo) => {
diff --git a/src/CountlyAnalytics.ts b/src/CountlyAnalytics.ts
index b4727bc88b..974c08df18 100644
--- a/src/CountlyAnalytics.ts
+++ b/src/CountlyAnalytics.ts
@@ -840,7 +840,7 @@ export default class CountlyAnalytics {
let endTime = CountlyAnalytics.getTimestamp();
const cli = MatrixClientPeg.get();
if (!cli.getRoom(roomId)) {
- await new Promise(resolve => {
+ await new Promise(resolve => {
const handler = (room) => {
if (room.roomId === roomId) {
cli.off("Room", handler);
@@ -880,7 +880,7 @@ export default class CountlyAnalytics {
let endTime = CountlyAnalytics.getTimestamp();
if (!room.findEventById(eventId)) {
- await new Promise(resolve => {
+ await new Promise(resolve => {
const handler = (ev) => {
if (ev.getId() === eventId) {
room.off("Room.localEchoUpdated", handler);
diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx
index 637c0a2696..7d6b049914 100644
--- a/src/HtmlUtils.tsx
+++ b/src/HtmlUtils.tsx
@@ -422,6 +422,8 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
if (SettingsStore.getValue("feature_latex_maths")) {
const phtml = cheerio.load(safeBody,
{ _useHtmlParser2: true, decodeEntities: false })
+ // @ts-ignore - The types for `replaceWith` wrongly expect
+ // Cheerio instance to be returned.
phtml('div, span[data-mx-maths!=""]').replaceWith(function(i, e) {
return katex.renderToString(
AllHtmlEntities.decode(phtml(e).attr('data-mx-maths')),
diff --git a/src/IdentityAuthClient.js b/src/IdentityAuthClient.js
index fbdb6812ee..d3bfee2380 100644
--- a/src/IdentityAuthClient.js
+++ b/src/IdentityAuthClient.js
@@ -165,6 +165,7 @@ export default class IdentityAuthClient {
});
const [confirmed] = await finished;
if (confirmed) {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
useDefaultIdentityServer();
} else {
throw new AbortedIdentityActionError(
diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts
index f87af1a791..7780d4c87a 100644
--- a/src/Lifecycle.ts
+++ b/src/Lifecycle.ts
@@ -46,11 +46,13 @@ import {IntegrationManagers} from "./integrations/IntegrationManagers";
import {Mjolnir} from "./mjolnir/Mjolnir";
import DeviceListener from "./DeviceListener";
import {Jitsi} from "./widgets/Jitsi";
-import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform";
+import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY, SSO_IDP_ID_KEY} from "./BasePlatform";
import ThreepidInviteStore from "./stores/ThreepidInviteStore";
import CountlyAnalytics from "./CountlyAnalytics";
import CallHandler from './CallHandler';
import LifecycleCustomisations from "./customisations/Lifecycle";
+import ErrorDialog from "./components/views/dialogs/ErrorDialog";
+import {_t} from "./languageHandler";
const HOMESERVER_URL_KEY = "mx_hs_url";
const ID_SERVER_URL_KEY = "mx_is_url";
@@ -162,7 +164,8 @@ export async function getStoredSessionOwner(): Promise<[string, boolean]> {
* query-parameters extracted from the real query-string of the starting
* URI.
*
- * @param {String} defaultDeviceDisplayName
+ * @param {string} defaultDeviceDisplayName
+ * @param {string} fragmentAfterLogin path to go to after a successful login, only used for "Try again"
*
* @returns {Promise} promise which resolves to true if we completed the token
* login, else false
@@ -170,6 +173,7 @@ export async function getStoredSessionOwner(): Promise<[string, boolean]> {
export function attemptTokenLogin(
queryParams: Record,
defaultDeviceDisplayName?: string,
+ fragmentAfterLogin?: string,
): Promise {
if (!queryParams.loginToken) {
return Promise.resolve(false);
@@ -179,6 +183,12 @@ export function attemptTokenLogin(
const identityServer = localStorage.getItem(SSO_ID_SERVER_URL_KEY);
if (!homeserver) {
console.warn("Cannot log in with token: can't determine HS URL to use");
+ Modal.createTrackedDialog("SSO", "Unknown HS", ErrorDialog, {
+ title: _t("We couldn't log you in"),
+ description: _t("We asked the browser to remember which homeserver you use to let you sign in, " +
+ "but unfortunately your browser has forgotten it. Go to the sign in page and try again."),
+ button: _t("Try again"),
+ });
return Promise.resolve(false);
}
@@ -198,8 +208,28 @@ export function attemptTokenLogin(
return true;
});
}).catch((err) => {
- console.error("Failed to log in with login token: " + err + " " +
- err.data);
+ Modal.createTrackedDialog("SSO", "Token Rejected", ErrorDialog, {
+ title: _t("We couldn't log you in"),
+ description: err.name === "ConnectionError"
+ ? _t("Your homeserver was unreachable and was not able to log you in. Please try again. " +
+ "If this continues, please contact your homeserver administrator.")
+ : _t("Your homeserver rejected your log in attempt. " +
+ "This could be due to things just taking too long. Please try again. " +
+ "If this continues, please contact your homeserver administrator."),
+ button: _t("Try again"),
+ onFinished: tryAgain => {
+ if (tryAgain) {
+ const cli = Matrix.createClient({
+ baseUrl: homeserver,
+ idBaseUrl: identityServer,
+ });
+ const idpId = localStorage.getItem(SSO_IDP_ID_KEY) || undefined;
+ PlatformPeg.get().startSingleSignOn(cli, "sso", fragmentAfterLogin, idpId);
+ }
+ },
+ });
+ console.error("Failed to log in with login token:");
+ console.error(err);
return false;
});
}
@@ -366,7 +396,7 @@ async function abortLogin() {
// The plan is to gradually move the localStorage access done here into
// SessionStore to avoid bugs where the view becomes out-of-sync with
// localStorage (e.g. isGuest etc.)
-async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }): Promise {
+export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }): Promise {
const ignoreGuest = opts?.ignoreGuest;
if (!localStorage) {
diff --git a/src/Login.ts b/src/Login.ts
index 6493b244e0..aecc0493c7 100644
--- a/src/Login.ts
+++ b/src/Login.ts
@@ -33,10 +33,20 @@ interface IPasswordFlow {
type: "m.login.password";
}
+export enum IdentityProviderBrand {
+ Gitlab = "org.matrix.gitlab",
+ Github = "org.matrix.github",
+ Apple = "org.matrix.apple",
+ Google = "org.matrix.google",
+ Facebook = "org.matrix.facebook",
+ Twitter = "org.matrix.twitter",
+}
+
export interface IIdentityProvider {
id: string;
name: string;
icon?: string;
+ brand?: IdentityProviderBrand | string;
}
export interface ISSOFlow {
diff --git a/src/Markdown.js b/src/Markdown.js
index dc4d442aff..f670bded12 100644
--- a/src/Markdown.js
+++ b/src/Markdown.js
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import commonmark from 'commonmark';
+import * as commonmark from 'commonmark';
import {escape} from "lodash";
const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u'];
diff --git a/src/RoomNotifs.js b/src/RoomNotifs.js
index a86c521ac4..600655f635 100644
--- a/src/RoomNotifs.js
+++ b/src/RoomNotifs.js
@@ -202,12 +202,13 @@ function setRoomNotifsStateUnmuted(roomId, newState) {
}
function findOverrideMuteRule(roomId) {
- if (!MatrixClientPeg.get().pushRules ||
- !MatrixClientPeg.get().pushRules['global'] ||
- !MatrixClientPeg.get().pushRules['global'].override) {
+ const cli = MatrixClientPeg.get();
+ if (!cli.pushRules ||
+ !cli.pushRules['global'] ||
+ !cli.pushRules['global'].override) {
return null;
}
- for (const rule of MatrixClientPeg.get().pushRules['global'].override) {
+ for (const rule of cli.pushRules['global'].override) {
if (isRuleForRoom(roomId, rule)) {
if (isMuteRule(rule) && rule.enabled) {
return rule;
diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx
index 79c21c4af5..a39ad33b04 100644
--- a/src/SlashCommands.tsx
+++ b/src/SlashCommands.tsx
@@ -48,6 +48,7 @@ import SettingsStore from "./settings/SettingsStore";
import {UIFeature} from "./settings/UIFeature";
import {CHAT_EFFECTS} from "./effects"
import CallHandler from "./CallHandler";
+import {guessAndSetDMRoom} from "./Rooms";
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
interface HTMLInputEvent extends Event {
@@ -1112,6 +1113,24 @@ export const Commands = [
return success();
},
}),
+ new Command({
+ command: "converttodm",
+ description: _td("Converts the room to a DM"),
+ category: CommandCategories.other,
+ runFn: function(roomId, args) {
+ const room = MatrixClientPeg.get().getRoom(roomId);
+ return success(guessAndSetDMRoom(room, true));
+ },
+ }),
+ new Command({
+ command: "converttoroom",
+ description: _td("Converts the DM to a room"),
+ category: CommandCategories.other,
+ runFn: function(roomId, args) {
+ const room = MatrixClientPeg.get().getRoom(roomId);
+ return success(guessAndSetDMRoom(room, false));
+ },
+ }),
// Command definitions for autocompletion ONLY:
// /me is special because its not handled by SlashCommands.js and is instead done inside the Composer classes
diff --git a/src/TextForEvent.js b/src/TextForEvent.js
index 56e9abc0f2..3afe41d216 100644
--- a/src/TextForEvent.js
+++ b/src/TextForEvent.js
@@ -19,6 +19,7 @@ import * as Roles from './Roles';
import {isValid3pidInvite} from "./RoomInvite";
import SettingsStore from "./settings/SettingsStore";
import {ALL_RULE_TYPES, ROOM_RULE_TYPES, SERVER_RULE_TYPES, USER_RULE_TYPES} from "./mjolnir/BanList";
+import {WIDGET_LAYOUT_EVENT_TYPE} from "./stores/widgets/WidgetLayoutStore";
function textForMemberEvent(ev) {
// XXX: SYJS-16 "sender is sometimes null for join messages"
@@ -477,6 +478,11 @@ function textForWidgetEvent(event) {
}
}
+function textForWidgetLayoutEvent(event) {
+ const senderName = event.sender?.name || event.getSender();
+ return _t("%(senderName)s has updated the widget layout", {senderName});
+}
+
function textForMjolnirEvent(event) {
const senderName = event.getSender();
const {entity: prevEntity} = event.getPrevContent();
@@ -583,6 +589,7 @@ const stateHandlers = {
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
'im.vector.modular.widgets': textForWidgetEvent,
+ [WIDGET_LAYOUT_EVENT_TYPE]: textForWidgetLayoutEvent,
};
// Add all the Mjolnir stuff to the renderer
diff --git a/src/VoipUserMapper.ts b/src/VoipUserMapper.ts
new file mode 100644
index 0000000000..a4f5822065
--- /dev/null
+++ b/src/VoipUserMapper.ts
@@ -0,0 +1,79 @@
+/*
+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 { ensureDMExists, findDMForUser } from './createRoom';
+import { MatrixClientPeg } from "./MatrixClientPeg";
+import DMRoomMap from "./utils/DMRoomMap";
+import SdkConfig from "./SdkConfig";
+
+// Functions for mapping users & rooms for the voip_mxid_translate_pattern
+// config option
+
+export function voipUserMapperEnabled(): boolean {
+ return SdkConfig.get()['voip_mxid_translate_pattern'] !== undefined;
+}
+
+// only exported for tests
+export function userToVirtualUser(userId: string, templateString?: string): string {
+ if (templateString === undefined) templateString = SdkConfig.get()['voip_mxid_translate_pattern'];
+ if (!templateString) return null;
+ return templateString.replace('${mxid}', encodeURIComponent(userId).replace(/%/g, '=').toLowerCase());
+}
+
+// only exported for tests
+export function virtualUserToUser(userId: string, templateString?: string): string {
+ if (templateString === undefined) templateString = SdkConfig.get()['voip_mxid_translate_pattern'];
+ if (!templateString) return null;
+
+ const regexString = templateString.replace('${mxid}', '(.+)');
+
+ const match = userId.match('^' + regexString + '$');
+ if (!match) return null;
+
+ return decodeURIComponent(match[1].replace(/=/g, '%'));
+}
+
+async function getOrCreateVirtualRoomForUser(userId: string):Promise {
+ const virtualUser = userToVirtualUser(userId);
+ if (!virtualUser) return null;
+
+ return await ensureDMExists(MatrixClientPeg.get(), virtualUser);
+}
+
+export async function getOrCreateVirtualRoomForRoom(roomId: string):Promise {
+ const user = DMRoomMap.shared().getUserIdForRoomId(roomId);
+ if (!user) return null;
+ return getOrCreateVirtualRoomForUser(user);
+}
+
+export function roomForVirtualRoom(roomId: string):string {
+ const virtualUser = DMRoomMap.shared().getUserIdForRoomId(roomId);
+ if (!virtualUser) return null;
+ const realUser = virtualUserToUser(virtualUser);
+ const room = findDMForUser(MatrixClientPeg.get(), realUser);
+ if (room) {
+ return room.roomId;
+ } else {
+ return null;
+ }
+}
+
+export function isVirtualRoom(roomId: string):boolean {
+ const virtualUser = DMRoomMap.shared().getUserIdForRoomId(roomId);
+ if (!virtualUser) return null;
+ const realUser = virtualUserToUser(virtualUser);
+ return Boolean(realUser);
+}
diff --git a/src/accessibility/KeyboardShortcuts.tsx b/src/accessibility/KeyboardShortcuts.tsx
index 48d0eb2ab1..7a0ba58c97 100644
--- a/src/accessibility/KeyboardShortcuts.tsx
+++ b/src/accessibility/KeyboardShortcuts.tsx
@@ -168,6 +168,12 @@ const shortcuts: Record = {
key: Key.U,
}],
description: _td("Upload a file"),
+ }, {
+ keybinds: [{
+ modifiers: [CMD_OR_CTRL],
+ key: Key.F,
+ }],
+ description: _td("Search (must be enabled)"),
},
],
diff --git a/src/async-components/views/dialogs/security/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.js
index ab39a094db..863ee2b427 100644
--- a/src/async-components/views/dialogs/security/CreateKeyBackupDialog.js
+++ b/src/async-components/views/dialogs/security/CreateKeyBackupDialog.js
@@ -95,7 +95,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
const blob = new Blob([this._keyBackupInfo.recovery_key], {
type: 'text/plain;charset=us-ascii',
});
- FileSaver.saveAs(blob, 'recovery-key.txt');
+ FileSaver.saveAs(blob, 'security-key.txt');
this.setState({
downloaded: true,
@@ -238,7 +238,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
)}
{_t(
"We'll store an encrypted copy of your keys on our server. " +
- "Secure your backup with a recovery passphrase.",
+ "Secure your backup with a Security Phrase.",
)}
{_t("For maximum security, this should be different from your account password.")}
@@ -252,10 +252,10 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
onValidate={this._onPassPhraseValidate}
fieldRef={this._passphraseField}
autoFocus={true}
- label={_td("Enter a recovery passphrase")}
- labelEnterPassword={_td("Enter a recovery passphrase")}
- labelStrongPassword={_td("Great! This recovery passphrase looks strong enough.")}
- labelAllowedButUnsafe={_td("Great! This recovery passphrase looks strong enough.")}
+ label={_td("Enter a Security Phrase")}
+ labelEnterPassword={_td("Enter a Security Phrase")}
+ labelStrongPassword={_td("Great! This Security Phrase looks strong enough.")}
+ labelAllowedButUnsafe={_td("Great! This Security Phrase looks strong enough.")}
/>
@@ -270,7 +270,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
{_t("Advanced")}
- {_t("Set up with a recovery key")}
+ {_t("Set up with a Security Key")}
;
@@ -310,7 +310,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return