Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/ts/12

 Conflicts:
	src/components/structures/InteractiveAuth.js
This commit is contained in:
Michael Telatynski 2021-07-23 23:43:48 +01:00
commit 22339688cb
582 changed files with 15762 additions and 10124 deletions

View file

@ -1,3 +1,15 @@
<!-- Please read https://github.com/matrix-org/matrix-js-sdk/blob/develop/CONTRIBUTING.rst before submitting your pull request -->
<!-- Please read https://github.com/matrix-org/matrix-js-sdk/blob/develop/CONTRIBUTING.md before submitting your pull request -->
<!-- Include a Sign-Off as described in https://github.com/matrix-org/matrix-js-sdk/blob/develop/CONTRIBUTING.rst#sign-off -->
<!-- Include a Sign-Off as described in https://github.com/matrix-org/matrix-js-sdk/blob/develop/CONTRIBUTING.md#sign-off -->
<!-- To specify text for the changelog entry (otherwise the PR title will be used):
Notes:
Changes in this project generate changelog entries in element-web by default.
To suppress this:
element-web notes: none
...or to specify different notes:
element-web notes: <notes>
-->

View file

@ -1,5 +1,8 @@
name: Develop
on:
# These tests won't work for non-develop branches at the moment as they
# won't pull in the right versions of other repos, so they're only enabled
# on develop.
push:
branches: [develop]
pull_request:

3
.gitignore vendored
View file

@ -15,3 +15,6 @@ package-lock.json
.DS_Store
*.tmp
.vscode
.vscode/

View file

@ -1,3 +1,152 @@
Changes in [3.26.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.26.0) (2021-07-19)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.26.0-rc.1...v3.26.0)
* Fix 'User' type import
[\#6376](https://github.com/matrix-org/matrix-react-sdk/pull/6376)
Changes in [3.26.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.26.0-rc.1) (2021-07-14)
===============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.25.0...v3.26.0-rc.1)
* Fix voice messages in right panels
[\#6370](https://github.com/matrix-org/matrix-react-sdk/pull/6370)
* Use TileShape enum more universally
[\#6369](https://github.com/matrix-org/matrix-react-sdk/pull/6369)
* Translations update from Weblate
[\#6373](https://github.com/matrix-org/matrix-react-sdk/pull/6373)
* Hide world readable history option in encrypted rooms
[\#5947](https://github.com/matrix-org/matrix-react-sdk/pull/5947)
* Make the Image View buttons easier to hit
[\#6372](https://github.com/matrix-org/matrix-react-sdk/pull/6372)
* Reorder buttons in the Image View
[\#6368](https://github.com/matrix-org/matrix-react-sdk/pull/6368)
* Add VS Code to gitignore
[\#6367](https://github.com/matrix-org/matrix-react-sdk/pull/6367)
* Fix inviter exploding due to member being null
[\#6362](https://github.com/matrix-org/matrix-react-sdk/pull/6362)
* Increase sample count in voice message thumbnail
[\#6359](https://github.com/matrix-org/matrix-react-sdk/pull/6359)
* Improve arraySeed utility
[\#6360](https://github.com/matrix-org/matrix-react-sdk/pull/6360)
* Convert FontManager to TS and stub it out for tests
[\#6358](https://github.com/matrix-org/matrix-react-sdk/pull/6358)
* Adjust recording waveform behaviour for voice messages
[\#6357](https://github.com/matrix-org/matrix-react-sdk/pull/6357)
* Do not honor string power levels
[\#6245](https://github.com/matrix-org/matrix-react-sdk/pull/6245)
* Add alias and directory customisation points
[\#6343](https://github.com/matrix-org/matrix-react-sdk/pull/6343)
* Fix multiinviter user already in room and clean up code
[\#6354](https://github.com/matrix-org/matrix-react-sdk/pull/6354)
* Fix right panel not closing user info when changing rooms
[\#6341](https://github.com/matrix-org/matrix-react-sdk/pull/6341)
* Quit sticker picker on m.sticker
[\#5679](https://github.com/matrix-org/matrix-react-sdk/pull/5679)
* Don't autodetect language in inline code blocks
[\#6350](https://github.com/matrix-org/matrix-react-sdk/pull/6350)
* Make ghost button background transparent
[\#6331](https://github.com/matrix-org/matrix-react-sdk/pull/6331)
* only consider valid & loaded url previews for show N more prompt
[\#6346](https://github.com/matrix-org/matrix-react-sdk/pull/6346)
* Extract MXCs from _matrix/media/r0/ URLs for inline images in messages
[\#6335](https://github.com/matrix-org/matrix-react-sdk/pull/6335)
* Fix small visual regression with the site name on url previews
[\#6342](https://github.com/matrix-org/matrix-react-sdk/pull/6342)
* Make PIP CallView draggable/movable
[\#5952](https://github.com/matrix-org/matrix-react-sdk/pull/5952)
* Convert VoiceUserSettingsTab to TS
[\#6340](https://github.com/matrix-org/matrix-react-sdk/pull/6340)
* Simplify typescript definition for Modernizr
[\#6339](https://github.com/matrix-org/matrix-react-sdk/pull/6339)
* Remember the last used server for room directory searches
[\#6322](https://github.com/matrix-org/matrix-react-sdk/pull/6322)
* Focus composer after reacting
[\#6332](https://github.com/matrix-org/matrix-react-sdk/pull/6332)
* Fix bug which prevented more than one event getting pinned
[\#6336](https://github.com/matrix-org/matrix-react-sdk/pull/6336)
* Make DeviceListener also update on megolm key in SSSS
[\#6337](https://github.com/matrix-org/matrix-react-sdk/pull/6337)
* Improve URL previews
[\#6326](https://github.com/matrix-org/matrix-react-sdk/pull/6326)
* Don't close settings dialog when opening spaces feedback prompt
[\#6334](https://github.com/matrix-org/matrix-react-sdk/pull/6334)
* Update import location for types
[\#6330](https://github.com/matrix-org/matrix-react-sdk/pull/6330)
* Improve blurhash rendering performance
[\#6329](https://github.com/matrix-org/matrix-react-sdk/pull/6329)
* Use a proper color scheme for codeblocks
[\#6320](https://github.com/matrix-org/matrix-react-sdk/pull/6320)
* Burn `sdk.getComponent()` with 🔥
[\#6308](https://github.com/matrix-org/matrix-react-sdk/pull/6308)
* Fix instances of the Edit Message Composer's save button being wrongly
disabled
[\#6307](https://github.com/matrix-org/matrix-react-sdk/pull/6307)
* Do not generate a lockfile when running in CI
[\#6327](https://github.com/matrix-org/matrix-react-sdk/pull/6327)
* Update lockfile with correct dependencies
[\#6324](https://github.com/matrix-org/matrix-react-sdk/pull/6324)
* Clarify the keys we use when submitting rageshakes
[\#6321](https://github.com/matrix-org/matrix-react-sdk/pull/6321)
* Fix ImageView context menu
[\#6318](https://github.com/matrix-org/matrix-react-sdk/pull/6318)
* TypeScript migration
[\#6315](https://github.com/matrix-org/matrix-react-sdk/pull/6315)
* Move animation to compositor
[\#6310](https://github.com/matrix-org/matrix-react-sdk/pull/6310)
* Reorganize preferences
[\#5742](https://github.com/matrix-org/matrix-react-sdk/pull/5742)
* Fix being able to un-rotate images
[\#6313](https://github.com/matrix-org/matrix-react-sdk/pull/6313)
* Fix icon size in passphrase prompt
[\#6312](https://github.com/matrix-org/matrix-react-sdk/pull/6312)
* Use sleep & defer from js-sdk instead of duplicating it
[\#6305](https://github.com/matrix-org/matrix-react-sdk/pull/6305)
* Convert EventTimeline, EventTimelineSet and TimelineWindow to TS
[\#6295](https://github.com/matrix-org/matrix-react-sdk/pull/6295)
* Comply with new member-delimiter-style rule
[\#6306](https://github.com/matrix-org/matrix-react-sdk/pull/6306)
* Fix Test Linting
[\#6304](https://github.com/matrix-org/matrix-react-sdk/pull/6304)
* Convert Markdown to TypeScript
[\#6303](https://github.com/matrix-org/matrix-react-sdk/pull/6303)
* Convert RoomHeader to TS
[\#6302](https://github.com/matrix-org/matrix-react-sdk/pull/6302)
* Prevent RoomDirectory from exploding when filterString is wrongly nulled
[\#6296](https://github.com/matrix-org/matrix-react-sdk/pull/6296)
* Add support for blurhash (MSC2448)
[\#5099](https://github.com/matrix-org/matrix-react-sdk/pull/5099)
* Remove rateLimitedFunc
[\#6300](https://github.com/matrix-org/matrix-react-sdk/pull/6300)
* Convert some Key Verification classes to TypeScript
[\#6299](https://github.com/matrix-org/matrix-react-sdk/pull/6299)
* Typescript conversion of Composer components and more
[\#6292](https://github.com/matrix-org/matrix-react-sdk/pull/6292)
* Upgrade browserlist target versions
[\#6298](https://github.com/matrix-org/matrix-react-sdk/pull/6298)
* Fix browser crashing when searching for a malformed HTML tag
[\#6297](https://github.com/matrix-org/matrix-react-sdk/pull/6297)
* Add custom audio player
[\#6264](https://github.com/matrix-org/matrix-react-sdk/pull/6264)
* Lint MXC APIs to centralise access
[\#6293](https://github.com/matrix-org/matrix-react-sdk/pull/6293)
* Remove reminescent references to the tinter
[\#6290](https://github.com/matrix-org/matrix-react-sdk/pull/6290)
* More js-sdk type consolidation
[\#6263](https://github.com/matrix-org/matrix-react-sdk/pull/6263)
* Convert MessagePanel, TimelinePanel, ScrollPanel, and more to Typescript
[\#6243](https://github.com/matrix-org/matrix-react-sdk/pull/6243)
* Migrate to `eslint-plugin-matrix-org`
[\#6285](https://github.com/matrix-org/matrix-react-sdk/pull/6285)
* Avoid cyclic dependencies by moving watchers out of constructor
[\#6287](https://github.com/matrix-org/matrix-react-sdk/pull/6287)
* Add spacing between toast buttons with cross browser support in mind
[\#6284](https://github.com/matrix-org/matrix-react-sdk/pull/6284)
* Deprecate Tinter and TintableSVG
[\#6279](https://github.com/matrix-org/matrix-react-sdk/pull/6279)
* Migrate FilePanel to TypeScript
[\#6283](https://github.com/matrix-org/matrix-react-sdk/pull/6283)
Changes in [3.25.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.25.0) (2021-07-05)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.25.0-rc.1...v3.25.0)

6
__mocks__/FontManager.js Normal file
View file

@ -0,0 +1,6 @@
// Stub out FontManager for tests as it doesn't validate anything we don't already know given
// our fixed test environment and it requires the installation of node-canvas.
module.exports = {
fixupColorFonts: () => Promise.resolve(),
};

1
__mocks__/workerMock.js Normal file
View file

@ -0,0 +1 @@
module.exports = jest.fn();

View file

@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
"version": "3.25.0",
"version": "3.26.0",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@ -46,6 +46,7 @@
"start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
"lint": "yarn lint:types && yarn lint:js && yarn lint:style",
"lint:js": "eslint --max-warnings 0 src test",
"lint:js-fix": "eslint --fix src test",
"lint:types": "tsc --noEmit --jsx react",
"lint:style": "stylelint 'res/css/**/*.scss'",
"test": "jest",
@ -54,7 +55,6 @@
},
"dependencies": {
"@babel/runtime": "^7.12.5",
"@types/commonmark": "^0.27.4",
"await-lock": "^2.1.0",
"blurhash": "^1.1.3",
"browser-encrypt-attachment": "^0.3.0",
@ -65,8 +65,8 @@
"counterpart": "^0.18.6",
"diff-dom": "^4.2.2",
"diff-match-patch": "^1.0.5",
"emojibase-data": "^5.1.1",
"emojibase-regex": "^4.1.1",
"emojibase-data": "^6.2.0",
"emojibase-regex": "^5.1.3",
"escape-html": "^1.0.3",
"file-saver": "^2.0.5",
"filesize": "6.1.0",
@ -80,7 +80,7 @@
"katex": "^0.12.0",
"linkifyjs": "^2.1.9",
"lodash": "^4.17.20",
"matrix-js-sdk": "12.0.1",
"matrix-js-sdk": "12.1.0",
"matrix-widget-api": "^0.1.0-beta.15",
"minimist": "^1.2.5",
"opus-recorder": "^8.0.3",
@ -92,6 +92,7 @@
"re-resizable": "^6.9.0",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.0",
"react-blurhash": "^0.1.3",
"react-dom": "^17.0.2",
"react-focus-lock": "^2.5.0",
"react-transition-group": "^4.4.1",
@ -124,7 +125,9 @@
"@peculiar/webcrypto": "^1.1.4",
"@sinonjs/fake-timers": "^7.0.2",
"@types/classnames": "^2.2.11",
"@types/commonmark": "^0.27.4",
"@types/counterpart": "^0.18.1",
"@types/css-font-loading-module": "^0.0.6",
"@types/diff-match-patch": "^1.0.32",
"@types/flux": "^3.1.9",
"@types/jest": "^26.0.20",
@ -185,7 +188,8 @@
"\\$webapp/i18n/languages.json": "<rootDir>/__mocks__/languages.json",
"decoderWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
"decoderWorker\\.min\\.wasm": "<rootDir>/__mocks__/empty.js",
"waveWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js"
"waveWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
"workers/(.+)\\.worker\\.ts": "<rootDir>/__mocks__/workerMock.js"
},
"transformIgnorePatterns": [
"/node_modules/(?!matrix-js-sdk).+$"

View file

@ -86,6 +86,7 @@
@import "./views/dialogs/_IncomingSasDialog.scss";
@import "./views/dialogs/_InviteDialog.scss";
@import "./views/dialogs/_KeyboardShortcutsDialog.scss";
@import "./views/dialogs/_ManageRestrictedJoinRuleDialog.scss";
@import "./views/dialogs/_MessageEditHistoryDialog.scss";
@import "./views/dialogs/_ModalWidgetDialog.scss";
@import "./views/dialogs/_NewSessionReviewDialog.scss";
@ -120,6 +121,7 @@
@import "./views/elements/_AddressTile.scss";
@import "./views/elements/_DesktopBuildsNotice.scss";
@import "./views/elements/_DesktopCapturerSourcePicker.scss";
@import "./views/elements/_DialPadBackspaceButton.scss";
@import "./views/elements/_DirectorySearchBox.scss";
@import "./views/elements/_Dropdown.scss";
@import "./views/elements/_EditableItemList.scss";
@ -148,6 +150,7 @@
@import "./views/elements/_StyledCheckbox.scss";
@import "./views/elements/_StyledRadioButton.scss";
@import "./views/elements/_SyntaxHighlight.scss";
@import "./views/elements/_TagComposer.scss";
@import "./views/elements/_TextWithTooltip.scss";
@import "./views/elements/_ToggleSwitch.scss";
@import "./views/elements/_Tooltip.scss";
@ -157,18 +160,19 @@
@import "./views/groups/_GroupPublicityToggle.scss";
@import "./views/groups/_GroupRoomList.scss";
@import "./views/groups/_GroupUserSettings.scss";
@import "./views/messages/_CallEvent.scss";
@import "./views/messages/_CreateEvent.scss";
@import "./views/messages/_DateSeparator.scss";
@import "./views/messages/_EventTileBubble.scss";
@import "./views/messages/_MEmoteBody.scss";
@import "./views/messages/_MFileBody.scss";
@import "./views/messages/_MImageBody.scss";
@import "./views/messages/_MImageReplyBody.scss";
@import "./views/messages/_MJitsiWidgetEvent.scss";
@import "./views/messages/_MNoticeBody.scss";
@import "./views/messages/_MStickerBody.scss";
@import "./views/messages/_MTextBody.scss";
@import "./views/messages/_MVideoBody.scss";
@import "./views/messages/_MVoiceMessageBody.scss";
@import "./views/messages/_MediaBody.scss";
@import "./views/messages/_MessageActionBar.scss";
@import "./views/messages/_MessageTimestamp.scss";
@ -197,10 +201,12 @@
@import "./views/rooms/_E2EIcon.scss";
@import "./views/rooms/_EditMessageComposer.scss";
@import "./views/rooms/_EntityTile.scss";
@import "./views/rooms/_EventBubbleTile.scss";
@import "./views/rooms/_EventTile.scss";
@import "./views/rooms/_GroupLayout.scss";
@import "./views/rooms/_IRCLayout.scss";
@import "./views/rooms/_JumpToBottomButton.scss";
@import "./views/rooms/_LinkPreviewGroup.scss";
@import "./views/rooms/_LinkPreviewWidget.scss";
@import "./views/rooms/_MemberInfo.scss";
@import "./views/rooms/_MemberList.scss";
@ -211,6 +217,7 @@
@import "./views/rooms/_PinnedEventTile.scss";
@import "./views/rooms/_PresenceLabel.scss";
@import "./views/rooms/_ReplyPreview.scss";
@import "./views/rooms/_ReplyTile.scss";
@import "./views/rooms/_RoomBreadcrumbs.scss";
@import "./views/rooms/_RoomHeader.scss";
@import "./views/rooms/_RoomList.scss";
@ -259,6 +266,7 @@
@import "./views/toasts/_NonUrgentEchoFailureToast.scss";
@import "./views/verification/_VerificationShowSas.scss";
@import "./views/voip/_CallContainer.scss";
@import "./views/voip/_CallPreview.scss";
@import "./views/voip/_CallView.scss";
@import "./views/voip/_CallViewForRoom.scss";
@import "./views/voip/_DialPad.scss";

View file

@ -45,9 +45,14 @@ limitations under the License.
/* Overrides for the attachment body tiles */
.mx_FilePanel .mx_EventTile {
.mx_FilePanel .mx_EventTile:not([data-layout=bubble]) {
word-break: break-word;
margin-top: 32px;
margin-top: 10px;
padding-top: 0;
.mx_EventTile_line {
padding-left: 0;
}
}
.mx_FilePanel .mx_EventTile .mx_MImageBody {
@ -118,10 +123,6 @@ limitations under the License.
padding-left: 0px;
}
.mx_FilePanel .mx_EventTile:hover .mx_EventTile_line {
background-color: $primary-bg-color;
}
.mx_FilePanel_empty::before {
mask-image: url('$(res)/img/element-icons/room/files.svg');
}

View file

@ -84,7 +84,7 @@ limitations under the License.
display: inline;
}
.mx_NotificationPanel .mx_EventTile_senderDetails {
.mx_NotificationPanel .mx_EventTile:not([data-layout=bubble]) .mx_EventTile_senderDetails {
padding-left: 36px; // align with the room name
position: relative;
@ -105,7 +105,7 @@ limitations under the License.
padding-left: 5px;
}
.mx_NotificationPanel .mx_EventTile_line {
.mx_NotificationPanel .mx_EventTile:not([data-layout=bubble]) .mx_EventTile_line {
margin-right: 0px;
padding-left: 36px; // align with the room name
padding-top: 0px;

View file

@ -234,6 +234,9 @@ $SpaceRoomViewInnerWidth: 428px;
}
.mx_SpaceRoomView_landing {
display: flex;
flex-direction: column;
> .mx_BaseAvatar_image,
> .mx_BaseAvatar > .mx_BaseAvatar_image {
border-radius: 12px;
@ -340,6 +343,7 @@ $SpaceRoomViewInnerWidth: 428px;
.mx_SearchBox {
margin: 0 0 20px;
flex: 0;
}
.mx_SpaceFeedbackPrompt {
@ -350,6 +354,11 @@ $SpaceRoomViewInnerWidth: 428px;
display: none;
}
}
.mx_SpaceRoomDirectory_list {
// we don't want this container to get forced into the flexbox layout
display: contents;
}
}
.mx_SpaceRoomView_privateScope {

View file

@ -1,6 +1,7 @@
/*
Copyright 2017 Travis Ralston
Copyright 2019 New Vector Ltd
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.
@ -20,7 +21,6 @@ limitations under the License.
padding: 0 0 0 16px;
display: flex;
flex-direction: column;
position: absolute;
top: 0;
bottom: 0;
left: 0;
@ -28,13 +28,95 @@ limitations under the License.
margin-top: 8px;
}
.mx_TabbedView_tabsOnLeft {
flex-direction: column;
position: absolute;
.mx_TabbedView_tabLabels {
width: 170px;
max-width: 170px;
color: $tab-label-fg-color;
position: fixed;
}
.mx_TabbedView_tabPanel {
margin-left: 240px; // 170px sidebar + 70px padding
flex-direction: column;
}
.mx_TabbedView_tabLabel_active {
background-color: $tab-label-active-bg-color;
color: $tab-label-active-fg-color;
}
.mx_TabbedView_tabLabel_active .mx_TabbedView_maskedIcon::before {
background-color: $tab-label-active-icon-bg-color;
}
.mx_TabbedView_maskedIcon {
width: 16px;
height: 16px;
margin-left: 8px;
margin-right: 16px;
}
.mx_TabbedView_maskedIcon::before {
mask-size: 16px;
width: 16px;
height: 16px;
}
}
.mx_TabbedView_tabsOnTop {
flex-direction: column;
.mx_TabbedView_tabLabels {
display: flex;
margin-bottom: 8px;
}
.mx_TabbedView_tabLabel {
padding-left: 0px;
padding-right: 52px;
.mx_TabbedView_tabLabel_text {
font-size: 15px;
color: $tertiary-fg-color;
}
}
.mx_TabbedView_tabPanel {
flex-direction: row;
}
.mx_TabbedView_tabLabel_active {
color: $accent-color;
.mx_TabbedView_tabLabel_text {
color: $accent-color;
}
}
.mx_TabbedView_tabLabel_active .mx_TabbedView_maskedIcon::before {
background-color: $accent-color;
}
.mx_TabbedView_maskedIcon {
width: 22px;
height: 22px;
margin-left: 0px;
margin-right: 8px;
}
.mx_TabbedView_maskedIcon::before {
mask-size: 22px;
width: inherit;
height: inherit;
}
}
.mx_TabbedView_tabLabels {
color: $tab-label-fg-color;
}
.mx_TabbedView_tabLabel {
display: flex;
align-items: center;
@ -46,43 +128,25 @@ limitations under the License.
position: relative;
}
.mx_TabbedView_tabLabel_active {
background-color: $tab-label-active-bg-color;
color: $tab-label-active-fg-color;
}
.mx_TabbedView_maskedIcon {
margin-left: 8px;
margin-right: 16px;
width: 16px;
height: 16px;
display: inline-block;
}
.mx_TabbedView_maskedIcon::before {
display: inline-block;
background-color: $tab-label-icon-bg-color;
background-color: $icon-button-color;
mask-repeat: no-repeat;
mask-size: 16px;
width: 16px;
height: 16px;
mask-position: center;
content: '';
}
.mx_TabbedView_tabLabel_active .mx_TabbedView_maskedIcon::before {
background-color: $tab-label-active-icon-bg-color;
}
.mx_TabbedView_tabLabel_text {
vertical-align: middle;
}
.mx_TabbedView_tabPanel {
margin-left: 240px; // 170px sidebar + 70px padding
flex-grow: 1;
display: flex;
flex-direction: column;
min-height: 0; // firefox
}

View file

@ -14,9 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_AudioPlayer_container {
.mx_MediaBody.mx_AudioPlayer_container {
padding: 16px 12px 12px 12px;
max-width: 267px; // use max to make the control fit in the files/pinned panels
.mx_AudioPlayer_primaryContainer {
display: flex;

View file

@ -18,10 +18,10 @@ limitations under the License.
// are shared amongst multiple voice message components.
// Container for live recording and playback controls
.mx_VoiceMessagePrimaryContainer {
// 7px top and bottom for visual design. 12px left & right, but the waveform (right)
// has a 1px padding on it that we want to account for.
padding: 7px 12px 7px 11px;
.mx_MediaBody.mx_VoiceMessagePrimaryContainer {
// The waveform (right) has a 1px padding on it that we want to account for, otherwise
// inherit from mx_MediaBody
padding-right: 11px;
// Cheat at alignment a bit
display: flex;
@ -49,4 +49,8 @@ limitations under the License.
padding-right: 6px; // with the fixed width this ends up as a visual 8px most of the time, as intended.
padding-left: 8px; // isolate from recording circle / play control
}
&.mx_VoiceMessagePrimaryContainer_noWaveform {
max-width: 162px; // with all the padding this results in 185px wide
}
}

View file

@ -65,7 +65,7 @@ limitations under the License.
.mx_CreateRoomDialog_aliasContainer {
display: flex;
// put margin on container so it can collapse with siblings
margin: 10px 0;
margin: 24px 0 10px;
.mx_RoomAliasField {
margin: 0;
@ -101,10 +101,6 @@ limitations under the License.
margin-left: 30px;
}
.mx_CreateRoomDialog_topic {
margin-bottom: 36px;
}
.mx_Dialog_content > .mx_SettingsFlag {
margin-top: 24px;
}
@ -113,5 +109,56 @@ limitations under the License.
margin: 0 85px 0 0;
font-size: $font-12px;
}
.mx_Dropdown {
margin-bottom: 8px;
font-weight: normal;
font-family: $font-family;
font-size: $font-14px;
color: $primary-fg-color;
.mx_Dropdown_input {
border: 1px solid $input-border-color;
}
.mx_Dropdown_option {
font-size: $font-14px;
line-height: $font-32px;
height: 32px;
min-height: 32px;
> div {
padding-left: 30px;
position: relative;
&::before {
content: "";
position: absolute;
height: 16px;
width: 16px;
left: 6px;
top: 8px;
mask-repeat: no-repeat;
mask-position: center;
background-color: $secondary-fg-color;
}
}
}
.mx_CreateRoomDialog_dropdown_invite::before {
mask-image: url('$(res)/img/element-icons/lock.svg');
mask-size: contain;
}
.mx_CreateRoomDialog_dropdown_public::before {
mask-image: url('$(res)/img/globe.svg');
mask-size: 12px;
}
.mx_CreateRoomDialog_dropdown_restricted::before {
mask-image: url('$(res)/img/element-icons/community-members.svg');
mask-size: contain;
}
}
}

View file

@ -36,6 +36,10 @@ limitations under the License.
flex-shrink: 0;
overflow-y: auto;
.mx_EventTile[data-layout=bubble] {
margin-top: 20px;
}
div {
pointer-events: none;
}

View file

@ -14,6 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_InviteDialog_transferWrapper .mx_Dialog {
padding-bottom: 16px;
}
.mx_InviteDialog_addressBar {
display: flex;
flex-direction: row;
@ -286,16 +290,41 @@ limitations under the License.
}
}
.mx_InviteDialog {
.mx_InviteDialog_other {
// Prevent the dialog from jumping around randomly when elements change.
height: 600px;
padding-left: 20px; // the design wants some padding on the left
display: flex;
.mx_InviteDialog_userSections {
height: calc(100% - 115px); // mx_InviteDialog's height minus some for the upper and lower elements
}
}
.mx_InviteDialog_content {
height: calc(100% - 36px); // full height minus the size of the header
overflow: hidden;
}
.mx_InviteDialog_transfer {
width: 496px;
height: 466px;
flex-direction: column;
.mx_InviteDialog_content {
overflow: hidden;
height: 100%;
flex-direction: column;
.mx_TabbedView {
height: calc(100% - 60px);
}
overflow: visible;
}
.mx_InviteDialog_addressBar {
margin-top: 8px;
}
input[type="checkbox"] {
margin-right: 8px;
}
}
@ -303,7 +332,6 @@ limitations under the License.
margin-top: 4px;
overflow-y: auto;
padding: 0 45px 4px 0;
height: calc(100% - 115px); // mx_InviteDialog's height minus some for the upper and lower elements
}
.mx_InviteDialog_hasFooter .mx_InviteDialog_userSections {
@ -318,6 +346,74 @@ limitations under the License.
padding: 0;
}
.mx_InviteDialog_dialPad .mx_InviteDialog_dialPadField {
border-top: 0;
border-left: 0;
border-right: 0;
border-radius: 0;
margin-top: 0;
border-color: $quaternary-fg-color;
input {
font-size: 18px;
font-weight: 600;
padding-top: 0;
}
}
.mx_InviteDialog_dialPad .mx_InviteDialog_dialPadField:focus-within {
border-color: $accent-color;
}
.mx_InviteDialog_dialPadField .mx_Field_postfix {
/* Remove border separator between postfix and field content */
border-left: none;
}
.mx_InviteDialog_dialPad {
width: 224px;
margin-top: 16px;
margin-left: auto;
margin-right: auto;
}
.mx_InviteDialog_dialPad .mx_DialPad {
row-gap: 16px;
column-gap: 48px;
margin-left: auto;
margin-right: auto;
}
.mx_InviteDialog_transferConsultConnect {
padding-top: 16px;
/* This wants a drop shadow the full width of the dialog, so relative-position it
* and make it wider, then compensate with padding
*/
position: relative;
width: 496px;
left: -24px;
padding-left: 24px;
padding-right: 24px;
border-top: 1px solid $message-body-panel-bg-color;
display: flex;
flex-direction: row;
align-items: center;
}
.mx_InviteDialog_transferConsultConnect_pushRight {
margin-left: auto;
}
.mx_InviteDialog_userDirectoryIcon::before {
mask-image: url('$(res)/img/voip/tab-userdirectory.svg');
}
.mx_InviteDialog_dialPadIcon::before {
mask-image: url('$(res)/img/voip/tab-dialpad.svg');
}
.mx_InviteDialog_multiInviterError {
> h4 {
font-size: $font-15px;

View file

@ -0,0 +1,150 @@
/*
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_ManageRestrictedJoinRuleDialog_wrapper {
.mx_Dialog {
display: flex;
flex-direction: column;
}
}
.mx_ManageRestrictedJoinRuleDialog {
width: 480px;
color: $primary-fg-color;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
min-height: 0;
height: 60vh;
.mx_SearchBox {
// To match the space around the title
margin: 0 0 15px 0;
flex-grow: 0;
}
.mx_ManageRestrictedJoinRuleDialog_content {
flex-grow: 1;
}
.mx_ManageRestrictedJoinRuleDialog_noResults {
display: block;
margin-top: 24px;
}
.mx_ManageRestrictedJoinRuleDialog_section {
&:not(:first-child) {
margin-top: 24px;
}
> h3 {
margin: 0;
color: $secondary-fg-color;
font-size: $font-12px;
font-weight: $font-semi-bold;
line-height: $font-15px;
}
.mx_ManageRestrictedJoinRuleDialog_entry {
display: flex;
margin-top: 12px;
> div {
flex-grow: 1;
}
img.mx_RoomAvatar_isSpaceRoom,
.mx_RoomAvatar_isSpaceRoom img {
border-radius: 4px;
}
.mx_ManageRestrictedJoinRuleDialog_entry_name {
margin: 0 8px;
font-size: $font-15px;
line-height: 30px;
flex-grow: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.mx_ManageRestrictedJoinRuleDialog_entry_description {
margin-top: 8px;
font-size: $font-12px;
line-height: $font-15px;
color: $tertiary-fg-color;
}
.mx_Checkbox {
align-items: center;
}
}
}
.mx_ManageRestrictedJoinRuleDialog_section_spaces {
.mx_BaseAvatar {
margin-right: 12px;
}
.mx_BaseAvatar_image {
border-radius: 8px;
}
}
.mx_ManageRestrictedJoinRuleDialog_section_info {
position: relative;
border-radius: 8px;
margin: 12px 0;
padding: 8px 8px 8px 42px;
background-color: $header-panel-bg-color;
font-size: $font-12px;
line-height: $font-15px;
color: $secondary-fg-color;
&::before {
content: '';
position: absolute;
left: 10px;
top: calc(50% - 8px); // vertical centering
height: 16px;
width: 16px;
background-color: $secondary-fg-color;
mask-repeat: no-repeat;
mask-size: contain;
mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
mask-position: center;
}
}
.mx_ManageRestrictedJoinRuleDialog_footer {
margin-top: 20px;
.mx_ManageRestrictedJoinRuleDialog_footer_buttons {
display: flex;
width: max-content;
margin-left: auto;
.mx_AccessibleButton {
display: inline-block;
& + .mx_AccessibleButton {
margin-left: 24px;
}
}
}
}
}

View file

@ -72,7 +72,7 @@ limitations under the License.
.mx_AccessibleButton_kind_danger_outline {
color: $button-danger-bg-color;
background-color: $button-secondary-bg-color;
background-color: transparent;
border: 1px solid $button-danger-bg-color;
}

View file

@ -0,0 +1,40 @@
/*
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_DialPadBackspaceButton {
position: relative;
height: 28px;
width: 28px;
&::before {
/* force this element to appear on the DOM */
content: "";
background-color: #8D97A5;
width: inherit;
height: inherit;
top: 0px;
left: 0px;
position: absolute;
display: inline-block;
vertical-align: middle;
mask-image: url('$(res)/img/element-icons/call/delete.svg');
mask-position: 8px;
mask-size: 20px;
mask-repeat: no-repeat;
}
}

View file

@ -27,7 +27,7 @@ limitations under the License.
display: flex;
align-items: center;
position: relative;
border-radius: 3px;
border-radius: 4px;
border: 1px solid $strong-input-border-color;
font-size: $font-12px;
user-select: none;
@ -109,7 +109,7 @@ input.mx_Dropdown_option:focus {
z-index: 2;
margin: 0;
padding: 0px;
border-radius: 3px;
border-radius: 4px;
border: 1px solid $input-focused-border-color;
background-color: $primary-bg-color;
max-height: 200px;

View file

@ -14,6 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
$button-size: 32px;
$icon-size: 22px;
$button-gap: 24px;
.mx_ImageView {
display: flex;
width: 100%;
@ -66,16 +70,17 @@ limitations under the License.
pointer-events: initial;
display: flex;
align-items: center;
gap: calc($button-gap - ($button-size - $icon-size));
}
.mx_ImageView_button {
margin-left: 24px;
padding: calc(($button-size - $icon-size) / 2);
display: block;
&::before {
content: '';
height: 22px;
width: 22px;
height: $icon-size;
width: $icon-size;
mask-repeat: no-repeat;
mask-size: contain;
mask-position: center;
@ -109,11 +114,12 @@ limitations under the License.
}
.mx_ImageView_button_close {
padding: calc($button-size - $button-size);
border-radius: 100%;
background: #21262c; // same on all themes
&::before {
width: 32px;
height: 32px;
width: $button-size;
height: $button-size;
mask-image: url('$(res)/img/image-view/close.svg');
mask-size: 40%;
}

View file

@ -30,5 +30,12 @@ limitations under the License.
mask-position: center;
content: '';
vertical-align: middle;
}
.mx_InfoTooltip_icon_info::before {
mask-image: url('$(res)/img/element-icons/info.svg');
}
.mx_InfoTooltip_icon_warning::before {
mask-image: url('$(res)/img/element-icons/warning.svg');
}

View file

@ -16,22 +16,46 @@ limitations under the License.
.mx_ReplyThread {
margin-top: 0;
}
.mx_ReplyThread .mx_DateSeparator {
font-size: 1em !important;
margin-top: 0;
margin-bottom: 0;
padding-bottom: 1px;
bottom: -5px;
}
margin-left: 0;
margin-right: 0;
margin-bottom: 8px;
padding: 0 10px;
border-left: 2px solid $button-bg-color;
border-radius: 2px;
.mx_ReplyThread_show {
cursor: pointer;
}
blockquote.mx_ReplyThread {
margin-left: 0;
padding-left: 10px;
border-left: 4px solid $blockquote-bar-color;
&.mx_ReplyThread_color1 {
border-left-color: $username-variant1-color;
}
&.mx_ReplyThread_color2 {
border-left-color: $username-variant2-color;
}
&.mx_ReplyThread_color3 {
border-left-color: $username-variant3-color;
}
&.mx_ReplyThread_color4 {
border-left-color: $username-variant4-color;
}
&.mx_ReplyThread_color5 {
border-left-color: $username-variant5-color;
}
&.mx_ReplyThread_color6 {
border-left-color: $username-variant6-color;
}
&.mx_ReplyThread_color7 {
border-left-color: $username-variant7-color;
}
&.mx_ReplyThread_color8 {
border-left-color: $username-variant8-color;
}
}

View file

@ -46,7 +46,7 @@ limitations under the License.
width: $font-16px;
}
> input[type=radio] {
input[type=radio] {
// Remove the OS's representation
margin: 0;
padding: 0;
@ -112,6 +112,12 @@ limitations under the License.
}
}
}
.mx_RadioButton_innerLabel {
display: flex;
position: relative;
top: 4px;
}
}
.mx_RadioButton_outlined {

View file

@ -0,0 +1,77 @@
/*
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_TagComposer {
.mx_TagComposer_input {
display: flex;
.mx_Field {
flex: 1;
margin: 0; // override from field styles
}
.mx_AccessibleButton {
min-width: 70px;
padding: 0; // override from button styles
margin-left: 16px; // distance from <Field>
}
.mx_Field, .mx_Field input, .mx_AccessibleButton {
// So they look related to each other by feeling the same
border-radius: 8px;
}
}
.mx_TagComposer_tags {
display: flex;
flex-wrap: wrap;
margin-top: 12px; // this plus 12px from the tags makes 24px from the input
.mx_TagComposer_tag {
padding: 6px 8px 8px 12px;
position: relative;
margin-right: 12px;
margin-top: 12px;
// Cheaty way to get an opacified variable colour background
&::before {
content: '';
border-radius: 20px;
background-color: $tertiary-fg-color;
opacity: 0.15;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
// Pass through the pointer otherwise we have effectively put a whole div
// on top of the component, which makes it hard to interact with buttons.
pointer-events: none;
}
}
.mx_AccessibleButton {
background-image: url('$(res)/img/subtract.svg');
width: 16px;
height: 16px;
margin-left: 8px;
display: inline-block;
vertical-align: middle;
cursor: pointer;
}
}
}

View file

@ -0,0 +1,154 @@
/*
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
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_CallEvent {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background-color: $dark-panel-bg-color;
border-radius: 8px;
margin: 10px auto;
max-width: 75%;
box-sizing: border-box;
height: 60px;
&.mx_CallEvent_voice {
.mx_CallEvent_type_icon::before,
.mx_CallEvent_content_button_callBack span::before,
.mx_CallEvent_content_button_answer span::before {
mask-image: url('$(res)/img/element-icons/call/voice-call.svg');
}
}
&.mx_CallEvent_video {
.mx_CallEvent_type_icon::before,
.mx_CallEvent_content_button_callBack span::before,
.mx_CallEvent_content_button_answer span::before {
mask-image: url('$(res)/img/element-icons/call/video-call.svg');
}
}
.mx_CallEvent_info {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 12px;
.mx_CallEvent_info_basic {
display: flex;
flex-direction: column;
margin-left: 10px; // To match mx_CallEvent
.mx_CallEvent_sender {
font-weight: 600;
font-size: 1.5rem;
line-height: 1.8rem;
margin-bottom: 3px;
}
.mx_CallEvent_type {
font-weight: 400;
color: $secondary-fg-color;
font-size: 1.2rem;
line-height: $font-13px;
display: flex;
align-items: center;
.mx_CallEvent_type_icon {
height: 13px;
width: 13px;
margin-right: 5px;
&::before {
content: '';
position: absolute;
height: 13px;
width: 13px;
background-color: $tertiary-fg-color;
mask-repeat: no-repeat;
mask-size: contain;
}
}
}
}
}
.mx_CallEvent_content {
display: flex;
flex-direction: row;
align-items: center;
color: $secondary-fg-color;
margin-right: 16px;
.mx_CallEvent_content_button {
height: 24px;
padding: 0px 12px;
margin-left: 8px;
span {
padding: 8px 0;
display: flex;
align-items: center;
&::before {
content: '';
display: inline-block;
background-color: $button-fg-color;
mask-position: center;
mask-repeat: no-repeat;
mask-size: 16px;
width: 16px;
height: 16px;
margin-right: 8px;
}
}
}
.mx_CallEvent_content_button_reject span::before {
mask-image: url('$(res)/img/element-icons/call/hangup.svg');
}
.mx_CallEvent_content_tooltip {
margin-right: 5px;
}
.mx_CallEvent_iconButton {
display: inline-flex;
margin-right: 8px;
&::before {
content: '';
height: 16px;
width: 16px;
background-color: $tertiary-fg-color;
mask-repeat: no-repeat;
mask-size: contain;
mask-position: center;
}
}
.mx_CallEvent_silence::before {
mask-image: url('$(res)/img/voip/silence.svg');
}
.mx_CallEvent_unSilence::before {
mask-image: url('$(res)/img/voip/un-silence.svg');
}
}
}

View file

@ -60,12 +60,6 @@ limitations under the License.
}
.mx_MFileBody_info {
background-color: $message-body-panel-bg-color;
border-radius: 12px;
width: 243px; // same width as a playable voice message, accounting for padding
padding: 6px 12px;
color: $message-body-panel-fg-color;
.mx_MFileBody_info_icon {
background-color: $message-body-panel-icon-bg-color;
border-radius: 20px;
@ -83,12 +77,12 @@ limitations under the License.
mask-size: cover;
mask-image: url('$(res)/img/element-icons/room/composer/attach.svg');
background-color: $message-body-panel-icon-fg-color;
width: 13px;
width: 15px;
height: 15px;
position: absolute;
top: 8px;
left: 9px;
left: 8px;
}
}

View file

@ -16,20 +16,15 @@ limitations under the License.
$timelineImageBorderRadius: 4px;
.mx_MImageBody {
display: block;
margin-right: 34px;
}
.mx_MImageBody_thumbnail {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
object-fit: contain;
border-radius: $timelineImageBorderRadius;
> canvas {
display: flex;
justify-content: center;
align-items: center;
> div > canvas {
border-radius: $timelineImageBorderRadius;
}
}
@ -43,17 +38,6 @@ $timelineImageBorderRadius: 4px;
position: relative;
}
.mx_MImageBody_thumbnail_spinner {
position: absolute;
left: 50%;
top: 50%;
}
// Inner img should be centered around 0, 0
.mx_MImageBody_thumbnail_spinner > * {
transform: translate(-50%, -50%);
}
.mx_MImageBody_gifLabel {
position: absolute;
display: block;

View file

@ -0,0 +1,37 @@
/*
Copyright 2020 Tulir Asokan <tulir@maunium.net>
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_MImageReplyBody {
display: flex;
.mx_MImageBody_thumbnail_container {
flex: 1;
margin-right: 4px;
}
.mx_MImageReplyBody_info {
flex: 1;
.mx_MImageReplyBody_sender {
grid-area: sender;
}
.mx_MImageReplyBody_filename {
grid-area: filename;
}
}
}

View file

@ -20,9 +20,11 @@ limitations under the License.
.mx_MediaBody {
background-color: $message-body-panel-bg-color;
border-radius: 12px;
max-width: 243px; // use max-width instead of width so it fits within right panels
color: $message-body-panel-fg-color;
font-size: $font-14px;
line-height: $font-24px;
}
padding: 6px 12px;
}

View file

@ -107,3 +107,12 @@ limitations under the License.
.mx_MessageActionBar_cancelButton::after {
mask-image: url('$(res)/img/element-icons/trashcan.svg');
}
.mx_MessageActionBar_downloadButton::after {
mask-size: 14px;
mask-image: url('$(res)/img/download.svg');
}
.mx_MessageActionBar_downloadButton.mx_MessageActionBar_downloadSpinnerButton::after {
background-color: transparent; // hide the download icon mask
}

View file

@ -26,6 +26,7 @@ limitations under the License.
height: 24px;
vertical-align: middle;
margin-left: 4px;
margin-right: 4px;
&::before {
content: '';

View file

@ -0,0 +1,336 @@
/*
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_EventTile[data-layout=bubble],
.mx_EventTile[data-layout=bubble] ~ .mx_EventListSummary {
--avatarSize: 32px;
--gutterSize: 11px;
--cornerRadius: 12px;
--maxWidth: 70%;
}
.mx_EventTile[data-layout=bubble] {
position: relative;
margin-top: var(--gutterSize);
margin-left: 50px;
margin-right: 100px;
&.mx_EventTile_continuation {
margin-top: 2px;
}
/* For replies */
.mx_EventTile {
padding-top: 0;
}
&:hover,
&.mx_EventTile_selected {
&::before {
content: '';
position: absolute;
top: -1px;
bottom: -1px;
left: -60px;
right: -60px;
z-index: -1;
background: $eventbubble-bg-hover;
border-radius: 4px;
}
.mx_EventTile_avatar {
img {
box-shadow: 0 0 0 3px $eventbubble-bg-hover;
}
}
}
.mx_SenderProfile,
.mx_EventTile_line {
width: fit-content;
max-width: 70%;
}
.mx_SenderProfile {
position: relative;
top: -2px;
left: 2px;
}
&[data-self=false] {
.mx_EventTile_line {
border-bottom-right-radius: var(--cornerRadius);
}
.mx_EventTile_avatar {
left: -34px;
}
.mx_MessageActionBar {
right: 0;
transform: translate3d(90%, 50%, 0);
}
--backgroundColor: $eventbubble-others-bg;
}
&[data-self=true] {
.mx_EventTile_line {
border-bottom-left-radius: var(--cornerRadius);
float: right;
> a {
left: auto;
right: -68px;
}
}
.mx_SenderProfile {
display: none;
}
.mx_ReplyTile .mx_SenderProfile {
display: block;
}
.mx_ReactionsRow {
float: right;
clear: right;
display: flex;
/* Moving the "add reaction button" before the reactions */
> :last-child {
order: -1;
}
}
.mx_EventTile_avatar {
top: -19px; // height of the sender block
right: -35px;
}
--backgroundColor: $eventbubble-self-bg;
}
.mx_EventTile_line {
position: relative;
padding: var(--gutterSize);
border-top-left-radius: var(--cornerRadius);
border-top-right-radius: var(--cornerRadius);
background: var(--backgroundColor);
display: flex;
gap: 5px;
margin: 0 -12px 0 -9px;
> a {
position: absolute;
padding: 10px 20px;
top: 0;
left: -68px;
}
}
&.mx_EventTile_continuation[data-self=false] .mx_EventTile_line {
border-top-left-radius: 0;
}
&.mx_EventTile_lastInSection[data-self=false] .mx_EventTile_line {
border-bottom-left-radius: var(--cornerRadius);
}
&.mx_EventTile_continuation[data-self=true] .mx_EventTile_line {
border-top-right-radius: 0;
}
&.mx_EventTile_lastInSection[data-self=true] .mx_EventTile_line {
border-bottom-right-radius: var(--cornerRadius);
}
.mx_EventTile_avatar {
position: absolute;
top: 0;
img {
box-shadow: 0 0 0 3px $eventbubble-avatar-outline;
border-radius: 50%;
}
}
.mx_BaseAvatar,
.mx_EventTile_avatar {
line-height: 1;
}
&[data-has-reply=true] {
> .mx_EventTile_line {
flex-direction: column;
}
.mx_ReplyThread_show {
order: 99999;
}
.mx_ReplyThread {
margin: 0 calc(-1 * var(--gutterSize));
.mx_EventTile_reply {
max-width: 90%;
padding: 0;
> a {
display: none !important;
}
}
.mx_EventTile {
display: flex;
gap: var(--gutterSize);
.mx_EventTile_avatar {
position: static;
}
.mx_SenderProfile {
display: none;
}
}
}
}
.mx_EditMessageComposer_buttons {
position: static;
padding: 0;
margin: 0;
background: transparent;
}
.mx_ReactionsRow {
margin-right: -18px;
margin-left: -9px;
}
.mx_ReplyThread {
border-left-width: 2px;
border-left-color: $eventbubble-reply-color;
}
&.mx_EventTile_bubbleContainer,
&.mx_EventTile_info,
& ~ .mx_EventListSummary[data-expanded=false] {
--backgroundColor: transparent;
--gutterSize: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 5px 0;
.mx_EventTile_avatar {
position: static;
order: -1;
margin-right: 5px;
}
}
& ~ .mx_EventListSummary {
--maxWidth: 80%;
margin-left: calc(var(--avatarSize) + var(--gutterSize));
margin-right: calc(var(--gutterSize) + var(--avatarSize));
.mx_EventListSummary_toggle {
float: none;
margin: 0;
order: 9;
margin-left: 5px;
}
.mx_EventListSummary_avatars {
padding-top: 0;
}
&::after {
content: "";
clear: both;
}
.mx_EventTile {
margin: 0 6px;
}
.mx_EventTile_line {
margin: 0 5px;
> a {
left: auto;
right: 0;
transform: translateX(calc(100% + 5px));
}
}
.mx_MessageActionBar {
transform: translate3d(90%, 0, 0);
}
}
& ~ .mx_EventListSummary[data-expanded=false] {
padding: 0 34px;
}
/* events that do not require bubble layout */
& ~ .mx_EventListSummary,
&.mx_EventTile_bad {
.mx_EventTile_line {
background: transparent;
}
&:hover {
&::before {
background: transparent;
}
}
}
& + .mx_EventListSummary {
.mx_EventTile {
margin-top: 0;
padding: 2px 0;
}
}
.mx_EventListSummary_toggle {
margin-right: 55px;
}
/* Special layout scenario for "Unable To Decrypt (UTD)" events */
&.mx_EventTile_bad > .mx_EventTile_line {
display: grid;
grid-template:
"reply reply" auto
"shield body" auto
"shield link" auto
/ auto 1fr;
.mx_EventTile_e2eIcon {
grid-area: shield;
}
.mx_UnknownBody {
grid-area: body;
}
.mx_EventTile_keyRequestInfo {
grid-area: link;
}
.mx_ReplyThread_wrapper {
grid-area: reply;
}
}
.mx_EventTile_readAvatars {
position: absolute;
right: -110px;
bottom: 0;
top: auto;
}
.mx_MTextBody {
max-width: 100%;
}
}

View file

@ -1,6 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2020-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.
@ -18,15 +18,14 @@ limitations under the License.
$left-gutter: 64px;
$hover-select-border: 4px;
.mx_EventTile {
.mx_EventTile:not([data-layout=bubble]) {
max-width: 100%;
clear: both;
padding-top: 18px;
font-size: $font-14px;
position: relative;
}
.mx_EventTile.mx_EventTile_info {
&.mx_EventTile_info {
padding-top: 1px;
}
@ -37,12 +36,12 @@ $hover-select-border: 4px;
user-select: none;
}
.mx_EventTile.mx_EventTile_info .mx_EventTile_avatar {
&.mx_EventTile_info .mx_EventTile_avatar {
top: $font-6px;
left: $left-gutter;
}
.mx_EventTile_continuation {
&.mx_EventTile_continuation {
padding-top: 0px !important;
&.mx_EventTile_isEditing {
@ -51,11 +50,11 @@ $hover-select-border: 4px;
}
}
.mx_EventTile_isEditing {
&.mx_EventTile_isEditing {
background-color: $header-panel-bg-color;
}
.mx_EventTile .mx_SenderProfile {
.mx_SenderProfile {
color: $primary-fg-color;
font-size: $font-14px;
display: inline-block; /* anti-zalgo, with overflow hidden */
@ -70,7 +69,7 @@ $hover-select-border: 4px;
max-width: calc(100% - $left-gutter);
}
.mx_EventTile .mx_SenderProfile .mx_Flair {
.mx_SenderProfile .mx_Flair {
opacity: 0.7;
margin-left: 5px;
display: inline-block;
@ -85,11 +84,11 @@ $hover-select-border: 4px;
}
}
.mx_EventTile_isEditing .mx_MessageTimestamp {
&.mx_EventTile_isEditing .mx_MessageTimestamp {
visibility: hidden;
}
.mx_EventTile .mx_MessageTimestamp {
.mx_MessageTimestamp {
display: block;
white-space: nowrap;
left: 0px;
@ -97,7 +96,7 @@ $hover-select-border: 4px;
user-select: none;
}
.mx_EventTile_continuation .mx_EventTile_line {
&.mx_EventTile_continuation .mx_EventTile_line {
clear: both;
}
@ -107,63 +106,25 @@ $hover-select-border: 4px;
border-radius: 8px;
}
.mx_RoomView_timeline_rr_enabled,
// on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter
.mx_EventListSummary {
.mx_EventTile_line {
/* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */
margin-right: 110px;
}
}
.mx_EventTile_bubbleContainer {
display: grid;
grid-template-columns: 1fr 100px;
.mx_EventTile_line {
margin-right: 0;
grid-column: 1 / 3;
// override default padding of mx_EventTile_line so that we can be centered
padding: 0 !important;
}
.mx_EventTile_msgOption {
grid-column: 2;
}
}
.mx_EventTile_reply {
margin-right: 10px;
}
/* HACK to override line-height which is already marked important elsewhere */
.mx_EventTile_bigEmoji.mx_EventTile_bigEmoji {
font-size: 48px !important;
line-height: 57px !important;
}
.mx_EventTile_selected > div > a > .mx_MessageTimestamp {
&.mx_EventTile_selected > div > a > .mx_MessageTimestamp {
left: calc(-$hover-select-border);
}
.mx_EventTile:hover .mx_MessageActionBar,
.mx_EventTile.mx_EventTile_actionBarFocused .mx_MessageActionBar,
[data-whatinput='keyboard'] .mx_EventTile:focus-within .mx_MessageActionBar,
.mx_EventTile.focus-visible:focus-within .mx_MessageActionBar {
visibility: visible;
}
/* this is used for the tile for the event which is selected via the URL.
* TODO: ultimately we probably want some transition on here.
*/
.mx_EventTile_selected > .mx_EventTile_line {
&.mx_EventTile_selected > .mx_EventTile_line {
border-left: $accent-color 4px solid;
padding-left: calc($left-gutter - $hover-select-border);
background-color: $event-selected-color;
}
.mx_EventTile_highlight,
.mx_EventTile_highlight .markdown-body {
&.mx_EventTile_highlight,
&.mx_EventTile_highlight .markdown-body {
color: $event-highlight-fg-color;
.mx_EventTile_line {
@ -171,17 +132,22 @@ $hover-select-border: 4px;
}
}
.mx_EventTile_info .mx_EventTile_line {
&.mx_EventTile_info .mx_EventTile_line,
& ~ .mx_EventListSummary .mx_EventTile_avatar ~ .mx_EventTile_line {
padding-left: calc($left-gutter + 18px);
}
.mx_EventTile_selected.mx_EventTile_info .mx_EventTile_line {
& ~ .mx_EventListSummary .mx_EventTile_line {
padding-left: calc($left-gutter);
}
&.mx_EventTile_selected.mx_EventTile_info .mx_EventTile_line {
padding-left: calc($left-gutter + 18px - $hover-select-border);
}
.mx_EventTile:hover .mx_EventTile_line,
.mx_EventTile.mx_EventTile_actionBarFocused .mx_EventTile_line,
.mx_EventTile.focus-visible:focus-within .mx_EventTile_line {
&.mx_EventTile:hover .mx_EventTile_line,
&.mx_EventTile.mx_EventTile_actionBarFocused .mx_EventTile_line,
&.mx_EventTile.focus-visible:focus-within .mx_EventTile_line {
background-color: $event-selected-color;
}
@ -225,7 +191,7 @@ $hover-select-border: 4px;
mask-image: url('$(res)/img/element-icons/circle-sending.svg');
}
.mx_EventTile_contextual {
&.mx_EventTile_contextual {
opacity: 0.4;
}
@ -247,6 +213,138 @@ $hover-select-border: 4px;
text-decoration: none;
}
/* De-zalgoing */
.mx_EventTile_body {
overflow-y: hidden;
}
&:hover.mx_EventTile_verified .mx_EventTile_line,
&:hover.mx_EventTile_unverified .mx_EventTile_line,
&:hover.mx_EventTile_unknown .mx_EventTile_line {
padding-left: calc($left-gutter - $hover-select-border);
}
&:hover.mx_EventTile_verified .mx_EventTile_line {
border-left: $e2e-verified-color $EventTile_e2e_state_indicator_width solid;
}
&:hover.mx_EventTile_unverified .mx_EventTile_line {
border-left: $e2e-unverified-color $EventTile_e2e_state_indicator_width solid;
}
&:hover.mx_EventTile_unknown .mx_EventTile_line {
border-left: $e2e-unknown-color $EventTile_e2e_state_indicator_width solid;
}
&:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line,
&:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line,
&:hover.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line {
padding-left: calc($left-gutter + 18px - $hover-select-border);
}
/* End to end encryption stuff */
&:hover .mx_EventTile_e2eIcon {
opacity: 1;
}
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
&:hover.mx_EventTile_verified .mx_EventTile_line > a > .mx_MessageTimestamp,
&:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp,
&:hover.mx_EventTile_unknown .mx_EventTile_line > a > .mx_MessageTimestamp {
left: calc(-$hover-select-border);
}
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
&:hover.mx_EventTile_verified .mx_EventTile_line > .mx_EventTile_e2eIcon,
&:hover.mx_EventTile_unverified .mx_EventTile_line > .mx_EventTile_e2eIcon,
&:hover.mx_EventTile_unknown .mx_EventTile_line > .mx_EventTile_e2eIcon {
display: block;
left: 41px;
}
.mx_MImageBody {
margin-right: 34px;
}
.mx_EventTile_e2eIcon {
position: absolute;
top: 6px;
left: 44px;
bottom: 0;
right: 0;
}
.mx_ReactionsRow {
margin: 0;
padding: 6px 60px;
}
}
/* all the overflow-y: hidden; are to trap Zalgos -
but they introduce an implicit overflow-x: auto.
so make that explicitly hidden too to avoid random
horizontal scrollbars occasionally appearing, like in
https://github.com/vector-im/vector-web/issues/1154 */
.mx_EventTile_content {
overflow-y: hidden;
overflow-x: hidden;
margin-right: 34px;
}
/* Spoiler stuff */
.mx_EventTile_spoiler {
cursor: pointer;
}
.mx_EventTile_spoiler_reason {
color: $event-timestamp-color;
font-size: $font-11px;
}
.mx_EventTile_spoiler_content {
filter: blur(5px) saturate(0.1) sepia(1);
transition-duration: 0.5s;
}
.mx_EventTile_spoiler.visible > .mx_EventTile_spoiler_content {
filter: none;
}
.mx_RoomView_timeline_rr_enabled {
.mx_EventTile:not([data-layout=bubble]) {
.mx_EventTile_line {
/* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */
margin-right: 110px;
}
}
// on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter
}
.mx_EventTile_bubbleContainer {
display: grid;
grid-template-columns: 1fr 100px;
.mx_EventTile_line {
margin-right: 0;
grid-column: 1 / 3;
// override default padding of mx_EventTile_line so that we can be centered
padding: 0 !important;
}
.mx_EventTile_msgOption {
grid-column: 2;
}
&:hover {
.mx_EventTile_line {
// To avoid bubble events being highlighted
background-color: inherit !important;
}
}
}
.mx_EventTile_readAvatars {
position: relative;
display: inline-block;
@ -277,52 +375,27 @@ $hover-select-border: 4px;
position: absolute;
}
/* all the overflow-y: hidden; are to trap Zalgos -
but they introduce an implicit overflow-x: auto.
so make that explicitly hidden too to avoid random
horizontal scrollbars occasionally appearing, like in
https://github.com/vector-im/vector-web/issues/1154
*/
.mx_EventTile_content {
display: block;
overflow-y: hidden;
overflow-x: hidden;
margin-right: 34px;
/* HACK to override line-height which is already marked important elsewhere */
.mx_EventTile_bigEmoji.mx_EventTile_bigEmoji {
font-size: 48px !important;
line-height: 57px !important;
}
/* De-zalgoing */
.mx_EventTile_body {
overflow-y: hidden;
}
/* Spoiler stuff */
.mx_EventTile_spoiler {
.mx_EventTile_content .mx_EventTile_edited {
user-select: none;
font-size: $font-12px;
color: $roomtopic-color;
display: inline-block;
margin-left: 9px;
cursor: pointer;
}
.mx_EventTile_spoiler_reason {
color: $event-timestamp-color;
font-size: $font-11px;
}
.mx_EventTile_spoiler_content {
filter: blur(5px) saturate(0.1) sepia(1);
transition-duration: 0.5s;
}
.mx_EventTile_spoiler.visible > .mx_EventTile_spoiler_content {
filter: none;
}
.mx_EventTile_e2eIcon {
position: absolute;
top: 6px;
left: 44px;
position: relative;
width: 14px;
height: 14px;
display: block;
bottom: 0;
right: 0;
opacity: 0.2;
background-repeat: no-repeat;
background-size: contain;
@ -381,87 +454,6 @@ $hover-select-border: 4px;
opacity: 1;
}
.mx_EventTile_keyRequestInfo {
font-size: $font-12px;
}
.mx_EventTile_keyRequestInfo_text {
opacity: 0.5;
}
.mx_EventTile_keyRequestInfo_text a {
color: $primary-fg-color;
text-decoration: underline;
cursor: pointer;
}
.mx_EventTile_keyRequestInfo_tooltip_contents p {
text-align: auto;
margin-left: 3px;
margin-right: 3px;
}
.mx_EventTile_keyRequestInfo_tooltip_contents p:first-child {
margin-top: 0px;
}
.mx_EventTile_keyRequestInfo_tooltip_contents p:last-child {
margin-bottom: 0px;
}
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line,
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line,
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line {
padding-left: calc($left-gutter - $hover-select-border);
}
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line {
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 $EventTile_e2e_state_indicator_width solid;
}
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line {
border-left: $e2e-unknown-color $EventTile_e2e_state_indicator_width solid;
}
.mx_EventTile:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line,
.mx_EventTile:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line,
.mx_EventTile:hover.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line {
padding-left: calc($left-gutter + 18px - $hover-select-border);
}
/* End to end encryption stuff */
.mx_EventTile:hover .mx_EventTile_e2eIcon {
opacity: 1;
}
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
.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: calc(-$hover-select-border);
}
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > .mx_EventTile_e2eIcon,
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > .mx_EventTile_e2eIcon,
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line > .mx_EventTile_e2eIcon {
display: block;
left: 41px;
}
.mx_EventTile_content .mx_EventTile_edited {
user-select: none;
font-size: $font-12px;
color: $roomtopic-color;
display: inline-block;
margin-left: 9px;
cursor: pointer;
}
/* Various markdown overrides */
.mx_EventTile_body pre {
@ -477,8 +469,11 @@ $hover-select-border: 4px;
pre, code {
font-family: $monospace-font-family !important;
// deliberate constants as we're behind an invert filter
color: #333;
background-color: $header-panel-bg-color;
}
pre code > * {
display: inline-block;
}
pre {
@ -488,11 +483,6 @@ $hover-select-border: 4px;
overflow-x: overlay;
overflow-y: visible;
}
code {
// deliberate constants as we're behind an invert filter
background-color: #f8f8f8;
}
}
.mx_EventTile_lineNumbers {
@ -601,6 +591,35 @@ $hover-select-border: 4px;
/* end of overrides */
.mx_EventTile_keyRequestInfo {
font-size: $font-12px;
}
.mx_EventTile_keyRequestInfo_text {
opacity: 0.5;
}
.mx_EventTile_keyRequestInfo_text a {
color: $primary-fg-color;
text-decoration: underline;
cursor: pointer;
}
.mx_EventTile_keyRequestInfo_tooltip_contents p {
text-align: auto;
margin-left: 3px;
margin-right: 3px;
}
.mx_EventTile_keyRequestInfo_tooltip_contents p:first-child {
margin-top: 0px;
}
.mx_EventTile_keyRequestInfo_tooltip_contents p:last-child {
margin-bottom: 0px;
}
.mx_EventTile_tileError {
color: red;
text-align: center;
@ -621,6 +640,13 @@ $hover-select-border: 4px;
}
}
.mx_EventTile:hover .mx_MessageActionBar,
.mx_EventTile.mx_EventTile_actionBarFocused .mx_MessageActionBar,
[data-whatinput='keyboard'] .mx_EventTile:focus-within .mx_MessageActionBar,
.mx_EventTile.focus-visible:focus-within .mx_MessageActionBar {
visibility: visible;
}
@media only screen and (max-width: 480px) {
.mx_EventTile_line, .mx_EventTile_reply {
padding-left: 0;

View file

@ -26,6 +26,7 @@ $left-gutter: 64px;
> .mx_EventTile_avatar {
position: absolute;
z-index: 9;
}
.mx_MessageTimestamp {

View file

@ -198,8 +198,9 @@ $irc-line-height: $font-18px;
.mx_ReplyThread {
margin: 0;
.mx_SenderProfile {
order: unset;
max-width: unset;
width: unset;
max-width: var(--name-width);
background: transparent;
}

View file

@ -0,0 +1,38 @@
/*
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_LinkPreviewGroup {
.mx_LinkPreviewGroup_hide {
cursor: pointer;
width: 18px;
height: 18px;
img {
flex: 0 0 40px;
visibility: hidden;
}
}
&:hover .mx_LinkPreviewGroup_hide img,
.mx_LinkPreviewGroup_hide.focus-visible:focus img {
visibility: visible;
}
> .mx_AccessibleButton {
color: $accent-color;
text-align: center;
}
}

View file

@ -19,7 +19,8 @@ limitations under the License.
margin-right: 15px;
margin-bottom: 15px;
display: flex;
border-left: 4px solid $preview-widget-bar-color;
border-left: 2px solid $preview-widget-bar-color;
border-radius: 2px;
color: $preview-widget-fg-color;
}
@ -33,38 +34,29 @@ limitations under the License.
.mx_LinkPreviewWidget_caption {
margin-left: 15px;
flex: 1 1 auto;
overflow: hidden; // cause it to wrap rather than clip
}
.mx_LinkPreviewWidget_title {
display: inline;
font-weight: bold;
white-space: normal;
}
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
.mx_LinkPreviewWidget_siteName {
display: inline;
font-weight: normal;
}
}
.mx_LinkPreviewWidget_description {
margin-top: 8px;
white-space: normal;
word-wrap: break-word;
}
.mx_LinkPreviewWidget_cancel {
cursor: pointer;
width: 18px;
height: 18px;
img {
flex: 0 0 40px;
visibility: hidden;
}
}
.mx_LinkPreviewWidget:hover .mx_LinkPreviewWidget_cancel img,
.mx_LinkPreviewWidget_cancel.focus-visible:focus img {
visibility: visible;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.mx_MatrixChat_useCompactLayout {

View file

@ -22,19 +22,21 @@ limitations under the License.
max-height: 50vh;
overflow: auto;
box-shadow: 0px -16px 32px $composer-shadow-color;
}
.mx_ReplyPreview_section {
border-bottom: 1px solid $primary-hairline-color;
}
.mx_ReplyPreview_header {
margin: 12px;
margin: 8px;
color: $primary-fg-color;
font-weight: 400;
opacity: 0.4;
}
.mx_ReplyPreview_tile {
margin: 0 8px;
}
.mx_ReplyPreview_title {
float: left;
}
@ -42,8 +44,12 @@ limitations under the License.
.mx_ReplyPreview_cancel {
float: right;
cursor: pointer;
display: flex;
}
.mx_ReplyPreview_clear {
clear: both;
}
}
}

View file

@ -0,0 +1,119 @@
/*
Copyright 2020 Tulir Asokan <tulir@maunium.net>
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_ReplyTile {
position: relative;
padding: 2px 0;
font-size: $font-14px;
line-height: $font-16px;
&.mx_ReplyTile_audio .mx_MFileBody_info_icon::before {
mask-image: url("$(res)/img/element-icons/speaker.svg");
}
&.mx_ReplyTile_video .mx_MFileBody_info_icon::before {
mask-image: url("$(res)/img/element-icons/call/video-call.svg");
}
.mx_MFileBody {
.mx_MFileBody_info {
margin: 5px 0;
}
.mx_MFileBody_download {
display: none;
}
}
> a {
display: flex;
flex-direction: column;
text-decoration: none;
color: $primary-fg-color;
}
.mx_RedactedBody {
padding: 4px 0 2px 20px;
&::before {
height: 13px;
width: 13px;
top: 5px;
}
}
// We do reply size limiting with CSS to avoid duplicating the TextualBody component.
.mx_EventTile_content {
$reply-lines: 2;
$line-height: $font-22px;
pointer-events: none;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: $reply-lines;
line-height: $line-height;
.mx_EventTile_body.mx_EventTile_bigEmoji {
line-height: $line-height !important;
font-size: $font-14px !important; // Override the big emoji override
}
// Hide line numbers
.mx_EventTile_lineNumbers {
display: none;
}
// Hack to cut content in <pre> tags too
.mx_EventTile_pre_container > pre {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: $reply-lines;
padding: 4px;
}
.markdown-body blockquote,
.markdown-body dl,
.markdown-body ol,
.markdown-body p,
.markdown-body pre,
.markdown-body table,
.markdown-body ul {
margin-bottom: 4px;
}
}
&.mx_ReplyTile_info {
padding-top: 0;
}
.mx_SenderProfile {
font-size: $font-14px;
line-height: $font-17px;
display: inline-block; // anti-zalgo, with overflow hidden
padding: 0;
margin: 0;
// truncate long display names
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}

View file

@ -193,6 +193,10 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/settings.svg');
}
.mx_RoomTile_iconCopyLink::before {
mask-image: url('$(res)/img/element-icons/link.svg');
}
.mx_RoomTile_iconInvite::before {
mask-image: url('$(res)/img/element-icons/room/invite.svg');
}

View file

@ -29,8 +29,10 @@ limitations under the License.
display: flex;
flex-direction: column;
// min-height at this level so the mx_BasicMessageComposer_input
// still stays vertically centered when less than 50px
min-height: 50px;
// still stays vertically centered when less than 55px.
// We also set this to ensure the voice message recording widget
// doesn't cause a jump.
min-height: 55px;
.mx_BasicMessageComposer_input {
padding: 3px 0;

View file

@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,82 +14,79 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_UserNotifSettings_tableRow {
display: table-row;
}
.mx_UserNotifSettings_inputCell {
display: table-cell;
padding-bottom: 8px;
padding-right: 8px;
width: 16px;
}
.mx_UserNotifSettings_labelCell {
padding-bottom: 8px;
width: 400px;
display: table-cell;
}
.mx_UserNotifSettings_pushRulesTableWrapper {
padding-bottom: 8px;
}
.mx_UserNotifSettings {
color: $primary-fg-color; // override from default settings page styles
.mx_UserNotifSettings_pushRulesTable {
width: 100%;
width: calc(100% + 12px); // +12px to line up center of 'Noisy' column with toggle switches
table-layout: fixed;
border-collapse: collapse;
border-spacing: 0;
margin-top: 40px;
tr > th {
font-weight: $font-semi-bold;
}
.mx_UserNotifSettings_pushRulesTable thead {
font-weight: bold;
}
.mx_UserNotifSettings_pushRulesTable tbody th {
font-weight: 400;
}
.mx_UserNotifSettings_pushRulesTable tbody th:first-child {
tr > th:first-child {
text-align: left;
font-size: $font-18px;
}
.mx_UserNotifSettings_keywords {
cursor: pointer;
color: $accent-color;
tr > th:nth-child(n + 2) {
color: $secondary-fg-color;
font-size: $font-12px;
vertical-align: middle;
width: 66px;
}
.mx_UserNotifSettings_devicesTable td {
padding-left: 20px;
padding-right: 20px;
tr > td:nth-child(n + 2) {
text-align: center;
}
.mx_UserNotifSettings_notifTable {
display: table;
position: relative;
tr > td {
padding-top: 8px;
}
.mx_UserNotifSettings_notifTable .mx_Spinner {
position: absolute;
}
// Override StyledRadioButton default styles
.mx_RadioButton {
justify-content: center;
.mx_NotificationSound_soundUpload {
.mx_RadioButton_content {
display: none;
}
.mx_NotificationSound_browse {
color: $accent-color;
border: 1px solid $accent-color;
background-color: transparent;
.mx_RadioButton_spacer {
display: none;
}
}
}
.mx_NotificationSound_save {
margin-left: 5px;
color: white;
background-color: $accent-color;
.mx_UserNotifSettings_floatingSection {
margin-top: 40px;
& > div:first-child { // section header
font-size: $font-18px;
font-weight: $font-semi-bold;
}
.mx_NotificationSound_resetSound {
margin-top: 5px;
color: white;
border: $warning-color;
background-color: $warning-color;
> table {
border-collapse: collapse;
border-spacing: 0;
margin-top: 8px;
tr > td:first-child {
// Just for a bit of spacing
padding-right: 8px;
}
}
}
.mx_UserNotifSettings_clearNotifsButton {
margin-top: 8px;
}
.mx_TagComposer {
margin-top: 35px; // lots of distance from the last line of the table
}
}

View file

@ -47,14 +47,14 @@ limitations under the License.
color: $settings-subsection-fg-color;
font-size: $font-14px;
display: block;
margin: 10px 100px 10px 0; // Align with the rest of the view
margin: 10px 80px 10px 0; // Align with the rest of the view
}
.mx_SettingsTab_section {
margin-bottom: 24px;
.mx_SettingsFlag {
margin-right: 100px;
margin-right: 80px;
margin-bottom: 10px;
}

View file

@ -14,6 +14,44 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_SecurityRoomSettingsTab {
.mx_SettingsTab_showAdvanced {
padding: 0;
margin-bottom: 16px;
}
.mx_SecurityRoomSettingsTab_spacesWithAccess {
> h4 {
color: $secondary-fg-color;
font-weight: $font-semi-bold;
font-size: $font-12px;
line-height: $font-15px;
text-transform: uppercase;
}
> span {
font-weight: 500;
font-size: $font-14px;
line-height: 32px; // matches height of avatar for v-align
color: $secondary-fg-color;
display: inline-block;
img.mx_RoomAvatar_isSpaceRoom,
.mx_RoomAvatar_isSpaceRoom img {
border-radius: 8px;
}
.mx_BaseAvatar {
margin-right: 8px;
}
& + span {
margin-left: 16px;
}
}
}
}
.mx_SecurityRoomSettingsTab_warning {
display: block;
@ -26,5 +64,51 @@ limitations under the License.
}
.mx_SecurityRoomSettingsTab_encryptionSection {
margin-bottom: 25px;
padding-bottom: 24px;
border-bottom: 1px solid $menu-border-color;
margin-bottom: 32px;
}
.mx_SecurityRoomSettingsTab_upgradeRequired {
margin-left: 16px;
padding: 4px 16px;
border: 1px solid $accent-color;
border-radius: 8px;
color: $accent-color;
font-size: $font-12px;
line-height: $font-15px;
}
.mx_SecurityRoomSettingsTab_joinRule {
.mx_RadioButton {
padding-top: 16px;
margin-bottom: 8px;
.mx_RadioButton_content {
margin-left: 14px;
font-weight: $font-semi-bold;
font-size: $font-15px;
line-height: $font-24px;
color: $primary-fg-color;
display: block;
}
}
> span {
display: inline-block;
margin-left: 34px;
margin-bottom: 16px;
font-size: $font-15px;
line-height: $font-24px;
color: $secondary-fg-color;
& + .mx_RadioButton {
border-top: 1px solid $menu-border-color;
}
}
.mx_AccessibleButton_kind_link {
padding: 0;
font-size: inherit;
}
}

View file

@ -15,8 +15,7 @@ limitations under the License.
*/
.mx_AppearanceUserSettingsTab_fontSlider,
.mx_AppearanceUserSettingsTab_fontSlider_preview,
.mx_AppearanceUserSettingsTab_Layout {
.mx_AppearanceUserSettingsTab_fontSlider_preview {
@mixin mx_Settings_fullWidthField;
}
@ -45,6 +44,11 @@ limitations under the License.
border-radius: 10px;
padding: 0 16px 9px 16px;
pointer-events: none;
display: flow-root;
.mx_EventTile[data-layout=bubble] {
margin-top: 30px;
}
.mx_EventTile_msgOption {
display: none;
@ -154,13 +158,10 @@ limitations under the License.
.mx_AppearanceUserSettingsTab_Layout_RadioButtons {
display: flex;
flex-direction: row;
gap: 24px;
color: $primary-fg-color;
.mx_AppearanceUserSettingsTab_spacer {
width: 24px;
}
> .mx_AppearanceUserSettingsTab_Layout_RadioButton {
flex-grow: 0;
flex-shrink: 1;
@ -210,6 +211,21 @@ limitations under the License.
.mx_RadioButton_checked {
background-color: rgba($accent-color, 0.08);
}
.mx_EventTile {
margin: 0;
&[data-layout=bubble] {
margin-right: 40px;
}
&[data-layout=irc] {
> a {
display: none;
}
}
.mx_EventTile_line {
max-width: 90%;
}
}
}
.mx_AppearanceUserSettingsTab_Advanced {

View file

@ -30,8 +30,8 @@ limitations under the License.
pointer-events: initial; // restore pointer events so the user can leave/interact
cursor: pointer;
.mx_CallView_video {
width: 350px;
.mx_VideoFeed_remote.mx_VideoFeed_voice {
min-height: 150px;
}
.mx_VideoFeed_local {

View file

@ -0,0 +1,21 @@
/*
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
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_CallPreview {
position: fixed;
left: 0;
top: 0;
}

View file

@ -39,7 +39,6 @@ limitations under the License.
.mx_CallView_pip {
width: 320px;
padding-bottom: 8px;
margin-top: 10px;
background-color: $voipcall-plinth-color;
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20);
border-radius: 8px;

View file

@ -16,11 +16,21 @@ limitations under the License.
.mx_DialPad {
display: grid;
row-gap: 16px;
column-gap: 0px;
margin-top: 24px;
margin-left: auto;
margin-right: auto;
/* squeeze the dial pad buttons together horizontally */
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.mx_DialPad_button {
display: flex;
flex-direction: column;
justify-content: center;
width: 40px;
height: 40px;
background-color: $dialpad-button-bg-color;
@ -29,10 +39,19 @@ limitations under the License.
font-weight: 600;
text-align: center;
vertical-align: middle;
line-height: 40px;
margin-left: auto;
margin-right: auto;
}
.mx_DialPad_deleteButton, .mx_DialPad_dialButton {
.mx_DialPad_button .mx_DialPad_buttonSubText {
font-size: 8px;
}
.mx_DialPad_dialButton {
/* Always show the dial button in the center grid column */
grid-column: 2;
background-color: $accent-color;
&::before {
content: '';
display: inline-block;
@ -42,21 +61,7 @@ limitations under the License.
mask-repeat: no-repeat;
mask-size: 20px;
mask-position: center;
background-color: $primary-bg-color;
}
}
.mx_DialPad_deleteButton {
background-color: $notice-primary-color;
&::before {
mask-image: url('$(res)/img/element-icons/call/delete.svg');
mask-position: 9px; // delete icon is right-heavy so have to be slightly to the left to look centered
}
}
.mx_DialPad_dialButton {
background-color: $accent-color;
&::before {
background-color: #FFF; // on all themes
mask-image: url('$(res)/img/element-icons/call/voice-call.svg');
}
}

View file

@ -14,10 +14,40 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_DialPadContextMenu_dialPad .mx_DialPad {
row-gap: 16px;
column-gap: 32px;
}
.mx_DialPadContextMenuWrapper {
padding: 15px;
}
.mx_DialPadContextMenu_header {
margin-top: 12px;
margin-left: 12px;
margin-right: 12px;
border: none;
margin-top: 32px;
margin-left: 20px;
margin-right: 20px;
/* a separator between the input line and the dial buttons */
border-bottom: 1px solid $quaternary-fg-color;
transition: border-bottom 0.25s;
}
.mx_DialPadContextMenu_cancel {
float: right;
mask: url('$(res)/img/feather-customised/cancel.svg');
mask-repeat: no-repeat;
mask-position: center;
mask-size: cover;
width: 14px;
height: 14px;
background-color: $dialog-close-fg-color;
cursor: pointer;
}
.mx_DialPadContextMenu_header:focus-within {
border-bottom: 1px solid $accent-color;
}
.mx_DialPadContextMenu_title {
@ -30,7 +60,6 @@ limitations under the License.
height: 1.5em;
font-size: 18px;
font-weight: 600;
max-width: 150px;
border: none;
margin: 0px;
}
@ -38,7 +67,7 @@ limitations under the License.
font-size: 18px;
font-weight: 600;
overflow: hidden;
max-width: 150px;
max-width: 185px;
text-align: left;
direction: rtl;
padding: 8px 0px;
@ -48,13 +77,3 @@ limitations under the License.
.mx_DialPadContextMenu_dialPad {
margin: 16px;
}
.mx_DialPadContextMenu_horizSep {
position: relative;
&::before {
content: '';
position: absolute;
width: 100%;
border-bottom: 1px solid $input-darker-bg-color;
}
}

View file

@ -19,14 +19,23 @@ limitations under the License.
}
.mx_DialPadModal {
width: 192px;
height: 368px;
width: 292px;
height: 370px;
padding: 16px 0px 0px 0px;
}
.mx_DialPadModal_header {
margin-top: 12px;
margin-left: 12px;
margin-right: 12px;
margin-top: 32px;
margin-left: 40px;
margin-right: 40px;
/* a separator between the input line and the dial buttons */
border-bottom: 1px solid $quaternary-fg-color;
transition: border-bottom 0.25s;
}
.mx_DialPadModal_header:focus-within {
border-bottom: 1px solid $accent-color;
}
.mx_DialPadModal_title {
@ -45,11 +54,18 @@ limitations under the License.
height: 14px;
background-color: $dialog-close-fg-color;
cursor: pointer;
margin-right: 16px;
}
.mx_DialPadModal_field {
border: none;
margin: 0px;
height: 30px;
}
.mx_DialPadModal_field .mx_Field_postfix {
/* Remove border separator between postfix and field content */
border-left: none;
}
.mx_DialPadModal_field input {
@ -62,13 +78,3 @@ limitations under the License.
margin-right: 16px;
margin-top: 16px;
}
.mx_DialPadModal_horizSep {
position: relative;
&::before {
content: '';
position: absolute;
width: 100%;
border-bottom: 1px solid $input-darker-bg-color;
}
}

View file

@ -15,8 +15,6 @@ limitations under the License.
*/
.mx_VideoFeed_voice {
// We don't want to collide with the call controls that have 52px of height
padding-bottom: 52px;
background-color: $inverted-bg-color;
}

View file

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.97991 1.48403L4 4.80062L1 4.80062C0.447715 4.80062 0 5.24834 0 5.80062V10.2006C0 10.7529 0.447714 11.2006 0.999999 11.2006L4 11.2006L7.97991 14.5172C8.30557 14.7886 8.8 14.557 8.8 14.1331V1.86814C8.8 1.44422 8.30557 1.21265 7.97991 1.48403Z" fill="#737D8C"/>
<path d="M14.1258 2.79107C13.8998 2.50044 13.4809 2.44808 13.1903 2.67413C12.9 2.89992 12.8475 3.3181 13.0726 3.6087L13.0731 3.60935L13.0738 3.61021L13.0829 3.62231C13.0917 3.63418 13.1059 3.65355 13.1248 3.68011C13.1625 3.73326 13.2187 3.81496 13.2872 3.92256C13.4243 4.13812 13.6097 4.45554 13.7955 4.85371C14.169 5.65407 14.5329 6.75597 14.5329 8.00036C14.5329 9.24475 14.169 10.3466 13.7955 11.147C13.6097 11.5452 13.4243 11.8626 13.2872 12.0782C13.2187 12.1858 13.1625 12.2675 13.1248 12.3206C13.1059 12.3472 13.0917 12.3665 13.0829 12.3784L13.0738 12.3905L13.0731 12.3914L13.0725 12.3921C12.8475 12.6827 12.9 13.1008 13.1903 13.3266C13.4809 13.5526 13.8998 13.5003 14.1258 13.2097L13.629 12.8232C14.1258 13.2096 14.1258 13.2097 14.1258 13.2097L14.1272 13.2079L14.1291 13.2055L14.1346 13.1982L14.1523 13.1748C14.1669 13.1552 14.187 13.1277 14.2119 13.0926C14.2617 13.0225 14.3305 12.9221 14.4121 12.794C14.5749 12.5381 14.7895 12.1698 15.0037 11.7109C15.4302 10.7969 15.8663 9.49883 15.8663 8.00036C15.8663 6.50189 15.4302 5.20379 15.0037 4.28987C14.7895 3.83089 14.5749 3.4626 14.4121 3.20673C14.3305 3.07862 14.2617 2.97818 14.2119 2.90811C14.187 2.87306 14.1669 2.84556 14.1523 2.82596L14.1346 2.80249L14.1291 2.79525L14.1272 2.79278L14.1264 2.79183C14.1264 2.79183 14.1258 2.79107 13.5996 3.20036L14.1258 2.79107Z" fill="#737D8C"/>
<path d="M11.7264 5.19121C11.5004 4.90058 11.0815 4.84823 10.7909 5.07427C10.501 5.29973 10.4482 5.71698 10.6722 6.00752L10.6745 6.01057C10.6775 6.01457 10.6831 6.02223 10.691 6.03338C10.7069 6.05572 10.7318 6.09189 10.7628 6.14057C10.8249 6.23827 10.9103 6.38426 10.9961 6.56815C11.1696 6.93993 11.3335 7.44183 11.3335 8.00051C11.3335 8.55918 11.1696 9.06108 10.9961 9.43287C10.9103 9.61675 10.8249 9.76275 10.7628 9.86045C10.7318 9.90912 10.7069 9.94529 10.691 9.96763C10.6831 9.97879 10.6775 9.98645 10.6745 9.99044L10.6722 9.9935C10.4482 10.284 10.501 10.7013 10.7909 10.9267C11.0815 11.1528 11.5004 11.1004 11.7264 10.8098L11.2002 10.4005C11.7264 10.8098 11.7264 10.8098 11.7264 10.8098L11.7276 10.8083L11.7291 10.8064L11.7329 10.8014L11.7439 10.7868C11.7526 10.7751 11.7642 10.7593 11.7781 10.7396C11.806 10.7004 11.8436 10.6455 11.8876 10.5763C11.9755 10.4383 12.0901 10.2414 12.2043 9.99672C12.4308 9.51136 12.6669 8.81326 12.6669 8.00051C12.6669 7.18775 12.4308 6.48965 12.2043 6.0043C12.0901 5.75961 11.9755 5.56275 11.8876 5.42473C11.8436 5.35555 11.806 5.30065 11.7781 5.26138C11.7642 5.24173 11.7526 5.22596 11.7439 5.21422L11.7329 5.19964L11.7291 5.19465L11.7276 5.19274L11.727 5.19193C11.727 5.19193 11.7264 5.19121 11.2002 5.60051L11.7264 5.19121Z" fill="#737D8C"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16ZM6.9806 4.5101C6.9306 3.9401 7.3506 3.4401 7.9206 3.4001C8.4806 3.3601 8.9806 3.7801 9.0406 4.3501V4.5101L8.7206 8.5101C8.6906 8.8801 8.3806 9.1601 8.0106 9.1601H7.9506C7.6006 9.1301 7.3306 8.8601 7.3006 8.5101L6.9806 4.5101ZM8.88012 11.1202C8.88012 11.6062 8.48613 12.0002 8.00012 12.0002C7.51411 12.0002 7.12012 11.6062 7.12012 11.1202C7.12012 10.6342 7.51411 10.2402 8.00012 10.2402C8.48613 10.2402 8.88012 10.6342 8.88012 11.1202Z" fill="#8D99A5"/>
</svg>

After

Width:  |  Height:  |  Size: 713 B

View file

@ -1,7 +0,0 @@
<svg height="12" viewBox="0 0 12 12" width="12" xmlns="http://www.w3.org/2000/svg">
<g style="stroke:#454545;stroke-width:.8;fill:none;fill-rule:evenodd;stroke-linecap:round;stroke-linejoin:round" transform="translate(1 1)">
<circle cx="5" cy="5" r="5"/>
<path d="m0 5h10"/>
<path d="m5 0c1.25064019 1.36917645 1.96137638 3.14601693 2 5-.03862362 1.85398307-.74935981 3.63082355-2 5-1.25064019-1.36917645-1.96137638-3.14601693-2-5 .03862362-1.85398307.74935981-3.63082355 2-5z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 524 B

3
res/img/subtract.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 16C12.4183 16 16 12.4183 16 8C16 3.58167 12.4183 0 8 0C3.58173 0 0 3.58167 0 8C0 12.4183 3.58173 16 8 16ZM3.96967 5.0304L6.93933 8L3.96967 10.9697L5.03033 12.0304L8 9.06067L10.9697 12.0304L12.0303 10.9697L9.06067 8L12.0303 5.0304L10.9697 3.96973L8 6.93945L5.03033 3.96973L3.96967 5.0304Z" fill="#8D97A5"/>
</svg>

After

Width:  |  Height:  |  Size: 461 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 19C10.9 19 10 19.9 10 21C10 22.1 10.9 23 12 23C13.1 23 14 22.1 14 21C14 19.9 13.1 19 12 19ZM6 1C4.9 1 4 1.9 4 3C4 4.1 4.9 5 6 5C7.1 5 8 4.1 8 3C8 1.9 7.1 1 6 1ZM6 7C4.9 7 4 7.9 4 9C4 10.1 4.9 11 6 11C7.1 11 8 10.1 8 9C8 7.9 7.1 7 6 7ZM6 13C4.9 13 4 13.9 4 15C4 16.1 4.9 17 6 17C7.1 17 8 16.1 8 15C8 13.9 7.1 13 6 13ZM18 5C19.1 5 20 4.1 20 3C20 1.9 19.1 1 18 1C16.9 1 16 1.9 16 3C16 4.1 16.9 5 18 5ZM12 13C10.9 13 10 13.9 10 15C10 16.1 10.9 17 12 17C13.1 17 14 16.1 14 15C14 13.9 13.1 13 12 13ZM18 13C16.9 13 16 13.9 16 15C16 16.1 16.9 17 18 17C19.1 17 20 16.1 20 15C20 13.9 19.1 13 18 13ZM18 7C16.9 7 16 7.9 16 9C16 10.1 16.9 11 18 11C19.1 11 20 10.1 20 9C20 7.9 19.1 7 18 7ZM12 7C10.9 7 10 7.9 10 9C10 10.1 10.9 11 12 11C13.1 11 14 10.1 14 9C14 7.9 13.1 7 12 7ZM12 1C10.9 1 10 1.9 10 3C10 4.1 10.9 5 12 5C13.1 5 14 4.1 14 3C14 1.9 13.1 1 12 1Z" fill="#8D97A5"/>
</svg>

After

Width:  |  Height:  |  Size: 979 B

View file

@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.1502 21.1214C16.3946 22.3074 14.2782 23 12 23C9.52367 23 7.23845 22.1817 5.4 20.8008C2.72821 18.794 1 15.5988 1 12C1 5.92487 5.92487 1 12 1C18.0751 1 23 5.92487 23 12C23 15.797 21.0762 19.1446 18.1502 21.1214ZM12 12.55C13.8225 12.55 15.3 10.9494 15.3 8.975C15.3 7.00058 13.8225 5.4 12 5.4C10.1775 5.4 8.7 7.00058 8.7 8.975C8.7 10.9494 10.1775 12.55 12 12.55ZM12 20.8C14.3782 20.8 16.536 19.8566 18.1197 18.3237C17.1403 15.9056 14.7693 14.2 12 14.2C9.23066 14.2 6.85969 15.9056 5.88028 18.3237C7.46399 19.8566 9.62183 20.8 12 20.8Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.1502 21.1214C16.3946 22.3074 14.2782 23 12 23C9.52367 23 7.23845 22.1817 5.4 20.8008C2.72821 18.794 1 15.5988 1 12C1 5.92487 5.92487 1 12 1C18.0751 1 23 5.92487 23 12C23 15.797 21.0762 19.1446 18.1502 21.1214ZM12 12.55C13.8225 12.55 15.3 10.9494 15.3 8.975C15.3 7.00058 13.8225 5.4 12 5.4C10.1775 5.4 8.7 7.00058 8.7 8.975C8.7 10.9494 10.1775 12.55 12 12.55ZM12 20.8C14.3782 20.8 16.536 19.8566 18.1197 18.3237C17.1403 15.9056 14.7693 14.2 12 14.2C9.23066 14.2 6.85969 15.9056 5.88028 18.3237C7.46399 19.8566 9.62183 20.8 12 20.8Z" fill="#8D97A5"/>
<path d="M18.1502 21.1214L18.9339 22.2814L18.1502 21.1214ZM5.4 20.8008L4.55919 21.9202H4.55919L5.4 20.8008ZM18.1197 18.3237L19.0934 19.3296L19.7717 18.6731L19.4173 17.7981L18.1197 18.3237ZM5.88028 18.3237L4.58268 17.7981L4.22829 18.6731L4.90659 19.3296L5.88028 18.3237ZM12 24.4C14.5662 24.4 16.9541 23.619 18.9339 22.2814L17.3665 19.9613C15.835 20.9959 13.9902 21.6 12 21.6V24.4ZM4.55919 21.9202C6.63176 23.477 9.21011 24.4 12 24.4V21.6C9.83723 21.6 7.84514 20.8865 6.24081 19.6814L4.55919 21.9202ZM-0.399998 12C-0.399998 16.0577 1.55052 19.6603 4.55919 21.9202L6.24081 19.6814C3.90591 17.9276 2.4 15.1399 2.4 12H-0.399998ZM12 -0.399998C5.15167 -0.399998 -0.399998 5.15167 -0.399998 12H2.4C2.4 6.69807 6.69807 2.4 12 2.4V-0.399998ZM24.4 12C24.4 5.15167 18.8483 -0.399998 12 -0.399998V2.4C17.3019 2.4 21.6 6.69807 21.6 12H24.4ZM18.9339 22.2814C22.2288 20.0554 24.4 16.2815 24.4 12H21.6C21.6 15.3124 19.9236 18.2337 17.3665 19.9613L18.9339 22.2814ZM13.9 8.975C13.9 10.2838 12.9459 11.15 12 11.15V13.95C14.6991 13.95 16.7 11.615 16.7 8.975H13.9ZM12 6.8C12.9459 6.8 13.9 7.66616 13.9 8.975H16.7C16.7 6.335 14.6991 4 12 4V6.8ZM10.1 8.975C10.1 7.66616 11.0541 6.8 12 6.8V4C9.30086 4 7.3 6.335 7.3 8.975H10.1ZM12 11.15C11.0541 11.15 10.1 10.2838 10.1 8.975H7.3C7.3 11.615 9.30086 13.95 12 13.95V11.15ZM17.146 17.3178C15.8129 18.6081 14.0004 19.4 12 19.4V22.2C14.756 22.2 17.2591 21.1051 19.0934 19.3296L17.146 17.3178ZM12 15.6C14.1797 15.6 16.0494 16.9415 16.8221 18.8493L19.4173 17.7981C18.2312 14.8697 15.359 12.8 12 12.8V15.6ZM7.17788 18.8493C7.95058 16.9415 9.8203 15.6 12 15.6V12.8C8.64102 12.8 5.7688 14.8697 4.58268 17.7981L7.17788 18.8493ZM12 19.4C9.99963 19.4 8.18709 18.6081 6.85397 17.3178L4.90659 19.3296C6.74088 21.1051 9.24402 22.2 12 22.2V19.4Z" fill="#8D97A5" mask="url(#path-1-inside-1)"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -118,9 +118,7 @@ $voipcall-plinth-color: #394049;
// ********************
$theme-button-bg-color: #e3e8f0;
$dialpad-button-bg-color: #6F7882;
;
$dialpad-button-bg-color: #394049;
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
$roomlist-filter-active-bg-color: $bg-color;
@ -211,8 +209,8 @@ $user-tile-hover-bg-color: $header-panel-bg-color;
$message-body-panel-fg-color: $secondary-fg-color;
$message-body-panel-bg-color: #394049; // "Dark Tile"
$message-body-panel-icon-fg-color: #21262C; // "Separator"
$message-body-panel-icon-bg-color: $tertiary-fg-color;
$message-body-panel-icon-fg-color: $secondary-fg-color;
$message-body-panel-icon-bg-color: #21262C; // "System Dark"
$voice-record-stop-border-color: $quaternary-fg-color;
$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color;
@ -229,6 +227,13 @@ $groupFilterPanel-background-blur-amount: 30px;
$composer-shadow-color: rgba(0, 0, 0, 0.28);
// Bubble tiles
$eventbubble-self-bg: #143A34;
$eventbubble-others-bg: #394049;
$eventbubble-bg-hover: #433C23;
$eventbubble-avatar-outline: $bg-color;
$eventbubble-reply-color: #C1C6CD;
// ***** Mixins! *****
@define-mixin mx_DialogButton {
@ -274,24 +279,7 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28);
}
// markdown overrides:
.mx_EventTile_content .markdown-body pre:hover {
border-color: #808080 !important; // inverted due to rules below
scrollbar-color: rgba(0, 0, 0, 0.2) transparent; // copied from light theme due to inversion below
// the code above works only in Firefox, this is for other browsers
// see https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-color
&::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2); // copied from light theme due to inversion below
}
}
.mx_EventTile_content .markdown-body {
pre, code {
filter: invert(1);
}
pre code {
filter: none;
}
table {
tr {
background-color: #000000;
@ -301,18 +289,17 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28);
background-color: #080808;
}
}
blockquote {
color: #919191;
}
}
// diff highlight colors
// intentionally swapped to avoid inversion
// highlight.js overrides
.hljs-tag {
color: inherit; // Without this they'd be weirdly blue which doesn't match the theme
}
.hljs-addition {
background: #fdd;
background: #1a4b59;
}
.hljs-deletion {
background: #dfd;
background: #53232a;
}

View file

@ -9,3 +9,4 @@
@import "_dark.scss";
@import "../../light/css/_mods.scss";
@import "../../../../res/css/_components.scss";
@import url("highlight.js/styles/atom-one-dark.css");

View file

@ -118,7 +118,7 @@ $voipcall-plinth-color: #394049;
$theme-button-bg-color: #e3e8f0;
$dialpad-button-bg-color: #6F7882;
;
$roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
@ -207,8 +207,8 @@ $user-tile-hover-bg-color: $header-panel-bg-color;
$message-body-panel-fg-color: $secondary-fg-color;
$message-body-panel-bg-color: #394049;
$message-body-panel-icon-fg-color: $primary-bg-color;
$message-body-panel-icon-bg-color: $secondary-fg-color;
$message-body-panel-icon-fg-color: $secondary-fg-color;
$message-body-panel-icon-bg-color: #21262C;
// See non-legacy dark for variable information
$voice-record-stop-border-color: #6F7882;
@ -267,18 +267,7 @@ $composer-shadow-color: tranparent;
}
// markdown overrides:
.mx_EventTile_content .markdown-body pre:hover {
border-color: #808080 !important; // inverted due to rules below
}
.mx_EventTile_content .markdown-body {
pre, code {
filter: invert(1);
}
pre code {
filter: none;
}
table {
tr {
background-color: #000000;
@ -290,12 +279,7 @@ $composer-shadow-color: tranparent;
}
}
// diff highlight colors
// intentionally swapped to avoid inversion
.hljs-addition {
background: #fdd;
}
.hljs-deletion {
background: #dfd;
// highlight.js overrides:
.hljs-tag {
color: inherit; // Without this they'd be weirdly blue which doesn't match the theme
}

View file

@ -4,3 +4,4 @@
@import "../../legacy-light/css/_legacy-light.scss";
@import "_legacy-dark.scss";
@import "../../../../res/css/_components.scss";
@import url("highlight.js/styles/atom-one-dark.css");

View file

@ -331,7 +331,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color;
$message-body-panel-fg-color: $secondary-fg-color;
$message-body-panel-bg-color: #E3E8F0;
$message-body-panel-icon-fg-color: $secondary-fg-color;
$message-body-panel-icon-bg-color: $primary-bg-color;
$message-body-panel-icon-bg-color: #F4F6FA;
// See non-legacy _light for variable information
$voice-record-stop-symbol-color: #ff4b55;
@ -347,6 +347,13 @@ $appearance-tab-border-color: $input-darker-bg-color;
$composer-shadow-color: tranparent;
// Bubble tiles
$eventbubble-self-bg: #F8FDFC;
$eventbubble-others-bg: #F7F8F9;
$eventbubble-bg-hover: rgb(242, 242, 242);
$eventbubble-avatar-outline: #fff;
$eventbubble-reply-color: #C1C6CD;
// ***** Mixins! *****
@define-mixin mx_DialogButton {

View file

@ -3,3 +3,4 @@
@import "_fonts.scss";
@import "_legacy-light.scss";
@import "../../../../res/css/_components.scss";
@import url("highlight.js/styles/atom-one-light.css");

View file

@ -327,7 +327,7 @@ $user-tile-hover-bg-color: $header-panel-bg-color;
$message-body-panel-fg-color: $secondary-fg-color;
$message-body-panel-bg-color: #E3E8F0; // "Separator"
$message-body-panel-icon-fg-color: $secondary-fg-color;
$message-body-panel-icon-bg-color: $primary-bg-color;
$message-body-panel-icon-bg-color: #F4F6FA;
// These two don't change between themes. They are the $warning-color, but we don't
// want custom themes to affect them by accident.
@ -349,6 +349,13 @@ $groupFilterPanel-background-blur-amount: 20px;
$composer-shadow-color: rgba(0, 0, 0, 0.04);
// Bubble tiles
$eventbubble-self-bg: #F8FDFC;
$eventbubble-others-bg: #F7F8F9;
$eventbubble-bg-hover: #FEFCF5;
$eventbubble-avatar-outline: $primary-bg-color;
$eventbubble-reply-color: #C1C6CD;
// ***** Mixins! *****
@define-mixin mx_DialogButton {

View file

@ -4,3 +4,4 @@
@import "_light.scss";
@import "_mods.scss";
@import "../../../../res/css/_components.scss";
@import url("highlight.js/styles/atom-one-light.css");

View file

@ -6,8 +6,8 @@ scripts/fetchdep.sh matrix-org matrix-js-sdk
pushd matrix-js-sdk
yarn link
yarn install $@
yarn install --pure-lockfile $@
popd
yarn link matrix-js-sdk
yarn install $@
yarn install --pure-lockfile $@

View file

@ -13,13 +13,13 @@
scripts/fetchdep.sh matrix-org matrix-js-sdk
pushd matrix-js-sdk
yarn link
yarn install
yarn install --pure-lockfile
popd
# Now set up the react-sdk
yarn link matrix-js-sdk
yarn link
yarn install
yarn install --pure-lockfile
yarn reskindex
# Finally, set up element-web
@ -27,6 +27,6 @@ scripts/fetchdep.sh vector-im element-web
pushd element-web
yarn link matrix-js-sdk
yarn link matrix-react-sdk
yarn install
yarn install --pure-lockfile
yarn build:res
popd

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { JSXElementConstructor } from "react";
import React, { JSXElementConstructor } from "react";
// Based on https://stackoverflow.com/a/53229857/3532235
export type Without<T, U> = {[P in Exclude<keyof T, keyof U>]?: never};
@ -22,3 +22,4 @@ export type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<
export type Writeable<T> = { -readonly [P in keyof T]: T[P] };
export type ComponentClass = keyof JSX.IntrinsicElements | JSXElementConstructor<any>;
export type ReactAnyComponent = React.Component | React.ExoticComponent;

View file

@ -15,7 +15,9 @@ limitations under the License.
*/
import "matrix-js-sdk/src/@types/global"; // load matrix-js-sdk's type extensions first
import * as ModernizrStatic from "modernizr";
// Load types for the WG CSS Font Loading APIs https://github.com/Microsoft/TypeScript/issues/13569
import "@types/css-font-loading-module";
import "@types/modernizr";
import ContentMessages from "../ContentMessages";
import { IMatrixClientPeg } from "../MatrixClientPeg";
@ -48,9 +50,10 @@ import UIStore from "../stores/UIStore";
import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
import { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
/* eslint-disable @typescript-eslint/naming-convention */
declare global {
interface Window {
Modernizr: ModernizrStatic;
matrixChat: ReturnType<Renderer>;
mxMatrixClientPeg: IMatrixClientPeg;
Olm: {
@ -89,6 +92,7 @@ declare global {
mxUIStore: UIStore;
mxSetupEncryptionStore?: SetupEncryptionStore;
mxRoomScrollStateStore?: RoomScrollStateStore;
mxOnRecaptchaLoaded?: () => void;
}
interface Document {
@ -184,4 +188,21 @@ declare global {
parameterDescriptors?: AudioParamDescriptor[];
}
);
// eslint-disable-next-line no-var
var grecaptcha:
| undefined
| {
reset: (id: string) => void;
render: (
divId: string,
options: {
sitekey: string;
callback: (response: string) => void;
},
) => string;
isReady: () => boolean;
};
}
/* eslint-enable @typescript-eslint/naming-convention */

20
src/@types/svg.d.ts vendored Normal file
View file

@ -0,0 +1,20 @@
/*
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
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.
*/
declare module "*.svg" {
const path: string;
export default path;
}

View file

@ -14,6 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_MVoiceMessageBody {
display: inline-block; // makes the playback controls magically line up
declare module "*.worker.ts" {
class WebpackWorker extends Worker {
constructor();
}
export default WebpackWorker;
}

View file

@ -15,6 +15,7 @@ limitations under the License.
*/
import RoomViewStore from './stores/RoomViewStore';
import { EventSubscription } from 'fbemitter';
type Listener = (isActive: boolean) => void;
@ -30,7 +31,7 @@ type Listener = (isActive: boolean) => void;
export class ActiveRoomObserver {
private listeners: {[key: string]: Listener[]} = {};
private _activeRoomId = RoomViewStore.getRoomId();
private readonly roomStoreToken: string;
private readonly roomStoreToken: EventSubscription;
constructor() {
// TODO: We could self-destruct when the last listener goes away, or at least stop listening.

View file

@ -248,7 +248,7 @@ export default class AddThreepid {
/**
* Takes a phone number verification code as entered by the user and validates
* it with the ID server, then if successful, adds the phone number.
* it with the identity server, then if successful, adds the phone number.
* @param {string} msisdnToken phone number verification code as entered by the user
* @return {Promise} Resolves if the phone number was added. Rejects with an object
* with a "message" property which contains a human-readable message detailing why

View file

@ -270,7 +270,7 @@ export class Analytics {
localStorage.removeItem(LAST_VISIT_TS_KEY);
}
private async _track(data: IData) {
private async track(data: IData) {
if (this.disabled) return;
const now = new Date();
@ -304,7 +304,7 @@ export class Analytics {
}
public ping() {
this._track({
this.track({
ping: "1",
});
localStorage.setItem(LAST_VISIT_TS_KEY, String(new Date().getTime())); // update last visit ts
@ -324,14 +324,14 @@ export class Analytics {
// But continue anyway because we still want to track the change
}
this._track({
this.track({
gt_ms: String(generationTimeMs),
});
}
public trackEvent(category: string, action: string, name?: string, value?: string) {
if (this.disabled) return;
this._track({
this.track({
e_c: category,
e_a: action,
e_n: name,
@ -390,6 +390,7 @@ export class Analytics {
{ expl: _td('Your device resolution'), value: resolution },
];
// FIXME: Using an import will result in test failures
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
Modal.createTrackedDialog('Analytics Details', '', ErrorDialog, {
title: _t('Analytics'),

View file

@ -77,6 +77,7 @@ export default class AsyncWrapper extends React.Component<IProps, IState> {
const Component = this.state.component;
return <Component {...this.props} />;
} else if (this.state.error) {
// FIXME: Using an import will result in test failures
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <BaseDialog onFinished={this.props.onFinished} title={_t("Error")}>

View file

@ -18,10 +18,11 @@ import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { User } from "matrix-js-sdk/src/models/user";
import { Room } from "matrix-js-sdk/src/models/room";
import { ResizeMethod } from "matrix-js-sdk/src/@types/partials";
import { split } from "lodash";
import DMRoomMap from './utils/DMRoomMap';
import { mediaFromMxc } from "./customisations/Media";
import SettingsStore from "./settings/SettingsStore";
import SpaceStore from "./stores/SpaceStore";
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
export function avatarUrlForMember(
@ -122,27 +123,13 @@ export function getInitialLetter(name: string): string {
return undefined;
}
let idx = 0;
const initial = name[0];
if ((initial === '@' || initial === '#' || initial === '+') && name[1]) {
idx++;
name = name.substring(1);
}
// string.codePointAt(0) would do this, but that isn't supported by
// some browsers (notably PhantomJS).
let chars = 1;
const first = name.charCodeAt(idx);
// check if its the start of a surrogate pair
if (first >= 0xD800 && first <= 0xDBFF && name[idx+1]) {
const second = name.charCodeAt(idx+1);
if (second >= 0xDC00 && second <= 0xDFFF) {
chars++;
}
}
const firstChar = name.substring(idx, idx+chars);
return firstChar.toUpperCase();
// rely on the grapheme cluster splitter in lodash so that we don't break apart compound emojis
return split(name, "", 1)[0].toUpperCase();
}
export function avatarUrlForRoom(room: Room, width: number, height: number, resizeMethod?: ResizeMethod) {
@ -153,7 +140,7 @@ export function avatarUrlForRoom(room: Room, width: number, height: number, resi
}
// space rooms cannot be DMs so skip the rest
if (SettingsStore.getValue("feature_spaces") && room.isSpaceRoom()) return null;
if (SpaceStore.spacesEnabled && room.isSpaceRoom()) return null;
let otherMember = null;
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);

60
src/BlurhashEncoder.ts Normal file
View file

@ -0,0 +1,60 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { defer, IDeferred } from "matrix-js-sdk/src/utils";
// @ts-ignore - `.ts` is needed here to make TS happy
import BlurhashWorker from "./workers/blurhash.worker.ts";
interface IBlurhashWorkerResponse {
seq: number;
blurhash: string;
}
export class BlurhashEncoder {
private static internalInstance = new BlurhashEncoder();
public static get instance(): BlurhashEncoder {
return BlurhashEncoder.internalInstance;
}
private readonly worker: Worker;
private seq = 0;
private pendingDeferredMap = new Map<number, IDeferred<string>>();
constructor() {
this.worker = new BlurhashWorker();
this.worker.onmessage = this.onMessage;
}
private onMessage = (ev: MessageEvent<IBlurhashWorkerResponse>) => {
const { seq, blurhash } = ev.data;
const deferred = this.pendingDeferredMap.get(seq);
if (deferred) {
this.pendingDeferredMap.delete(seq);
deferred.resolve(blurhash);
}
};
public getBlurhash(imageData: ImageData): Promise<string> {
const seq = this.seq++;
const deferred = defer<string>();
this.pendingDeferredMap.set(seq, deferred);
this.worker.postMessage({ seq, imageData });
return deferred.promise;
}
}

View file

@ -99,7 +99,7 @@ const CHECK_PROTOCOLS_ATTEMPTS = 3;
// (and store the ID of their native room)
export const VIRTUAL_ROOM_EVENT_TYPE = 'im.vector.is_virtual_room';
export enum AudioID {
enum AudioID {
Ring = 'ringAudio',
Ringback = 'ringbackAudio',
CallEnd = 'callendAudio',
@ -142,6 +142,7 @@ export enum PlaceCallType {
export enum CallHandlerEvent {
CallsChanged = "calls_changed",
CallChangeRoom = "call_change_room",
SilencedCallsChanged = "silenced_calls_changed",
}
export default class CallHandler extends EventEmitter {
@ -154,7 +155,7 @@ export default class CallHandler extends EventEmitter {
private supportsPstnProtocol = null;
private pstnSupportPrefixed = null; // True if the server only support the prefixed pstn protocol
private supportsSipNativeVirtual = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native
private pstnSupportCheckTimer: NodeJS.Timeout; // number actually because we're in the browser
private pstnSupportCheckTimer: number;
// For rooms we've been invited to, true if they're from virtual user, false if we've checked and they aren't.
private invitedRoomsAreVirtual = new Map<string, boolean>();
private invitedRoomCheckInProgress = false;
@ -164,6 +165,8 @@ export default class CallHandler extends EventEmitter {
// do the async lookup when we get new information and then store these mappings here
private assertedIdentityNativeUsers = new Map<string, string>();
private silencedCalls = new Set<string>(); // callIds
static sharedInstance() {
if (!window.mxCallHandler) {
window.mxCallHandler = new CallHandler();
@ -224,6 +227,33 @@ export default class CallHandler extends EventEmitter {
}
}
public silenceCall(callId: string) {
this.silencedCalls.add(callId);
this.emit(CallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
// Don't pause audio if we have calls which are still ringing
if (this.areAnyCallsUnsilenced()) return;
this.pause(AudioID.Ring);
}
public unSilenceCall(callId: string) {
this.silencedCalls.delete(callId);
this.emit(CallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
this.play(AudioID.Ring);
}
public isCallSilenced(callId: string): boolean {
return this.silencedCalls.has(callId);
}
/**
* Returns true if there is at least one unsilenced call
* @returns {boolean}
*/
private areAnyCallsUnsilenced(): boolean {
return this.calls.size > this.silencedCalls.size;
}
private async checkProtocols(maxTries) {
try {
const protocols = await MatrixClientPeg.get().getThirdpartyProtocols();
@ -301,6 +331,13 @@ export default class CallHandler extends EventEmitter {
}, true);
};
public getCallById(callId: string): MatrixCall {
for (const call of this.calls.values()) {
if (call.callId === callId) return call;
}
return null;
}
getCallForRoom(roomId: string): MatrixCall {
return this.calls.get(roomId) || null;
}
@ -394,7 +431,7 @@ export default class CallHandler extends EventEmitter {
}
private setCallListeners(call: MatrixCall) {
let mappedRoomId = CallHandler.sharedInstance().roomIdForCall(call);
let mappedRoomId = this.roomIdForCall(call);
call.on(CallEvent.Error, (err: CallError) => {
if (!this.matchesCallForThisRoom(call)) return;
@ -441,6 +478,10 @@ export default class CallHandler extends EventEmitter {
break;
}
if (newState !== CallState.Ringing) {
this.silencedCalls.delete(call.callId);
}
switch (newState) {
case CallState.Ringing:
this.play(AudioID.Ring);
@ -871,6 +912,12 @@ export default class CallHandler extends EventEmitter {
case Action.DialNumber:
this.dialNumber(payload.number);
break;
case Action.TransferCallToMatrixID:
this.startTransferToMatrixID(payload.call, payload.destination, payload.consultFirst);
break;
case Action.TransferCallToPhoneNumber:
this.startTransferToPhoneNumber(payload.call, payload.destination, payload.consultFirst);
break;
}
};
@ -905,6 +952,48 @@ export default class CallHandler extends EventEmitter {
});
}
private async startTransferToPhoneNumber(call: MatrixCall, destination: string, consultFirst: boolean) {
const results = await this.pstnLookup(destination);
if (!results || results.length === 0 || !results[0].userid) {
Modal.createTrackedDialog('', '', ErrorDialog, {
title: _t("Unable to transfer call"),
description: _t("There was an error looking up the phone number"),
});
return;
}
await this.startTransferToMatrixID(call, results[0].userid, consultFirst);
}
private async startTransferToMatrixID(call: MatrixCall, destination: string, consultFirst: boolean) {
if (consultFirst) {
const dmRoomId = await ensureDMExists(MatrixClientPeg.get(), destination);
dis.dispatch({
action: 'place_call',
type: call.type,
room_id: dmRoomId,
transferee: call,
});
dis.dispatch({
action: 'view_room',
room_id: dmRoomId,
should_peek: false,
joining: false,
});
} else {
try {
await call.transfer(destination);
} catch (e) {
console.log("Failed to transfer call", e);
Modal.createTrackedDialog('Failed to transfer call', '', ErrorDialog, {
title: _t('Transfer Failed'),
description: _t('Failed to transfer call'),
});
}
}
}
setActiveCallRoomId(activeCallRoomId: string) {
logger.info("Setting call in room " + activeCallRoomId + " active");

View file

@ -17,7 +17,6 @@ limitations under the License.
*/
import React from "react";
import { encode } from "blurhash";
import { MatrixClient } from "matrix-js-sdk/src/client";
import dis from './dispatcher/dispatcher';
@ -28,7 +27,6 @@ import RoomViewStore from './stores/RoomViewStore';
import encrypt from "browser-encrypt-attachment";
import extractPngChunks from "png-chunks-extract";
import Spinner from "./components/views/elements/Spinner";
import { Action } from "./dispatcher/actions";
import CountlyAnalytics from "./CountlyAnalytics";
import {
@ -39,7 +37,8 @@ import {
UploadStartedPayload,
} from "./dispatcher/payloads/UploadPayload";
import { IUpload } from "./models/IUpload";
import { IImageInfo } from "matrix-js-sdk/src/@types/partials";
import { IAbortablePromise, IImageInfo } from "matrix-js-sdk/src/@types/partials";
import { BlurhashEncoder } from "./BlurhashEncoder";
const MAX_WIDTH = 800;
const MAX_HEIGHT = 600;
@ -49,8 +48,6 @@ const MAX_HEIGHT = 600;
const PHYS_HIDPI = [0x00, 0x00, 0x16, 0x25, 0x00, 0x00, 0x16, 0x25, 0x01];
export const BLURHASH_FIELD = "xyz.amorgan.blurhash"; // MSC2448
const BLURHASH_X_COMPONENTS = 6;
const BLURHASH_Y_COMPONENTS = 6;
export class UploadCanceledError extends Error {}
@ -87,10 +84,6 @@ interface IThumbnail {
thumbnail: Blob;
}
interface IAbortablePromise<T> extends Promise<T> {
abort(): void;
}
/**
* Create a thumbnail for a image DOM element.
* The image will be smaller than MAX_WIDTH and MAX_HEIGHT.
@ -109,13 +102,12 @@ interface IAbortablePromise<T> extends Promise<T> {
* @return {Promise} A promise that resolves with an object with an info key
* and a thumbnail key.
*/
function createThumbnail(
async function createThumbnail(
element: ThumbnailableElement,
inputWidth: number,
inputHeight: number,
mimeType: string,
): Promise<IThumbnail> {
return new Promise((resolve) => {
let targetWidth = inputWidth;
let targetHeight = inputHeight;
if (targetHeight > MAX_HEIGHT) {
@ -127,21 +119,32 @@ function createThumbnail(
targetWidth = MAX_WIDTH;
}
const canvas = document.createElement("canvas");
let canvas: HTMLCanvasElement | OffscreenCanvas;
if (window.OffscreenCanvas) {
canvas = new window.OffscreenCanvas(targetWidth, targetHeight);
} else {
canvas = document.createElement("canvas");
canvas.width = targetWidth;
canvas.height = targetHeight;
}
const context = canvas.getContext("2d");
context.drawImage(element, 0, 0, targetWidth, targetHeight);
let thumbnailPromise: Promise<Blob>;
if (window.OffscreenCanvas) {
thumbnailPromise = (canvas as OffscreenCanvas).convertToBlob({ type: mimeType });
} else {
thumbnailPromise = new Promise<Blob>(resolve => (canvas as HTMLCanvasElement).toBlob(resolve, mimeType));
}
const imageData = context.getImageData(0, 0, targetWidth, targetHeight);
const blurhash = encode(
imageData.data,
imageData.width,
imageData.height,
BLURHASH_X_COMPONENTS,
BLURHASH_Y_COMPONENTS,
);
canvas.toBlob(function(thumbnail) {
resolve({
// thumbnailPromise and blurhash promise are being awaited concurrently
const blurhash = await BlurhashEncoder.instance.getBlurhash(imageData);
const thumbnail = await thumbnailPromise;
return {
info: {
thumbnail_info: {
w: targetWidth,
@ -154,9 +157,7 @@ function createThumbnail(
[BLURHASH_FIELD]: blurhash,
},
thumbnail,
});
}, mimeType);
});
};
}
/**
@ -334,7 +335,7 @@ export function uploadFile(
roomId: string,
file: File | Blob,
progressHandler?: any, // TODO: Types
): Promise<{url?: string, file?: any}> { // TODO: Types
): IAbortablePromise<{url?: string, file?: any}> { // TODO: Types
let canceled = false;
if (matrixClient.isRoomEncrypted(roomId)) {
// If the room is encrypted then encrypt the file before uploading it.
@ -366,8 +367,8 @@ export function uploadFile(
encryptInfo.mimetype = file.type;
}
return { "file": encryptInfo };
});
(prom as IAbortablePromise<any>).abort = () => {
}) as IAbortablePromise<{ file: any }>;
prom.abort = () => {
canceled = true;
if (uploadPromise) matrixClient.cancelUpload(uploadPromise);
};
@ -380,8 +381,8 @@ export function uploadFile(
if (canceled) throw new UploadCanceledError();
// If the attachment isn't encrypted then include the URL directly.
return { url };
});
(promise1 as any).abort = () => {
}) as IAbortablePromise<{ url: string }>;
promise1.abort = () => {
canceled = true;
matrixClient.cancelUpload(basePromise);
};
@ -419,6 +420,7 @@ export default class ContentMessages {
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
if (isQuoting) {
// FIXME: Using an import will result in Element crashing
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const { finished } = Modal.createTrackedDialog<[boolean]>('Upload Reply Warning', '', QuestionDialog, {
title: _t('Replying With Files'),
@ -453,6 +455,7 @@ export default class ContentMessages {
}
if (tooBigFiles.length > 0) {
// FIXME: Using an import will result in Element crashing
const UploadFailureDialog = sdk.getComponent("dialogs.UploadFailureDialog");
const { finished } = Modal.createTrackedDialog<[boolean]>('Upload Failure', '', UploadFailureDialog, {
badFiles: tooBigFiles,
@ -463,7 +466,6 @@ export default class ContentMessages {
if (!shouldContinue) return;
}
const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
let uploadAll = false;
// Promise to complete before sending next file into room, used for synchronisation of file-sending
// to match the order the files were specified in
@ -471,6 +473,8 @@ export default class ContentMessages {
for (let i = 0; i < okFiles.length; ++i) {
const file = okFiles[i];
if (!uploadAll) {
// FIXME: Using an import will result in Element crashing
const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
const { finished } = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation',
'', UploadConfirmDialog, {
file,
@ -549,10 +553,10 @@ export default class ContentMessages {
content.msgtype = 'm.file';
resolve();
}
});
}) as IAbortablePromise<void>;
// create temporary abort handler for before the actual upload gets passed off to js-sdk
(prom as IAbortablePromise<any>).abort = () => {
prom.abort = () => {
upload.canceled = true;
};
@ -567,7 +571,7 @@ export default class ContentMessages {
dis.dispatch<UploadStartedPayload>({ action: Action.UploadStarted, upload });
// Focus the composer view
dis.fire(Action.FocusComposer);
dis.fire(Action.FocusSendMessageComposer);
function onProgress(ev) {
upload.total = ev.total;
@ -581,9 +585,7 @@ export default class ContentMessages {
// XXX: upload.promise must be the promise that
// is returned by uploadFile as it has an abort()
// method hacked onto it.
upload.promise = uploadFile(
matrixClient, roomId, file, onProgress,
);
upload.promise = uploadFile(matrixClient, roomId, file, onProgress);
return upload.promise.then(function(result) {
content.file = result.file;
content.url = result.url;
@ -606,6 +608,7 @@ export default class ContentMessages {
{ fileName: upload.fileName },
);
}
// FIXME: Using an import will result in Element crashing
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Upload failed', '', ErrorDialog, {
title: _t('Upload Failed'),

View file

@ -364,8 +364,8 @@ export default class CountlyAnalytics {
private initTime = CountlyAnalytics.getTimestamp();
private firstPage = true;
private heartbeatIntervalId: NodeJS.Timeout;
private activityIntervalId: NodeJS.Timeout;
private heartbeatIntervalId: number;
private activityIntervalId: number;
private trackTime = true;
private lastBeat: number;
private storedDuration = 0;

View file

@ -46,8 +46,8 @@ export class DecryptionFailureTracker {
};
// Set to an interval ID when `start` is called
public checkInterval: NodeJS.Timeout = null;
public trackInterval: NodeJS.Timeout = null;
public checkInterval: number = null;
public trackInterval: number = null;
// Spread the load on `Analytics` by tracking at a low frequency, `TRACK_INTERVAL_MS`.
static TRACK_INTERVAL_MS = 60000;

View file

@ -33,6 +33,7 @@ import { isSecretStorageBeingAccessed, accessSecretStorage } from "./SecurityMan
import { isSecureBackupRequired } from './utils/WellKnownUtils';
import { isLoggedIn } from './components/structures/MatrixChat';
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { ActionPayload } from "./dispatcher/payloads";
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
@ -58,28 +59,28 @@ export default class DeviceListener {
}
start() {
MatrixClientPeg.get().on('crypto.willUpdateDevices', this._onWillUpdateDevices);
MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated);
MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged);
MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged);
MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
MatrixClientPeg.get().on('accountData', this._onAccountData);
MatrixClientPeg.get().on('sync', this._onSync);
MatrixClientPeg.get().on('RoomState.events', this._onRoomStateEvents);
this.dispatcherRef = dis.register(this._onAction);
this._recheck();
MatrixClientPeg.get().on('crypto.willUpdateDevices', this.onWillUpdateDevices);
MatrixClientPeg.get().on('crypto.devicesUpdated', this.onDevicesUpdated);
MatrixClientPeg.get().on('deviceVerificationChanged', this.onDeviceVerificationChanged);
MatrixClientPeg.get().on('userTrustStatusChanged', this.onUserTrustStatusChanged);
MatrixClientPeg.get().on('crossSigning.keysChanged', this.onCrossSingingKeysChanged);
MatrixClientPeg.get().on('accountData', this.onAccountData);
MatrixClientPeg.get().on('sync', this.onSync);
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
this.dispatcherRef = dis.register(this.onAction);
this.recheck();
}
stop() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener('crypto.willUpdateDevices', this._onWillUpdateDevices);
MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated);
MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged);
MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged);
MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
MatrixClientPeg.get().removeListener('sync', this._onSync);
MatrixClientPeg.get().removeListener('RoomState.events', this._onRoomStateEvents);
MatrixClientPeg.get().removeListener('crypto.willUpdateDevices', this.onWillUpdateDevices);
MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this.onDevicesUpdated);
MatrixClientPeg.get().removeListener('deviceVerificationChanged', this.onDeviceVerificationChanged);
MatrixClientPeg.get().removeListener('userTrustStatusChanged', this.onUserTrustStatusChanged);
MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this.onCrossSingingKeysChanged);
MatrixClientPeg.get().removeListener('accountData', this.onAccountData);
MatrixClientPeg.get().removeListener('sync', this.onSync);
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
}
if (this.dispatcherRef) {
dis.unregister(this.dispatcherRef);
@ -103,15 +104,15 @@ export default class DeviceListener {
this.dismissed.add(d);
}
this._recheck();
this.recheck();
}
dismissEncryptionSetup() {
this.dismissedThisDeviceToast = true;
this._recheck();
this.recheck();
}
_ensureDeviceIdsAtStartPopulated() {
private ensureDeviceIdsAtStartPopulated() {
if (this.ourDeviceIdsAtStart === null) {
const cli = MatrixClientPeg.get();
this.ourDeviceIdsAtStart = new Set(
@ -120,39 +121,39 @@ export default class DeviceListener {
}
}
_onWillUpdateDevices = async (users: string[], initialFetch?: boolean) => {
private onWillUpdateDevices = async (users: string[], initialFetch?: boolean) => {
// If we didn't know about *any* devices before (ie. it's fresh login),
// then they are all pre-existing devices, so ignore this and set the
// devicesAtStart list to the devices that we see after the fetch.
if (initialFetch) return;
const myUserId = MatrixClientPeg.get().getUserId();
if (users.includes(myUserId)) this._ensureDeviceIdsAtStartPopulated();
if (users.includes(myUserId)) this.ensureDeviceIdsAtStartPopulated();
// No need to do a recheck here: we just need to get a snapshot of our devices
// before we download any new ones.
};
_onDevicesUpdated = (users: string[]) => {
private onDevicesUpdated = (users: string[]) => {
if (!users.includes(MatrixClientPeg.get().getUserId())) return;
this._recheck();
this.recheck();
};
_onDeviceVerificationChanged = (userId: string) => {
private onDeviceVerificationChanged = (userId: string) => {
if (userId !== MatrixClientPeg.get().getUserId()) return;
this._recheck();
this.recheck();
};
_onUserTrustStatusChanged = (userId: string) => {
private onUserTrustStatusChanged = (userId: string) => {
if (userId !== MatrixClientPeg.get().getUserId()) return;
this._recheck();
this.recheck();
};
_onCrossSingingKeysChanged = () => {
this._recheck();
private onCrossSingingKeysChanged = () => {
this.recheck();
};
_onAccountData = (ev) => {
private onAccountData = (ev: MatrixEvent) => {
// User may have:
// * migrated SSSS to symmetric
// * uploaded keys to secret storage
@ -160,34 +161,35 @@ export default class DeviceListener {
// which result in account data changes affecting checks below.
if (
ev.getType().startsWith('m.secret_storage.') ||
ev.getType().startsWith('m.cross_signing.')
ev.getType().startsWith('m.cross_signing.') ||
ev.getType() === 'm.megolm_backup.v1'
) {
this._recheck();
this.recheck();
}
};
_onSync = (state, prevState) => {
if (state === 'PREPARED' && prevState === null) this._recheck();
private onSync = (state, prevState) => {
if (state === 'PREPARED' && prevState === null) this.recheck();
};
_onRoomStateEvents = (ev: MatrixEvent) => {
private onRoomStateEvents = (ev: MatrixEvent) => {
if (ev.getType() !== "m.room.encryption") {
return;
}
// If a room changes to encrypted, re-check as it may be our first
// encrypted room. This also catches encrypted room creation as well.
this._recheck();
this.recheck();
};
_onAction = ({ action }) => {
private onAction = ({ action }: ActionPayload) => {
if (action !== "on_logged_in") return;
this._recheck();
this.recheck();
};
// The server doesn't tell us when key backup is set up, so we poll
// & cache the result
async _getKeyBackupInfo() {
private async getKeyBackupInfo() {
const now = (new Date()).getTime();
if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
@ -205,7 +207,7 @@ export default class DeviceListener {
return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId));
}
async _recheck() {
private async recheck() {
const cli = MatrixClientPeg.get();
if (!await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) return;
@ -234,7 +236,7 @@ export default class DeviceListener {
// Cross-signing on account but this device doesn't trust the master key (verify this session)
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
} else {
const backupInfo = await this._getKeyBackupInfo();
const backupInfo = await this.getKeyBackupInfo();
if (backupInfo) {
// No cross-signing on account but key backup available (upgrade encryption)
showSetupEncryptionToast(SetupKind.UPGRADE_ENCRYPTION);
@ -255,7 +257,7 @@ export default class DeviceListener {
// This needs to be done after awaiting on downloadKeys() above, so
// we make sure we get the devices after the fetch is done.
this._ensureDeviceIdsAtStartPopulated();
this.ensureDeviceIdsAtStartPopulated();
// Unverified devices that were there last time the app ran
// (technically could just be a boolean: we don't actually

View file

@ -25,7 +25,6 @@ import _linkifyElement from 'linkifyjs/element';
import _linkifyString from 'linkifyjs/string';
import classNames from 'classnames';
import EMOJIBASE_REGEX from 'emojibase-regex';
import url from 'url';
import katex from 'katex';
import { AllHtmlEntities } from 'html-entities';
import { IContent } from 'matrix-js-sdk/src/models/event';
@ -34,7 +33,7 @@ import { IExtendedSanitizeOptions } from './@types/sanitize-html';
import linkifyMatrix from './linkify-matrix';
import SettingsStore from './settings/SettingsStore';
import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks";
import { SHORTCODE_TO_EMOJI, getEmojiFromUnicode } from "./emoji";
import { getEmojiFromUnicode } from "./emoji";
import ReplyThread from "./components/views/elements/ReplyThread";
import { mediaFromMxc } from "./customisations/Media";
@ -58,7 +57,9 @@ const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, 'i');
const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
export const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet'];
export const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet', 'matrix'];
const MEDIA_API_MXC_REGEX = /\/_matrix\/media\/r0\/(?:download|thumbnail)\/(.+?)\/(.+?)(?:[?/]|$)/;
/*
* Return true if the given string contains emoji
@ -78,20 +79,8 @@ function mightContainEmoji(str: string): boolean {
* @return {String} The shortcode (such as :thumbup:)
*/
export function unicodeToShortcode(char: string): string {
const data = getEmojiFromUnicode(char);
return (data && data.shortcodes ? `:${data.shortcodes[0]}:` : '');
}
/**
* Returns the unicode character for an emoji shortcode
*
* @param {String} shortcode The shortcode (such as :thumbup:)
* @return {String} The emoji character; null if none exists
*/
export function shortcodeToUnicode(shortcode: string): string {
shortcode = shortcode.slice(1, shortcode.length - 1);
const data = SHORTCODE_TO_EMOJI.get(shortcode);
return data ? data.unicode : null;
const shortcodes = getEmojiFromUnicode(char)?.shortcodes;
return shortcodes?.length ? `:${shortcodes[0]}:` : '';
}
export function processHtmlForSending(html: string): string {
@ -151,10 +140,8 @@ export function getHtmlText(insaneHtml: string): string {
*/
export function isUrlPermitted(inputUrl: string): boolean {
try {
const parsed = url.parse(inputUrl);
if (!parsed.protocol) return false;
// URL parser protocol includes the trailing colon
return PERMITTED_URL_SCHEMES.includes(parsed.protocol.slice(0, -1));
return PERMITTED_URL_SCHEMES.includes(new URL(inputUrl).protocol.slice(0, -1));
} catch (e) {
return false;
}
@ -176,18 +163,31 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to
return { tagName, attribs };
},
'img': function(tagName: string, attribs: sanitizeHtml.Attributes) {
let src = attribs.src;
// Strip out imgs that aren't `mxc` here instead of using allowedSchemesByTag
// because transformTags is used _before_ we filter by allowedSchemesByTag and
// we don't want to allow images with `https?` `src`s.
// We also drop inline images (as if they were not present at all) when the "show
// images" preference is disabled. Future work might expose some UI to reveal them
// like standalone image events have.
if (!attribs.src || !attribs.src.startsWith('mxc://') || !SettingsStore.getValue("showImages")) {
if (!src || !SettingsStore.getValue("showImages")) {
return { tagName, attribs: {} };
}
if (!src.startsWith("mxc://")) {
const match = MEDIA_API_MXC_REGEX.exec(src);
if (match) {
src = `mxc://${match[1]}/${match[2]}`;
}
}
if (!src.startsWith("mxc://")) {
return { tagName, attribs: {} };
}
const width = Number(attribs.width) || 800;
const height = Number(attribs.height) || 600;
attribs.src = mediaFromMxc(attribs.src).getThumbnailOfSourceHttp(width, height);
attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height);
return { tagName, attribs };
},
'code': function(tagName: string, attribs: sanitizeHtml.Attributes) {

View file

@ -127,7 +127,7 @@ export default class IdentityAuthClient {
await this._matrixClient.getIdentityAccount(token);
} catch (e) {
if (e.errcode === "M_TERMS_NOT_SIGNED") {
console.log("Identity Server requires new terms to be agreed to");
console.log("Identity server requires new terms to be agreed to");
await startTermsFlow([new Service(
SERVICE_TYPES.IS,
identityServerUrl,

View file

@ -21,6 +21,7 @@ import { createClient } from 'matrix-js-sdk/src/matrix';
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { decryptAES, encryptAES, IEncryptedPayload } from "matrix-js-sdk/src/crypto/aes";
import { QueryDict } from 'matrix-js-sdk/src/utils';
import { IMatrixClientCreds, MatrixClientPeg } from './MatrixClientPeg';
import SecurityCustomisations from "./customisations/Security";
@ -33,7 +34,6 @@ import Presence from './Presence';
import dis from './dispatcher/dispatcher';
import DMRoomMap from './utils/DMRoomMap';
import Modal from './Modal';
import * as sdk from './index';
import ActiveWidgetStore from './stores/ActiveWidgetStore';
import PlatformPeg from "./PlatformPeg";
import { sendLoginRequest } from "./Login";
@ -52,6 +52,10 @@ import CallHandler from './CallHandler';
import LifecycleCustomisations from "./customisations/Lifecycle";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import { _t } from "./languageHandler";
import LazyLoadingResyncDialog from "./components/views/dialogs/LazyLoadingResyncDialog";
import LazyLoadingDisabledDialog from "./components/views/dialogs/LazyLoadingDisabledDialog";
import SessionRestoreErrorDialog from "./components/views/dialogs/SessionRestoreErrorDialog";
import StorageEvictedDialog from "./components/views/dialogs/StorageEvictedDialog";
const HOMESERVER_URL_KEY = "mx_hs_url";
const ID_SERVER_URL_KEY = "mx_is_url";
@ -62,7 +66,7 @@ interface ILoadSessionOpts {
guestIsUrl?: string;
ignoreGuest?: boolean;
defaultDeviceDisplayName?: string;
fragmentQueryParams?: Record<string, string>;
fragmentQueryParams?: QueryDict;
}
/**
@ -115,8 +119,8 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise<boolean>
) {
console.log("Using guest access credentials");
return doSetLoggedIn({
userId: fragmentQueryParams.guest_user_id,
accessToken: fragmentQueryParams.guest_access_token,
userId: fragmentQueryParams.guest_user_id as string,
accessToken: fragmentQueryParams.guest_access_token as string,
homeserverUrl: guestHsUrl,
identityServerUrl: guestIsUrl,
guest: true,
@ -170,7 +174,7 @@ export async function getStoredSessionOwner(): Promise<[string, boolean]> {
* login, else false
*/
export function attemptTokenLogin(
queryParams: Record<string, string>,
queryParams: QueryDict,
defaultDeviceDisplayName?: string,
fragmentAfterLogin?: string,
): Promise<boolean> {
@ -195,7 +199,7 @@ export function attemptTokenLogin(
homeserver,
identityServer,
"m.login.token", {
token: queryParams.loginToken,
token: queryParams.loginToken as string,
initial_device_display_name: defaultDeviceDisplayName,
},
).then(function(creds) {
@ -238,8 +242,6 @@ export function handleInvalidStoreError(e: InvalidStoreError): Promise<void> {
return Promise.resolve().then(() => {
const lazyLoadEnabled = e.value;
if (lazyLoadEnabled) {
const LazyLoadingResyncDialog =
sdk.getComponent("views.dialogs.LazyLoadingResyncDialog");
return new Promise((resolve) => {
Modal.createDialog(LazyLoadingResyncDialog, {
onFinished: resolve,
@ -250,8 +252,6 @@ export function handleInvalidStoreError(e: InvalidStoreError): Promise<void> {
// between LL/non-LL version on same host.
// as disabling LL when previously enabled
// is a strong indicator of this (/develop & /app)
const LazyLoadingDisabledDialog =
sdk.getComponent("views.dialogs.LazyLoadingDisabledDialog");
return new Promise((resolve) => {
Modal.createDialog(LazyLoadingDisabledDialog, {
onFinished: resolve,
@ -451,9 +451,6 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
async function handleLoadSessionFailure(e: Error): Promise<boolean> {
console.error("Unable to load session", e);
const SessionRestoreErrorDialog =
sdk.getComponent('views.dialogs.SessionRestoreErrorDialog');
const modal = Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, {
error: e.message,
});
@ -612,7 +609,6 @@ async function doSetLoggedIn(
}
function showStorageEvictedDialog(): Promise<boolean> {
const StorageEvictedDialog = sdk.getComponent('views.dialogs.StorageEvictedDialog');
return new Promise(resolve => {
Modal.createTrackedDialog('Storage evicted', '', StorageEvictedDialog, {
onFinished: resolve,

View file

@ -17,8 +17,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { ICreateClientOpts } from 'matrix-js-sdk/src/matrix';
import { MatrixClient } from 'matrix-js-sdk/src/client';
import { ICreateClientOpts, PendingEventOrdering } from 'matrix-js-sdk/src/matrix';
import { IStartClientOpts, MatrixClient } from 'matrix-js-sdk/src/client';
import { MemoryStore } from 'matrix-js-sdk/src/store/memory';
import * as utils from 'matrix-js-sdk/src/utils';
import { EventTimeline } from 'matrix-js-sdk/src/models/event-timeline';
@ -47,25 +47,8 @@ export interface IMatrixClientCreds {
freshLogin?: boolean;
}
// TODO: Move this to the js-sdk
export interface IOpts {
initialSyncLimit?: number;
pendingEventOrdering?: "detached" | "chronological";
lazyLoadMembers?: boolean;
clientWellKnownPollPeriod?: number;
}
export interface IMatrixClientPeg {
opts: IOpts;
/**
* Sets the script href passed to the IndexedDB web worker
* If set, a separate web worker will be started to run the IndexedDB
* queries on.
*
* @param {string} script href to the script to be passed to the web worker
*/
setIndexedDbWorkerScript(script: string): void;
opts: IStartClientOpts;
/**
* Return the server name of the user's homeserver
@ -122,12 +105,12 @@ export interface IMatrixClientPeg {
* This module provides a singleton instance of this class so the 'current'
* Matrix Client object is available easily.
*/
class _MatrixClientPeg implements IMatrixClientPeg {
class MatrixClientPegClass implements IMatrixClientPeg {
// These are the default options used when when the
// client is started in 'start'. These can be altered
// at any time up to after the 'will_start_client'
// event is finished processing.
public opts: IOpts = {
public opts: IStartClientOpts = {
initialSyncLimit: 20,
};
@ -141,10 +124,6 @@ class _MatrixClientPeg implements IMatrixClientPeg {
constructor() {
}
public setIndexedDbWorkerScript(script: string): void {
createMatrixClient.indexedDbWorkerScript = script;
}
public get(): MatrixClient {
return this.matrixClient;
}
@ -219,6 +198,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
} catch (e) {
if (e && e.name === 'InvalidCryptoStoreError') {
// The js-sdk found a crypto DB too new for it to use
// FIXME: Using an import will result in test failures
const CryptoStoreTooNewDialog =
sdk.getComponent("views.dialogs.CryptoStoreTooNewDialog");
Modal.createDialog(CryptoStoreTooNewDialog);
@ -230,7 +210,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
const opts = utils.deepCopy(this.opts);
// the react sdk doesn't work without this, so don't allow
opts.pendingEventOrdering = "detached";
opts.pendingEventOrdering = PendingEventOrdering.Detached;
opts.lazyLoadMembers = true;
opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours
@ -320,7 +300,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
}
if (!window.mxMatrixClientPeg) {
window.mxMatrixClientPeg = new _MatrixClientPeg();
window.mxMatrixClientPeg = new MatrixClientPegClass();
}
export const MatrixClientPeg = window.mxMatrixClientPeg;

View file

@ -20,12 +20,15 @@ import { SettingLevel } from "./settings/SettingLevel";
import { setMatrixCallAudioInput, setMatrixCallVideoInput } from "matrix-js-sdk/src/matrix";
import EventEmitter from 'events';
interface IMediaDevices {
audioOutput: Array<MediaDeviceInfo>;
audioInput: Array<MediaDeviceInfo>;
videoInput: Array<MediaDeviceInfo>;
// XXX: MediaDeviceKind is a union type, so we make our own enum
export enum MediaDeviceKindEnum {
AudioOutput = "audiooutput",
AudioInput = "audioinput",
VideoInput = "videoinput",
}
export type IMediaDevices = Record<MediaDeviceKindEnum, Array<MediaDeviceInfo>>;
export enum MediaDeviceHandlerEvent {
AudioOutputChanged = "audio_output_changed",
}
@ -51,20 +54,14 @@ export default class MediaDeviceHandler extends EventEmitter {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const output = {
[MediaDeviceKindEnum.AudioOutput]: [],
[MediaDeviceKindEnum.AudioInput]: [],
[MediaDeviceKindEnum.VideoInput]: [],
};
const audioOutput = [];
const audioInput = [];
const videoInput = [];
devices.forEach((device) => {
switch (device.kind) {
case 'audiooutput': audioOutput.push(device); break;
case 'audioinput': audioInput.push(device); break;
case 'videoinput': videoInput.push(device); break;
}
});
return { audioOutput, audioInput, videoInput };
devices.forEach((device) => output[device.kind].push(device));
return output;
} catch (error) {
console.warn('Unable to refresh WebRTC Devices: ', error);
}
@ -106,6 +103,14 @@ export default class MediaDeviceHandler extends EventEmitter {
setMatrixCallVideoInput(deviceId);
}
public setDevice(deviceId: string, kind: MediaDeviceKindEnum): void {
switch (kind) {
case MediaDeviceKindEnum.AudioOutput: this.setAudioOutput(deviceId); break;
case MediaDeviceKindEnum.AudioInput: this.setAudioInput(deviceId); break;
case MediaDeviceKindEnum.VideoInput: this.setVideoInput(deviceId); break;
}
}
public static getAudioOutput(): string {
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audiooutput");
}

View file

@ -27,7 +27,6 @@ import * as TextForEvent from './TextForEvent';
import Analytics from './Analytics';
import * as Avatar from './Avatar';
import dis from './dispatcher/dispatcher';
import * as sdk from './index';
import { _t } from './languageHandler';
import Modal from './Modal';
import SettingsStore from "./settings/SettingsStore";
@ -37,6 +36,7 @@ import { isPushNotifyDisabled } from "./settings/controllers/NotificationControl
import RoomViewStore from "./stores/RoomViewStore";
import UserActivity from "./UserActivity";
import { mediaFromMxc } from "./customisations/Media";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
/*
* Dispatches:
@ -240,7 +240,6 @@ export const Notifier = {
? _t('%(brand)s does not have permission to send you notifications - ' +
'please check your browser settings', { brand })
: _t('%(brand)s was not given permission to send notifications - please try again', { brand });
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
Modal.createTrackedDialog('Unable to enable Notifications', result, ErrorDialog, {
title: _t('Unable to enable Notifications'),
description,
@ -329,7 +328,7 @@ export const Notifier = {
onEvent: function(ev: MatrixEvent) {
if (!this.isSyncing) return; // don't alert for any messages initially
if (ev.sender && ev.sender.userId === MatrixClientPeg.get().credentials.userId) return;
if (ev.getSender() === MatrixClientPeg.get().credentials.userId) return;
MatrixClientPeg.get().decryptEventIfNeeded(ev);

View file

@ -51,10 +51,15 @@ export async function startAnyRegistrationFlow(options) {
description: _t("Use your account or create a new one to continue."),
button: _t("Create Account"),
extraButtons: [
<button key="start_login" onClick={() => {
<button
key="start_login"
onClick={() => {
modal.close();
dis.dispatch({ action: 'start_login', screenAfterLogin: options.screen_after });
}}>{ _t('Sign In') }</button>,
}}
>
{ _t('Sign In') }
</button>,
],
onFinished: (proceed) => {
if (proceed) {

View file

@ -22,13 +22,13 @@ import { User } from "matrix-js-sdk/src/models/user";
import { MatrixClientPeg } from './MatrixClientPeg';
import MultiInviter, { CompletionStates } from './utils/MultiInviter';
import Modal from './Modal';
import * as sdk from './';
import { _t } from './languageHandler';
import InviteDialog, { KIND_DM, KIND_INVITE, Member } from "./components/views/dialogs/InviteDialog";
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
import BaseAvatar from "./components/views/avatars/BaseAvatar";
import { mediaFromMxc } from "./customisations/Media";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
export interface IInviteResult {
states: CompletionStates;
@ -51,7 +51,6 @@ export function inviteMultipleToRoom(roomId: string, addresses: string[]): Promi
export function showStartChatInviteDialog(initialText = ""): void {
// This dialog handles the room creation internally - we don't need to worry about it.
const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
Modal.createTrackedDialog(
'Start DM', '', InviteDialog, { kind: KIND_DM, initialText },
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
@ -111,7 +110,6 @@ export function inviteUsersToRoom(roomId: string, userIds: string[]): Promise<vo
showAnyInviteErrors(result.states, room, result.inviter);
}).catch((err) => {
console.error(err.stack);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, {
title: _t("Failed to invite"),
description: ((err && err.message) ? err.message : _t("Operation failed")),
@ -131,7 +129,6 @@ export function showAnyInviteErrors(
// Just get the first message because there was a fatal problem on the first
// user. This usually means that no other users were attempted, making it
// pointless for us to list who failed exactly.
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite users to the room', '', ErrorDialog, {
title: _t("Failed to invite users to the room:", { roomName: room.name }),
description: inviter.getErrorText(failedUsers[0]),
@ -178,7 +175,6 @@ export function showAnyInviteErrors(
</div>
</div>;
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog("Some invites could not be sent", "", ErrorDialog, {
title: _t("Some invites couldn't be sent"),
description,

View file

@ -17,6 +17,7 @@ limitations under the License.
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClientPeg } from './MatrixClientPeg';
import AliasCustomisations from './customisations/Alias';
/**
* Given a room object, return the alias we should use for it,
@ -28,7 +29,18 @@ import { MatrixClientPeg } from './MatrixClientPeg';
* @returns {string} A display alias for the given room
*/
export function getDisplayAliasForRoom(room: Room): string {
return room.getCanonicalAlias() || room.getAltAliases()[0];
return getDisplayAliasForAliasSet(
room.getCanonicalAlias(), room.getAltAliases(),
);
}
// The various display alias getters should all feed through this one path so
// there's a single place to change the logic.
export function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: string[]): string {
if (AliasCustomisations.getDisplayAliasForAliasSet) {
return AliasCustomisations.getDisplayAliasForAliasSet(canonicalAlias, altAliases);
}
return canonicalAlias || altAliases?.[0];
}
export function looksLikeDirectMessageRoom(room: Room, myUserId: string): boolean {
@ -72,10 +84,8 @@ export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise<void>
this room as a DM room
* @returns {object} A promise
*/
export function setDMRoom(roomId: string, userId: string): Promise<void> {
if (MatrixClientPeg.get().isGuest()) {
return Promise.resolve();
}
export async function setDMRoom(roomId: string, userId: string): Promise<void> {
if (MatrixClientPeg.get().isGuest()) return;
const mDirectEvent = MatrixClientPeg.get().getAccountData('m.direct');
let dmRoomMap = {};
@ -104,7 +114,7 @@ export function setDMRoom(roomId: string, userId: string): Promise<void> {
dmRoomMap[userId] = roomList;
}
return MatrixClientPeg.get().setAccountData('m.direct', dmRoomMap);
await MatrixClientPeg.get().setAccountData('m.direct', dmRoomMap);
}
/**

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,26 +14,42 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {
IResultRoomEvents,
ISearchRequestBody,
ISearchResponse,
ISearchResult,
ISearchResults,
SearchOrderBy,
} from "matrix-js-sdk/src/@types/search";
import { IRoomEventFilter } from "matrix-js-sdk/src/filter";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { ISearchArgs } from "./indexing/BaseEventIndexManager";
import EventIndexPeg from "./indexing/EventIndexPeg";
import { MatrixClientPeg } from "./MatrixClientPeg";
import { SearchResult } from "matrix-js-sdk/src/models/search-result";
const SEARCH_LIMIT = 10;
async function serverSideSearch(term, roomId = undefined) {
async function serverSideSearch(
term: string,
roomId: string = undefined,
): Promise<{ response: ISearchResponse, query: ISearchRequestBody }> {
const client = MatrixClientPeg.get();
const filter = {
const filter: IRoomEventFilter = {
limit: SEARCH_LIMIT,
};
if (roomId !== undefined) filter.rooms = [roomId];
const body = {
const body: ISearchRequestBody = {
search_categories: {
room_events: {
search_term: term,
filter: filter,
order_by: "recent",
order_by: SearchOrderBy.Recent,
event_context: {
before_limit: 1,
after_limit: 1,
@ -45,31 +61,26 @@ async function serverSideSearch(term, roomId = undefined) {
const response = await client.search({ body: body });
const result = {
response: response,
query: body,
};
return result;
return { response, query: body };
}
async function serverSideSearchProcess(term, roomId = undefined) {
async function serverSideSearchProcess(term: string, roomId: string = undefined): Promise<ISearchResults> {
const client = MatrixClientPeg.get();
const result = await serverSideSearch(term, roomId);
// The js-sdk method backPaginateRoomEventsSearch() uses _query internally
// so we're reusing the concept here since we wan't to delegate the
// so we're reusing the concept here since we want to delegate the
// pagination back to backPaginateRoomEventsSearch() in some cases.
const searchResult = {
const searchResults: ISearchResults = {
_query: result.query,
results: [],
highlights: [],
};
return client.processRoomEventsSearch(searchResult, result.response);
return client.processRoomEventsSearch(searchResults, result.response);
}
function compareEvents(a, b) {
function compareEvents(a: ISearchResult, b: ISearchResult): number {
const aEvent = a.result;
const bEvent = b.result;
@ -79,7 +90,7 @@ function compareEvents(a, b) {
return 0;
}
async function combinedSearch(searchTerm) {
async function combinedSearch(searchTerm: string): Promise<ISearchResults> {
const client = MatrixClientPeg.get();
// Create two promises, one for the local search, one for the
@ -111,10 +122,10 @@ async function combinedSearch(searchTerm) {
// returns since that one can be either a server-side one, a local one or a
// fake one to fetch the remaining cached events. See the docs for
// combineEvents() for an explanation why we need to cache events.
const emptyResult = {
const emptyResult: ISeshatSearchResults = {
seshatQuery: localQuery,
_query: serverQuery,
serverSideNextBatch: serverResponse.next_batch,
serverSideNextBatch: serverResponse.search_categories.room_events.next_batch,
cachedEvents: [],
oldestEventFrom: "server",
results: [],
@ -125,7 +136,7 @@ async function combinedSearch(searchTerm) {
const combinedResult = combineResponses(emptyResult, localResponse, serverResponse.search_categories.room_events);
// Let the client process the combined result.
const response = {
const response: ISearchResponse = {
search_categories: {
room_events: combinedResult,
},
@ -139,10 +150,14 @@ async function combinedSearch(searchTerm) {
return result;
}
async function localSearch(searchTerm, roomId = undefined, processResult = true) {
async function localSearch(
searchTerm: string,
roomId: string = undefined,
processResult = true,
): Promise<{ response: IResultRoomEvents, query: ISearchArgs }> {
const eventIndex = EventIndexPeg.get();
const searchArgs = {
const searchArgs: ISearchArgs = {
search_term: searchTerm,
before_limit: 1,
after_limit: 1,
@ -167,11 +182,18 @@ async function localSearch(searchTerm, roomId = undefined, processResult = true)
return result;
}
async function localSearchProcess(searchTerm, roomId = undefined) {
export interface ISeshatSearchResults extends ISearchResults {
seshatQuery?: ISearchArgs;
cachedEvents?: ISearchResult[];
oldestEventFrom?: "local" | "server";
serverSideNextBatch?: string;
}
async function localSearchProcess(searchTerm: string, roomId: string = undefined): Promise<ISeshatSearchResults> {
const emptyResult = {
results: [],
highlights: [],
};
} as ISeshatSearchResults;
if (searchTerm === "") return emptyResult;
@ -179,7 +201,7 @@ async function localSearchProcess(searchTerm, roomId = undefined) {
emptyResult.seshatQuery = result.query;
const response = {
const response: ISearchResponse = {
search_categories: {
room_events: result.response,
},
@ -192,7 +214,7 @@ async function localSearchProcess(searchTerm, roomId = undefined) {
return processedResult;
}
async function localPagination(searchResult) {
async function localPagination(searchResult: ISeshatSearchResults): Promise<ISeshatSearchResults> {
const eventIndex = EventIndexPeg.get();
const searchArgs = searchResult.seshatQuery;
@ -221,10 +243,10 @@ async function localPagination(searchResult) {
return result;
}
function compareOldestEvents(firstResults, secondResults) {
function compareOldestEvents(firstResults: ISearchResult[], secondResults: ISearchResult[]): number {
try {
const oldestFirstEvent = firstResults.results[firstResults.results.length - 1].result;
const oldestSecondEvent = secondResults.results[secondResults.results.length - 1].result;
const oldestFirstEvent = firstResults[firstResults.length - 1].result;
const oldestSecondEvent = secondResults[secondResults.length - 1].result;
if (oldestFirstEvent.origin_server_ts <= oldestSecondEvent.origin_server_ts) {
return -1;
@ -236,7 +258,12 @@ function compareOldestEvents(firstResults, secondResults) {
}
}
function combineEventSources(previousSearchResult, response, a, b) {
function combineEventSources(
previousSearchResult: ISeshatSearchResults,
response: IResultRoomEvents,
a: ISearchResult[],
b: ISearchResult[],
): void {
// Merge event sources and sort the events.
const combinedEvents = a.concat(b).sort(compareEvents);
// Put half of the events in the response, and cache the other half.
@ -353,8 +380,12 @@ function combineEventSources(previousSearchResult, response, a, b) {
* different event sources.
*
*/
function combineEvents(previousSearchResult, localEvents = undefined, serverEvents = undefined) {
const response = {};
function combineEvents(
previousSearchResult: ISeshatSearchResults,
localEvents: IResultRoomEvents = undefined,
serverEvents: IResultRoomEvents = undefined,
): IResultRoomEvents {
const response = {} as IResultRoomEvents;
const cachedEvents = previousSearchResult.cachedEvents;
let oldestEventFrom = previousSearchResult.oldestEventFrom;
@ -364,7 +395,7 @@ function combineEvents(previousSearchResult, localEvents = undefined, serverEven
// This is a first search call, combine the events from the server and
// the local index. Note where our oldest event came from, we shall
// fetch the next batch of events from the other source.
if (compareOldestEvents(localEvents, serverEvents) < 0) {
if (compareOldestEvents(localEvents.results, serverEvents.results) < 0) {
oldestEventFrom = "local";
}
@ -375,7 +406,7 @@ function combineEvents(previousSearchResult, localEvents = undefined, serverEven
// meaning that our oldest event was on the server.
// Change the source of the oldest event if our local event is older
// than the cached one.
if (compareOldestEvents(localEvents, cachedEvents) < 0) {
if (compareOldestEvents(localEvents.results, cachedEvents) < 0) {
oldestEventFrom = "local";
}
combineEventSources(previousSearchResult, response, localEvents.results, cachedEvents);
@ -384,7 +415,7 @@ function combineEvents(previousSearchResult, localEvents = undefined, serverEven
// meaning that our oldest event was in the local index.
// Change the source of the oldest event if our server event is older
// than the cached one.
if (compareOldestEvents(serverEvents, cachedEvents) < 0) {
if (compareOldestEvents(serverEvents.results, cachedEvents) < 0) {
oldestEventFrom = "server";
}
combineEventSources(previousSearchResult, response, serverEvents.results, cachedEvents);
@ -412,7 +443,11 @@ function combineEvents(previousSearchResult, localEvents = undefined, serverEven
* @return {object} A response object that combines the events from the
* different event sources.
*/
function combineResponses(previousSearchResult, localEvents = undefined, serverEvents = undefined) {
function combineResponses(
previousSearchResult: ISeshatSearchResults,
localEvents: IResultRoomEvents = undefined,
serverEvents: IResultRoomEvents = undefined,
): IResultRoomEvents {
// Combine our events first.
const response = combineEvents(previousSearchResult, localEvents, serverEvents);
@ -454,40 +489,49 @@ function combineResponses(previousSearchResult, localEvents = undefined, serverE
return response;
}
function restoreEncryptionInfo(searchResultSlice = []) {
interface IEncryptedSeshatEvent {
curve25519Key: string;
ed25519Key: string;
algorithm: string;
forwardingCurve25519KeyChain: string[];
}
function restoreEncryptionInfo(searchResultSlice: SearchResult[] = []): void {
for (let i = 0; i < searchResultSlice.length; i++) {
const timeline = searchResultSlice[i].context.getTimeline();
for (let j = 0; j < timeline.length; j++) {
const ev = timeline[j];
const mxEv = timeline[j];
const ev = mxEv.event as IEncryptedSeshatEvent;
if (ev.event.curve25519Key) {
ev.makeEncrypted(
"m.room.encrypted",
{ algorithm: ev.event.algorithm },
ev.event.curve25519Key,
ev.event.ed25519Key,
if (ev.curve25519Key) {
mxEv.makeEncrypted(
EventType.RoomMessageEncrypted,
{ algorithm: ev.algorithm },
ev.curve25519Key,
ev.ed25519Key,
);
ev.forwardingCurve25519KeyChain = ev.event.forwardingCurve25519KeyChain;
// @ts-ignore
mxEv.forwardingCurve25519KeyChain = ev.forwardingCurve25519KeyChain;
delete ev.event.curve25519Key;
delete ev.event.ed25519Key;
delete ev.event.algorithm;
delete ev.event.forwardingCurve25519KeyChain;
delete ev.curve25519Key;
delete ev.ed25519Key;
delete ev.algorithm;
delete ev.forwardingCurve25519KeyChain;
}
}
}
}
async function combinedPagination(searchResult) {
async function combinedPagination(searchResult: ISeshatSearchResults): Promise<ISeshatSearchResults> {
const eventIndex = EventIndexPeg.get();
const client = MatrixClientPeg.get();
const searchArgs = searchResult.seshatQuery;
const oldestEventFrom = searchResult.oldestEventFrom;
let localResult;
let serverSideResult;
let localResult: IResultRoomEvents;
let serverSideResult: ISearchResponse;
// Fetch events from the local index if we have a token for it and if it's
// the local indexes turn or the server has exhausted its results.
@ -502,7 +546,7 @@ async function combinedPagination(searchResult) {
serverSideResult = await client.search(body);
}
let serverEvents;
let serverEvents: IResultRoomEvents;
if (serverSideResult) {
serverEvents = serverSideResult.search_categories.room_events;
@ -532,8 +576,8 @@ async function combinedPagination(searchResult) {
return result;
}
function eventIndexSearch(term, roomId = undefined) {
let searchPromise;
function eventIndexSearch(term: string, roomId: string = undefined): Promise<ISearchResults> {
let searchPromise: Promise<ISearchResults>;
if (roomId !== undefined) {
if (MatrixClientPeg.get().isRoomEncrypted(roomId)) {
@ -554,7 +598,7 @@ function eventIndexSearch(term, roomId = undefined) {
return searchPromise;
}
function eventIndexSearchPagination(searchResult) {
function eventIndexSearchPagination(searchResult: ISeshatSearchResults): Promise<ISeshatSearchResults> {
const client = MatrixClientPeg.get();
const seshatQuery = searchResult.seshatQuery;
@ -580,7 +624,7 @@ function eventIndexSearchPagination(searchResult) {
}
}
export function searchPagination(searchResult) {
export function searchPagination(searchResult: ISearchResults): Promise<ISearchResults> {
const eventIndex = EventIndexPeg.get();
const client = MatrixClientPeg.get();
@ -590,7 +634,7 @@ export function searchPagination(searchResult) {
else return eventIndexSearchPagination(searchResult);
}
export default function eventSearch(term, roomId = undefined) {
export default function eventSearch(term: string, roomId: string = undefined): Promise<ISearchResults> {
const eventIndex = EventIndexPeg.get();
if (eventIndex === null) return serverSideSearchProcess(term, roomId);

View file

@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { ICryptoCallbacks, ISecretStorageKeyInfo } from 'matrix-js-sdk/src/matrix';
import { ICryptoCallbacks } from 'matrix-js-sdk/src/matrix';
import { ISecretStorageKeyInfo } from 'matrix-js-sdk/src/crypto/api';
import { MatrixClient } from 'matrix-js-sdk/src/client';
import Modal from './Modal';
import * as sdk from './index';
@ -354,6 +355,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
throw new Error("Secret storage creation canceled");
}
} else {
// FIXME: Using an import will result in test failures
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
await cli.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest) => {

View file

@ -23,7 +23,6 @@ import { User } from "matrix-js-sdk/src/models/user";
import * as ContentHelpers from 'matrix-js-sdk/src/content-helpers';
import { MatrixClientPeg } from './MatrixClientPeg';
import dis from './dispatcher/dispatcher';
import * as sdk from './index';
import { _t, _td } from './languageHandler';
import Modal from './Modal';
import MultiInviter from './utils/MultiInviter';
@ -35,7 +34,6 @@ import { getAddressType } from './UserAddress';
import { abbreviateUrl } from './utils/UrlUtils';
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from './utils/IdentityServerUtils';
import { isPermalinkHost, parsePermalink } from "./utils/permalinks/Permalinks";
import { inviteUsersToRoom } from "./RoomInvite";
import { WidgetType } from "./widgets/WidgetType";
import { Jitsi } from "./widgets/Jitsi";
import { parseFragment as parseHtml, Element as ChildElement } from "parse5";
@ -50,6 +48,13 @@ import { UIFeature } from "./settings/UIFeature";
import { CHAT_EFFECTS } from "./effects";
import CallHandler from "./CallHandler";
import { guessAndSetDMRoom } from "./Rooms";
import { upgradeRoom } from './utils/RoomUpgrade';
import UploadConfirmDialog from './components/views/dialogs/UploadConfirmDialog';
import ErrorDialog from './components/views/dialogs/ErrorDialog';
import DevtoolsDialog from './components/views/dialogs/DevtoolsDialog';
import RoomUpgradeWarningDialog from "./components/views/dialogs/RoomUpgradeWarningDialog";
import InfoDialog from "./components/views/dialogs/InfoDialog";
import SlashCommandHelpDialog from "./components/views/dialogs/SlashCommandHelpDialog";
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
interface HTMLInputEvent extends Event {
@ -63,7 +68,6 @@ const singleMxcUpload = async (): Promise<any> => {
fileSelector.onchange = (ev: HTMLInputEvent) => {
const file = ev.target.files[0];
const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
Modal.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, {
file,
onFinished: (shouldContinue) => {
@ -246,7 +250,6 @@ export const Commands = [
args: '<query>',
description: _td('Searches DuckDuckGo for results'),
runFn: function() {
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
// TODO Don't explain this away, actually show a search UI here.
Modal.createTrackedDialog('Slash Commands', '/ddg is not a command', ErrorDialog, {
title: _t('/ddg is not a command'),
@ -269,58 +272,13 @@ export const Commands = [
return reject(_t("You do not have the required permissions to use this command."));
}
const RoomUpgradeWarningDialog = sdk.getComponent("dialogs.RoomUpgradeWarningDialog");
const { finished } = Modal.createTrackedDialog('Slash Commands', 'upgrade room confirmation',
RoomUpgradeWarningDialog, { roomId: roomId, targetVersion: args }, /*className=*/null,
/*isPriority=*/false, /*isStatic=*/true);
return success(finished.then(async ([resp]) => {
if (!resp.continue) return;
let checkForUpgradeFn;
try {
const upgradePromise = cli.upgradeRoom(roomId, args);
// We have to wait for the js-sdk to give us the room back so
// we can more effectively abuse the MultiInviter behaviour
// which heavily relies on the Room object being available.
if (resp.invite) {
checkForUpgradeFn = async (newRoom) => {
// The upgradePromise should be done by the time we await it here.
const { replacement_room: newRoomId } = await upgradePromise;
if (newRoom.roomId !== newRoomId) return;
const toInvite = [
...room.getMembersWithMembership("join"),
...room.getMembersWithMembership("invite"),
].map(m => m.userId).filter(m => m !== cli.getUserId());
if (toInvite.length > 0) {
// Errors are handled internally to this function
await inviteUsersToRoom(newRoomId, toInvite);
}
cli.removeListener('Room', checkForUpgradeFn);
};
cli.on('Room', checkForUpgradeFn);
}
// We have to await after so that the checkForUpgradesFn has a proper reference
// to the new room's ID.
await upgradePromise;
} catch (e) {
console.error(e);
if (checkForUpgradeFn) cli.removeListener('Room', checkForUpgradeFn);
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
Modal.createTrackedDialog('Slash Commands', 'room upgrade error', ErrorDialog, {
title: _t('Error upgrading room'),
description: _t(
'Double check that your server supports the room version chosen and try again.'),
});
}
if (!resp?.continue) return;
await upgradeRoom(room, args, resp.invite);
}));
}
return reject(this.getUsage());
@ -434,7 +392,6 @@ export const Commands = [
const topic = topicEvents && topicEvents.getContent().topic;
const topicHtml = topic ? linkifyAndSanitizeHtml(topic) : _t('This room has no topic.');
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
Modal.createTrackedDialog('Slash Commands', 'Topic', InfoDialog, {
title: room.name,
description: <div dangerouslySetInnerHTML={{ __html: topicHtml }} />,
@ -523,7 +480,7 @@ export const Commands = [
aliases: ['j', 'goto'],
args: '<room-address>',
description: _td('Joins room with given address'),
runFn: function(_, args) {
runFn: function(roomId, args) {
if (args) {
// Note: we support 2 versions of this command. The first is
// the public-facing one for most users and the other is a
@ -737,7 +694,6 @@ export const Commands = [
ignoredUsers.push(userId); // de-duped internally in the js-sdk
return success(
cli.setIgnoredUsers(ignoredUsers).then(() => {
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
Modal.createTrackedDialog('Slash Commands', 'User ignored', InfoDialog, {
title: _t('Ignored user'),
description: <div>
@ -768,7 +724,6 @@ export const Commands = [
if (index !== -1) ignoredUsers.splice(index, 1);
return success(
cli.setIgnoredUsers(ignoredUsers).then(() => {
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
Modal.createTrackedDialog('Slash Commands', 'User unignored', InfoDialog, {
title: _t('Unignored user'),
description: <div>
@ -838,7 +793,6 @@ export const Commands = [
command: 'devtools',
description: _td('Opens the Developer Tools dialog'),
runFn: function(roomId) {
const DevtoolsDialog = sdk.getComponent('dialogs.DevtoolsDialog');
Modal.createDialog(DevtoolsDialog, { roomId });
return success();
},
@ -943,7 +897,6 @@ export const Commands = [
await cli.setDeviceVerified(userId, deviceId, true);
// Tell the user we verified everything
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
Modal.createTrackedDialog('Slash Commands', 'Verified key', InfoDialog, {
title: _t('Verified key'),
description: <div>
@ -1000,8 +953,6 @@ export const Commands = [
command: "help",
description: _td("Displays list of commands with usages and descriptions"),
runFn: function() {
const SlashCommandHelpDialog = sdk.getComponent('dialogs.SlashCommandHelpDialog');
Modal.createTrackedDialog('Slash Commands', 'Help', SlashCommandHelpDialog);
return success();
},
@ -1076,7 +1027,7 @@ export const Commands = [
command: "msg",
description: _td("Sends a message to the given user"),
args: "<user-id> <message>",
runFn: function(_, args) {
runFn: function(roomId, args) {
if (args) {
// matches the first whitespace delimited group and then the rest of the string
const matches = args.match(/^(\S+?)(?: +(.*))?$/s);

Some files were not shown because too many files have changed in this diff Show more