Merge branch 'develop' into text-for-event-perf
This commit is contained in:
commit
b147bcd207
238 changed files with 3504 additions and 2904 deletions
|
@ -1,16 +0,0 @@
|
||||||
# autogenerated file: run scripts/generate-eslint-error-ignore-file to update.
|
|
||||||
|
|
||||||
src/Markdown.js
|
|
||||||
src/NodeAnimator.js
|
|
||||||
src/components/structures/RoomDirectory.js
|
|
||||||
src/components/views/rooms/MemberList.js
|
|
||||||
src/ratelimitedfunc.js
|
|
||||||
src/utils/DMRoomMap.js
|
|
||||||
src/utils/MultiInviter.js
|
|
||||||
test/components/structures/MessagePanel-test.js
|
|
||||||
test/components/views/dialogs/InteractiveAuthDialog-test.js
|
|
||||||
test/mock-clock.js
|
|
||||||
src/component-index.js
|
|
||||||
test/end-to-end-tests/node_modules/
|
|
||||||
test/end-to-end-tests/element/
|
|
||||||
test/end-to-end-tests/synapse/
|
|
25
.eslintrc.js
25
.eslintrc.js
|
@ -24,6 +24,18 @@ module.exports = {
|
||||||
// It's disabled here, but we should using it sparingly.
|
// It's disabled here, but we should using it sparingly.
|
||||||
"react/jsx-no-bind": "off",
|
"react/jsx-no-bind": "off",
|
||||||
"react/jsx-key": ["error"],
|
"react/jsx-key": ["error"],
|
||||||
|
|
||||||
|
"no-restricted-properties": [
|
||||||
|
"error",
|
||||||
|
...buildRestrictedPropertiesOptions(
|
||||||
|
["window.innerHeight", "window.innerWidth", "window.visualViewport"],
|
||||||
|
"Use UIStore to access window dimensions instead.",
|
||||||
|
),
|
||||||
|
...buildRestrictedPropertiesOptions(
|
||||||
|
["*.mxcUrlToHttp", "*.getHttpUriForMxc"],
|
||||||
|
"Use Media helper instead to centralise access for customisation.",
|
||||||
|
),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
overrides: [{
|
overrides: [{
|
||||||
files: [
|
files: [
|
||||||
|
@ -49,21 +61,16 @@ module.exports = {
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
// We'd rather not do this but we do
|
// We'd rather not do this but we do
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
|
||||||
"no-restricted-properties": [
|
|
||||||
"error",
|
|
||||||
...buildRestrictedPropertiesOptions(
|
|
||||||
["window.innerHeight", "window.innerWidth", "window.visualViewport"],
|
|
||||||
"Use UIStore to access window dimensions instead",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
function buildRestrictedPropertiesOptions(properties, message) {
|
function buildRestrictedPropertiesOptions(properties, message) {
|
||||||
return properties.map(prop => {
|
return properties.map(prop => {
|
||||||
const [object, property] = prop.split(".");
|
let [object, property] = prop.split(".");
|
||||||
|
if (object === "*") {
|
||||||
|
object = undefined;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
object,
|
object,
|
||||||
property,
|
property,
|
||||||
|
|
171
CHANGELOG.md
171
CHANGELOG.md
|
@ -1,3 +1,174 @@
|
||||||
|
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)
|
||||||
|
|
||||||
|
* Remove reminescent references to the tinter
|
||||||
|
[\#6316](https://github.com/matrix-org/matrix-react-sdk/pull/6316)
|
||||||
|
* Update to released version of js-sdk
|
||||||
|
|
||||||
|
Changes in [3.25.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.25.0-rc.1) (2021-06-29)
|
||||||
|
===============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.24.0...v3.25.0-rc.1)
|
||||||
|
|
||||||
|
* Update to js-sdk v12.0.1-rc.1
|
||||||
|
* Translations update from Weblate
|
||||||
|
[\#6286](https://github.com/matrix-org/matrix-react-sdk/pull/6286)
|
||||||
|
* Fix back button on user info card after clicking a permalink
|
||||||
|
[\#6277](https://github.com/matrix-org/matrix-react-sdk/pull/6277)
|
||||||
|
* Group ACLs with MELS
|
||||||
|
[\#6280](https://github.com/matrix-org/matrix-react-sdk/pull/6280)
|
||||||
|
* Fix editState not getting passed through
|
||||||
|
[\#6282](https://github.com/matrix-org/matrix-react-sdk/pull/6282)
|
||||||
|
* Migrate message context menu to IconizedContextMenu
|
||||||
|
[\#5671](https://github.com/matrix-org/matrix-react-sdk/pull/5671)
|
||||||
|
* Improve audio recording performance
|
||||||
|
[\#6240](https://github.com/matrix-org/matrix-react-sdk/pull/6240)
|
||||||
|
* Fix multiple timeline panels handling composer and edit events
|
||||||
|
[\#6278](https://github.com/matrix-org/matrix-react-sdk/pull/6278)
|
||||||
|
* Let m.notice messages mark a room as unread
|
||||||
|
[\#6281](https://github.com/matrix-org/matrix-react-sdk/pull/6281)
|
||||||
|
* Removes the override on the Bubble Container
|
||||||
|
[\#5953](https://github.com/matrix-org/matrix-react-sdk/pull/5953)
|
||||||
|
* Fix IRC layout regressions
|
||||||
|
[\#6193](https://github.com/matrix-org/matrix-react-sdk/pull/6193)
|
||||||
|
* Fix trashcan.svg by exporting it with its viewbox
|
||||||
|
[\#6248](https://github.com/matrix-org/matrix-react-sdk/pull/6248)
|
||||||
|
* Fix tiny scrollbar dot on chrome/electron in Forward Dialog
|
||||||
|
[\#6276](https://github.com/matrix-org/matrix-react-sdk/pull/6276)
|
||||||
|
* Upgrade puppeteer to use newer version of Chrome
|
||||||
|
[\#6268](https://github.com/matrix-org/matrix-react-sdk/pull/6268)
|
||||||
|
* Make toast dismiss button less prominent
|
||||||
|
[\#6275](https://github.com/matrix-org/matrix-react-sdk/pull/6275)
|
||||||
|
* Encrypt the voice message file if needed
|
||||||
|
[\#6269](https://github.com/matrix-org/matrix-react-sdk/pull/6269)
|
||||||
|
* Fix hyper-precise presence
|
||||||
|
[\#6270](https://github.com/matrix-org/matrix-react-sdk/pull/6270)
|
||||||
|
* Fix issues around private spaces, including previewable
|
||||||
|
[\#6265](https://github.com/matrix-org/matrix-react-sdk/pull/6265)
|
||||||
|
* Make _pinned messages_ in `m.room.pinned_events` event clickable
|
||||||
|
[\#6257](https://github.com/matrix-org/matrix-react-sdk/pull/6257)
|
||||||
|
* Fix space avatar management layout being broken
|
||||||
|
[\#6266](https://github.com/matrix-org/matrix-react-sdk/pull/6266)
|
||||||
|
* Convert EntityTile, MemberTile and PresenceLabel to TS
|
||||||
|
[\#6251](https://github.com/matrix-org/matrix-react-sdk/pull/6251)
|
||||||
|
* Fix UserInfo not working when rendered without a room
|
||||||
|
[\#6260](https://github.com/matrix-org/matrix-react-sdk/pull/6260)
|
||||||
|
* Update membership reason handling, including leave reason displaying
|
||||||
|
[\#6253](https://github.com/matrix-org/matrix-react-sdk/pull/6253)
|
||||||
|
* Consolidate types with js-sdk changes
|
||||||
|
[\#6220](https://github.com/matrix-org/matrix-react-sdk/pull/6220)
|
||||||
|
* Fix edit history modal
|
||||||
|
[\#6258](https://github.com/matrix-org/matrix-react-sdk/pull/6258)
|
||||||
|
* Convert MemberList to TS
|
||||||
|
[\#6249](https://github.com/matrix-org/matrix-react-sdk/pull/6249)
|
||||||
|
* Fix two PRs duplicating the css attribute
|
||||||
|
[\#6259](https://github.com/matrix-org/matrix-react-sdk/pull/6259)
|
||||||
|
* Improve invite error messages in InviteDialog for room invites
|
||||||
|
[\#6201](https://github.com/matrix-org/matrix-react-sdk/pull/6201)
|
||||||
|
* Fix invite dialog being cut off when it has limited results
|
||||||
|
[\#6256](https://github.com/matrix-org/matrix-react-sdk/pull/6256)
|
||||||
|
* Fix pinning event in a room which hasn't had events pinned in before
|
||||||
|
[\#6255](https://github.com/matrix-org/matrix-react-sdk/pull/6255)
|
||||||
|
* Allow modal widget buttons to be disabled when the modal opens
|
||||||
|
[\#6178](https://github.com/matrix-org/matrix-react-sdk/pull/6178)
|
||||||
|
* Decrease e2e shield fill mask size so that it doesn't overlap
|
||||||
|
[\#6250](https://github.com/matrix-org/matrix-react-sdk/pull/6250)
|
||||||
|
* Dial Pad UI bug fixes
|
||||||
|
[\#5786](https://github.com/matrix-org/matrix-react-sdk/pull/5786)
|
||||||
|
* Simple handling of mid-call output changes
|
||||||
|
[\#6247](https://github.com/matrix-org/matrix-react-sdk/pull/6247)
|
||||||
|
* Improve ForwardDialog performance by using TruncatedList
|
||||||
|
[\#6228](https://github.com/matrix-org/matrix-react-sdk/pull/6228)
|
||||||
|
* Fix dependency and lockfile mismatch
|
||||||
|
[\#6246](https://github.com/matrix-org/matrix-react-sdk/pull/6246)
|
||||||
|
* Improve room directory click behaviour
|
||||||
|
[\#6234](https://github.com/matrix-org/matrix-react-sdk/pull/6234)
|
||||||
|
* Fix keyboard accessibility of the space panel
|
||||||
|
[\#6239](https://github.com/matrix-org/matrix-react-sdk/pull/6239)
|
||||||
|
* Add ways to manage addresses for Spaces
|
||||||
|
[\#6151](https://github.com/matrix-org/matrix-react-sdk/pull/6151)
|
||||||
|
* Hide communities invites and the community autocompleter when Spaces on
|
||||||
|
[\#6244](https://github.com/matrix-org/matrix-react-sdk/pull/6244)
|
||||||
|
* Convert bunch of files to TS
|
||||||
|
[\#6241](https://github.com/matrix-org/matrix-react-sdk/pull/6241)
|
||||||
|
* Open local addresses section by default when there are no existing local
|
||||||
|
addresses
|
||||||
|
[\#6179](https://github.com/matrix-org/matrix-react-sdk/pull/6179)
|
||||||
|
* Allow reordering of the space panel via Drag and Drop
|
||||||
|
[\#6137](https://github.com/matrix-org/matrix-react-sdk/pull/6137)
|
||||||
|
* Replace drag and drop mechanism in communities with something simpler
|
||||||
|
[\#6134](https://github.com/matrix-org/matrix-react-sdk/pull/6134)
|
||||||
|
* EventTilePreview fixes
|
||||||
|
[\#6000](https://github.com/matrix-org/matrix-react-sdk/pull/6000)
|
||||||
|
* Upgrade @types/react and @types/react-dom
|
||||||
|
[\#6233](https://github.com/matrix-org/matrix-react-sdk/pull/6233)
|
||||||
|
* Fix type error in the SpaceStore
|
||||||
|
[\#6242](https://github.com/matrix-org/matrix-react-sdk/pull/6242)
|
||||||
|
* Add experimental options to the Spaces beta
|
||||||
|
[\#6199](https://github.com/matrix-org/matrix-react-sdk/pull/6199)
|
||||||
|
* Consolidate types with js-sdk changes
|
||||||
|
[\#6215](https://github.com/matrix-org/matrix-react-sdk/pull/6215)
|
||||||
|
* Fix branch matching for Buildkite
|
||||||
|
[\#6236](https://github.com/matrix-org/matrix-react-sdk/pull/6236)
|
||||||
|
* Migrate SearchBar to TypeScript
|
||||||
|
[\#6230](https://github.com/matrix-org/matrix-react-sdk/pull/6230)
|
||||||
|
* Add support to keyboard shortcuts dialog for [digits]
|
||||||
|
[\#6088](https://github.com/matrix-org/matrix-react-sdk/pull/6088)
|
||||||
|
* Fix modal opening race condition
|
||||||
|
[\#6238](https://github.com/matrix-org/matrix-react-sdk/pull/6238)
|
||||||
|
* Deprecate FormButton in favour of AccessibleButton
|
||||||
|
[\#6229](https://github.com/matrix-org/matrix-react-sdk/pull/6229)
|
||||||
|
* Add PR template
|
||||||
|
[\#6216](https://github.com/matrix-org/matrix-react-sdk/pull/6216)
|
||||||
|
* Prefer canonical aliases while autocompleting rooms
|
||||||
|
[\#6222](https://github.com/matrix-org/matrix-react-sdk/pull/6222)
|
||||||
|
* Fix quote button
|
||||||
|
[\#6232](https://github.com/matrix-org/matrix-react-sdk/pull/6232)
|
||||||
|
* Restore branch matching support for GitHub Actions e2e tests
|
||||||
|
[\#6224](https://github.com/matrix-org/matrix-react-sdk/pull/6224)
|
||||||
|
* Fix View Source accessing renamed private field on MatrixEvent
|
||||||
|
[\#6225](https://github.com/matrix-org/matrix-react-sdk/pull/6225)
|
||||||
|
* Fix ConfirmUserActionDialog returning an input field rather than text
|
||||||
|
[\#6219](https://github.com/matrix-org/matrix-react-sdk/pull/6219)
|
||||||
|
* Revert "Partially restore immutable event objects at the rendering layer"
|
||||||
|
[\#6221](https://github.com/matrix-org/matrix-react-sdk/pull/6221)
|
||||||
|
* Add jq to e2e tests Dockerfile
|
||||||
|
[\#6218](https://github.com/matrix-org/matrix-react-sdk/pull/6218)
|
||||||
|
* Partially restore immutable event objects at the rendering layer
|
||||||
|
[\#6196](https://github.com/matrix-org/matrix-react-sdk/pull/6196)
|
||||||
|
* Update MSC number references for voice messages
|
||||||
|
[\#6197](https://github.com/matrix-org/matrix-react-sdk/pull/6197)
|
||||||
|
* Fix phase enum usage in JS modules as well
|
||||||
|
[\#6214](https://github.com/matrix-org/matrix-react-sdk/pull/6214)
|
||||||
|
* Migrate some dialogs to TypeScript
|
||||||
|
[\#6185](https://github.com/matrix-org/matrix-react-sdk/pull/6185)
|
||||||
|
* Typescript fixes due to MatrixEvent being TSified
|
||||||
|
[\#6208](https://github.com/matrix-org/matrix-react-sdk/pull/6208)
|
||||||
|
* Allow click-to-ping, quote & emoji picker for edit composer too
|
||||||
|
[\#5858](https://github.com/matrix-org/matrix-react-sdk/pull/5858)
|
||||||
|
* Add call silencing
|
||||||
|
[\#6082](https://github.com/matrix-org/matrix-react-sdk/pull/6082)
|
||||||
|
* Fix types in SlashCommands
|
||||||
|
[\#6207](https://github.com/matrix-org/matrix-react-sdk/pull/6207)
|
||||||
|
* Benchmark multiple common user scenario
|
||||||
|
[\#6190](https://github.com/matrix-org/matrix-react-sdk/pull/6190)
|
||||||
|
* Fix forward dialog message preview display names
|
||||||
|
[\#6204](https://github.com/matrix-org/matrix-react-sdk/pull/6204)
|
||||||
|
* Remove stray bullet point in reply preview
|
||||||
|
[\#6206](https://github.com/matrix-org/matrix-react-sdk/pull/6206)
|
||||||
|
* Stop requesting null next replies from the server
|
||||||
|
[\#6203](https://github.com/matrix-org/matrix-react-sdk/pull/6203)
|
||||||
|
* Fix soft crash caused by a broken shouldComponentUpdate
|
||||||
|
[\#6202](https://github.com/matrix-org/matrix-react-sdk/pull/6202)
|
||||||
|
* Keep composer reply when scrolling away from a highlighted event
|
||||||
|
[\#6200](https://github.com/matrix-org/matrix-react-sdk/pull/6200)
|
||||||
|
* Cache virtual/native room mappings when they're created
|
||||||
|
[\#6194](https://github.com/matrix-org/matrix-react-sdk/pull/6194)
|
||||||
|
* Disable comment-on-alert
|
||||||
|
[\#6191](https://github.com/matrix-org/matrix-react-sdk/pull/6191)
|
||||||
|
* Bump postcss from 7.0.35 to 7.0.36
|
||||||
|
[\#6195](https://github.com/matrix-org/matrix-react-sdk/pull/6195)
|
||||||
|
|
||||||
Changes in [3.24.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.24.0) (2021-06-21)
|
Changes in [3.24.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.24.0) (2021-06-21)
|
||||||
=====================================================================================================
|
=====================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.24.0-rc.1...v3.24.0)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.24.0-rc.1...v3.24.0)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "3.24.0",
|
"version": "3.25.0",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
"start:all": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n build,reskindex \"yarn start:build\" \"yarn reskindex:watch\"",
|
"start:all": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n build,reskindex \"yarn start:build\" \"yarn reskindex:watch\"",
|
||||||
"start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
|
"start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
|
||||||
"lint": "yarn lint:types && yarn lint:js && yarn lint:style",
|
"lint": "yarn lint:types && yarn lint:js && yarn lint:style",
|
||||||
"lint:js": "eslint --max-warnings 0 --ignore-path .eslintignore.errorfiles src test",
|
"lint:js": "eslint --max-warnings 0 src test",
|
||||||
"lint:types": "tsc --noEmit --jsx react",
|
"lint:types": "tsc --noEmit --jsx react",
|
||||||
"lint:style": "stylelint 'res/css/**/*.scss'",
|
"lint:style": "stylelint 'res/css/**/*.scss'",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
|
@ -55,6 +55,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.12.5",
|
"@babel/runtime": "^7.12.5",
|
||||||
"await-lock": "^2.1.0",
|
"await-lock": "^2.1.0",
|
||||||
|
"blurhash": "^1.1.3",
|
||||||
"browser-encrypt-attachment": "^0.3.0",
|
"browser-encrypt-attachment": "^0.3.0",
|
||||||
"browser-request": "^0.3.3",
|
"browser-request": "^0.3.3",
|
||||||
"cheerio": "^1.0.0-rc.9",
|
"cheerio": "^1.0.0-rc.9",
|
||||||
|
@ -78,7 +79,7 @@
|
||||||
"katex": "^0.12.0",
|
"katex": "^0.12.0",
|
||||||
"linkifyjs": "^2.1.9",
|
"linkifyjs": "^2.1.9",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"matrix-js-sdk": "12.0.0",
|
"matrix-js-sdk": "12.0.1",
|
||||||
"matrix-widget-api": "^0.1.0-beta.15",
|
"matrix-widget-api": "^0.1.0-beta.15",
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"opus-recorder": "^8.0.3",
|
"opus-recorder": "^8.0.3",
|
||||||
|
@ -90,6 +91,7 @@
|
||||||
"re-resizable": "^6.9.0",
|
"re-resizable": "^6.9.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-beautiful-dnd": "^13.1.0",
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
|
"react-blurhash": "^0.1.3",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-focus-lock": "^2.5.0",
|
"react-focus-lock": "^2.5.0",
|
||||||
"react-transition-group": "^4.4.1",
|
"react-transition-group": "^4.4.1",
|
||||||
|
@ -122,6 +124,7 @@
|
||||||
"@peculiar/webcrypto": "^1.1.4",
|
"@peculiar/webcrypto": "^1.1.4",
|
||||||
"@sinonjs/fake-timers": "^7.0.2",
|
"@sinonjs/fake-timers": "^7.0.2",
|
||||||
"@types/classnames": "^2.2.11",
|
"@types/classnames": "^2.2.11",
|
||||||
|
"@types/commonmark": "^0.27.4",
|
||||||
"@types/counterpart": "^0.18.1",
|
"@types/counterpart": "^0.18.1",
|
||||||
"@types/diff-match-patch": "^1.0.32",
|
"@types/diff-match-patch": "^1.0.32",
|
||||||
"@types/flux": "^3.1.9",
|
"@types/flux": "^3.1.9",
|
||||||
|
|
|
@ -37,6 +37,11 @@
|
||||||
@import "./structures/_ViewSource.scss";
|
@import "./structures/_ViewSource.scss";
|
||||||
@import "./structures/auth/_CompleteSecurity.scss";
|
@import "./structures/auth/_CompleteSecurity.scss";
|
||||||
@import "./structures/auth/_Login.scss";
|
@import "./structures/auth/_Login.scss";
|
||||||
|
@import "./views/audio_messages/_AudioPlayer.scss";
|
||||||
|
@import "./views/audio_messages/_PlayPauseButton.scss";
|
||||||
|
@import "./views/audio_messages/_PlaybackContainer.scss";
|
||||||
|
@import "./views/audio_messages/_SeekBar.scss";
|
||||||
|
@import "./views/audio_messages/_Waveform.scss";
|
||||||
@import "./views/auth/_AuthBody.scss";
|
@import "./views/auth/_AuthBody.scss";
|
||||||
@import "./views/auth/_AuthButtons.scss";
|
@import "./views/auth/_AuthButtons.scss";
|
||||||
@import "./views/auth/_AuthFooter.scss";
|
@import "./views/auth/_AuthFooter.scss";
|
||||||
|
@ -52,7 +57,6 @@
|
||||||
@import "./views/avatars/_BaseAvatar.scss";
|
@import "./views/avatars/_BaseAvatar.scss";
|
||||||
@import "./views/avatars/_DecoratedRoomAvatar.scss";
|
@import "./views/avatars/_DecoratedRoomAvatar.scss";
|
||||||
@import "./views/avatars/_MemberStatusMessageAvatar.scss";
|
@import "./views/avatars/_MemberStatusMessageAvatar.scss";
|
||||||
@import "./views/avatars/_PulsedAvatar.scss";
|
|
||||||
@import "./views/avatars/_WidgetAvatar.scss";
|
@import "./views/avatars/_WidgetAvatar.scss";
|
||||||
@import "./views/beta/_BetaCard.scss";
|
@import "./views/beta/_BetaCard.scss";
|
||||||
@import "./views/context_menus/_CallContextMenu.scss";
|
@import "./views/context_menus/_CallContextMenu.scss";
|
||||||
|
@ -165,6 +169,7 @@
|
||||||
@import "./views/messages/_MTextBody.scss";
|
@import "./views/messages/_MTextBody.scss";
|
||||||
@import "./views/messages/_MVideoBody.scss";
|
@import "./views/messages/_MVideoBody.scss";
|
||||||
@import "./views/messages/_MVoiceMessageBody.scss";
|
@import "./views/messages/_MVoiceMessageBody.scss";
|
||||||
|
@import "./views/messages/_MediaBody.scss";
|
||||||
@import "./views/messages/_MessageActionBar.scss";
|
@import "./views/messages/_MessageActionBar.scss";
|
||||||
@import "./views/messages/_MessageTimestamp.scss";
|
@import "./views/messages/_MessageTimestamp.scss";
|
||||||
@import "./views/messages/_MjolnirBody.scss";
|
@import "./views/messages/_MjolnirBody.scss";
|
||||||
|
@ -196,6 +201,7 @@
|
||||||
@import "./views/rooms/_GroupLayout.scss";
|
@import "./views/rooms/_GroupLayout.scss";
|
||||||
@import "./views/rooms/_IRCLayout.scss";
|
@import "./views/rooms/_IRCLayout.scss";
|
||||||
@import "./views/rooms/_JumpToBottomButton.scss";
|
@import "./views/rooms/_JumpToBottomButton.scss";
|
||||||
|
@import "./views/rooms/_LinkPreviewGroup.scss";
|
||||||
@import "./views/rooms/_LinkPreviewWidget.scss";
|
@import "./views/rooms/_LinkPreviewWidget.scss";
|
||||||
@import "./views/rooms/_MemberInfo.scss";
|
@import "./views/rooms/_MemberInfo.scss";
|
||||||
@import "./views/rooms/_MemberList.scss";
|
@import "./views/rooms/_MemberList.scss";
|
||||||
|
@ -253,12 +259,10 @@
|
||||||
@import "./views/toasts/_AnalyticsToast.scss";
|
@import "./views/toasts/_AnalyticsToast.scss";
|
||||||
@import "./views/toasts/_NonUrgentEchoFailureToast.scss";
|
@import "./views/toasts/_NonUrgentEchoFailureToast.scss";
|
||||||
@import "./views/verification/_VerificationShowSas.scss";
|
@import "./views/verification/_VerificationShowSas.scss";
|
||||||
@import "./views/voice_messages/_PlayPauseButton.scss";
|
|
||||||
@import "./views/voice_messages/_PlaybackContainer.scss";
|
|
||||||
@import "./views/voice_messages/_Waveform.scss";
|
|
||||||
@import "./views/voip/_CallContainer.scss";
|
@import "./views/voip/_CallContainer.scss";
|
||||||
@import "./views/voip/_CallView.scss";
|
@import "./views/voip/_CallView.scss";
|
||||||
@import "./views/voip/_CallViewForRoom.scss";
|
@import "./views/voip/_CallViewForRoom.scss";
|
||||||
|
@import "./views/voip/_CallPreview.scss";
|
||||||
@import "./views/voip/_DialPad.scss";
|
@import "./views/voip/_DialPad.scss";
|
||||||
@import "./views/voip/_DialPadContextMenu.scss";
|
@import "./views/voip/_DialPadContextMenu.scss";
|
||||||
@import "./views/voip/_DialPadModal.scss";
|
@import "./views/voip/_DialPadModal.scss";
|
||||||
|
|
|
@ -323,7 +323,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_GroupView_featuredThing .mx_BaseAvatar {
|
.mx_GroupView_featuredThing .mx_BaseAvatar {
|
||||||
/* To prevent misalignment with mx_TintableSvg (in addButton) */
|
/* To prevent misalignment with img (in addButton) */
|
||||||
vertical-align: initial;
|
vertical-align: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,23 +121,51 @@ $pulse-color: $pinned-unread-color;
|
||||||
box-shadow: 0 0 0 0 rgba($pulse-color, 1);
|
box-shadow: 0 0 0 0 rgba($pulse-color, 1);
|
||||||
animation: mx_RightPanel_indicator_pulse 2s infinite;
|
animation: mx_RightPanel_indicator_pulse 2s infinite;
|
||||||
animation-iteration-count: 1;
|
animation-iteration-count: 1;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transform: scale(1);
|
||||||
|
transform-origin: center center;
|
||||||
|
animation-name: mx_RightPanel_indicator_pulse_shadow;
|
||||||
|
animation-duration: inherit;
|
||||||
|
animation-iteration-count: inherit;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba($pulse-color, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes mx_RightPanel_indicator_pulse {
|
@keyframes mx_RightPanel_indicator_pulse {
|
||||||
0% {
|
0% {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
box-shadow: 0 0 0 0 rgba($pulse-color, 0.7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
70% {
|
70% {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
box-shadow: 0 0 0 10px rgba($pulse-color, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
box-shadow: 0 0 0 0 rgba($pulse-color, 0);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mx_RightPanel_indicator_pulse_shadow {
|
||||||
|
0% {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
transform: scale(2.2);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,14 +57,15 @@ limitations under the License.
|
||||||
|
|
||||||
@keyframes mx_RoomView_fileDropTarget_image_animation {
|
@keyframes mx_RoomView_fileDropTarget_image_animation {
|
||||||
from {
|
from {
|
||||||
width: 0px;
|
transform: scaleX(0);
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
width: 32px;
|
transform: scaleX(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomView_fileDropTarget_image {
|
.mx_RoomView_fileDropTarget_image {
|
||||||
|
width: 32px;
|
||||||
animation: mx_RoomView_fileDropTarget_image_animation;
|
animation: mx_RoomView_fileDropTarget_image_animation;
|
||||||
animation-duration: 0.5s;
|
animation-duration: 0.5s;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|
68
res/css/views/audio_messages/_AudioPlayer.scss
Normal file
68
res/css/views/audio_messages/_AudioPlayer.scss
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
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_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;
|
||||||
|
|
||||||
|
.mx_PlayPauseButton {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AudioPlayer_mediaInfo {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden; // makes the ellipsis on the file name work
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AudioPlayer_mediaName {
|
||||||
|
color: $primary-fg-color;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-bottom: 4px; // mimics the line-height differences in the Figma
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AudioPlayer_byline {
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AudioPlayer_seek {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.mx_SeekBar {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Clock {
|
||||||
|
width: $font-42px; // we're not using a monospace font, so fake it
|
||||||
|
min-width: $font-42px; // for flexbox
|
||||||
|
padding-left: 4px; // isolate from seek bar
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,8 @@ limitations under the License.
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
min-width: 32px; // for when the button is used in a flexbox
|
||||||
|
min-height: 32px; // for when the button is used in a flexbox
|
||||||
border-radius: 32px;
|
border-radius: 32px;
|
||||||
background-color: $voice-playback-button-bg-color;
|
background-color: $voice-playback-button-bg-color;
|
||||||
|
|
|
@ -22,17 +22,11 @@ limitations under the License.
|
||||||
// 7px top and bottom for visual design. 12px left & right, but the waveform (right)
|
// 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.
|
// has a 1px padding on it that we want to account for.
|
||||||
padding: 7px 12px 7px 11px;
|
padding: 7px 12px 7px 11px;
|
||||||
background-color: $voice-record-waveform-bg-color;
|
|
||||||
border-radius: 12px;
|
|
||||||
|
|
||||||
// Cheat at alignment a bit
|
// Cheat at alignment a bit
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
color: $voice-record-waveform-fg-color;
|
|
||||||
font-size: $font-14px;
|
|
||||||
line-height: $font-24px;
|
|
||||||
|
|
||||||
contain: content;
|
contain: content;
|
||||||
|
|
||||||
.mx_Waveform {
|
.mx_Waveform {
|
||||||
|
@ -45,7 +39,7 @@ limitations under the License.
|
||||||
&.mx_Waveform_bar_100pct {
|
&.mx_Waveform_bar_100pct {
|
||||||
// Small animation to remove the mechanical feel of progress
|
// Small animation to remove the mechanical feel of progress
|
||||||
transition: background-color 250ms ease;
|
transition: background-color 250ms ease;
|
||||||
background-color: $voice-record-waveform-fg-color;
|
background-color: $message-body-panel-fg-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
103
res/css/views/audio_messages/_SeekBar.scss
Normal file
103
res/css/views/audio_messages/_SeekBar.scss
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// CSS inspiration from:
|
||||||
|
// * https://www.w3schools.com/howto/howto_js_rangeslider.asp
|
||||||
|
// * https://stackoverflow.com/a/28283806
|
||||||
|
// * https://css-tricks.com/styling-cross-browser-compatible-range-inputs-css/
|
||||||
|
|
||||||
|
.mx_SeekBar {
|
||||||
|
// Dev note: we deliberately do not have the -ms-track (and friends) selectors because we don't
|
||||||
|
// need to support IE.
|
||||||
|
|
||||||
|
appearance: none; // default style override
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background: $quaternary-fg-color;
|
||||||
|
outline: none; // remove blue selection border
|
||||||
|
position: relative; // for before+after pseudo elements later on
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&::-webkit-slider-thumb {
|
||||||
|
appearance: none; // default style override
|
||||||
|
|
||||||
|
// Dev note: This needs to be duplicated with the -moz-range-thumb selector
|
||||||
|
// because otherwise Edge (webkit) will fail to see the styles and just refuse
|
||||||
|
// to apply them.
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-thumb {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
// Firefox adds a border on the thumb
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is for webkit support, but we can't limit the functionality of it to just webkit
|
||||||
|
// browsers. Firefox responds to webkit-prefixed values now, which means we can't use media
|
||||||
|
// or support queries to selectively apply the rule. An upside is that this CSS doesn't work
|
||||||
|
// in firefox, so it's just wasted CPU/GPU time.
|
||||||
|
&::before { // ::before to ensure it ends up under the thumb
|
||||||
|
content: '';
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
|
||||||
|
// Absolute positioning to ensure it overlaps with the existing bar
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
// Sizing to match the bar
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
|
||||||
|
// And finally dynamic width without overly hurting the rendering engine.
|
||||||
|
transform-origin: 0 100%;
|
||||||
|
transform: scaleX(var(--fillTo));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is firefox's built-in support for the above, with 100% less hacks.
|
||||||
|
&::-moz-range-progress {
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase clickable area for the slider (approximately same size as browser default)
|
||||||
|
// We do it this way to keep the same padding and margins of the element, avoiding margin math.
|
||||||
|
// Source: https://front-back.com/expand-clickable-areas-for-a-better-touch-experience/
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -6px;
|
||||||
|
bottom: -6px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,24 +110,52 @@ $dot-size: 12px;
|
||||||
width: $dot-size;
|
width: $dot-size;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
background: rgba($pulse-color, 1);
|
background: rgba($pulse-color, 1);
|
||||||
box-shadow: 0 0 0 0 rgba($pulse-color, 1);
|
|
||||||
animation: mx_Beta_bluePulse 2s infinite;
|
animation: mx_Beta_bluePulse 2s infinite;
|
||||||
animation-iteration-count: 20;
|
animation-iteration-count: 20;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transform: scale(1);
|
||||||
|
transform-origin: center center;
|
||||||
|
animation-name: mx_Beta_bluePulse_shadow;
|
||||||
|
animation-duration: inherit;
|
||||||
|
animation-iteration-count: inherit;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba($pulse-color, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes mx_Beta_bluePulse {
|
@keyframes mx_Beta_bluePulse {
|
||||||
0% {
|
0% {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
box-shadow: 0 0 0 0 rgba($pulse-color, 0.7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
70% {
|
70% {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
box-shadow: 0 0 0 10px rgba($pulse-color, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
box-shadow: 0 0 0 0 rgba($pulse-color, 0);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mx_Beta_bluePulse_shadow {
|
||||||
|
0% {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
transform: scale(2.2);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ limitations under the License.
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 2px; // alignment
|
top: 2px; // alignment
|
||||||
background-image: url("$(res)/img/element-icons/warning-badge.svg");
|
background-image: url("$(res)/img/element-icons/warning-badge.svg");
|
||||||
|
background-size: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AccessSecretStorageDialog_reset_link {
|
.mx_AccessSecretStorageDialog_reset_link {
|
||||||
|
|
|
@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
$timelineImageBorderRadius: 4px;
|
||||||
|
|
||||||
.mx_MImageBody {
|
.mx_MImageBody {
|
||||||
display: block;
|
display: block;
|
||||||
margin-right: 34px;
|
margin-right: 34px;
|
||||||
|
@ -25,7 +27,11 @@ limitations under the License.
|
||||||
height: 100%;
|
height: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
border-radius: 4px;
|
border-radius: $timelineImageBorderRadius;
|
||||||
|
|
||||||
|
> canvas {
|
||||||
|
border-radius: $timelineImageBorderRadius;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MImageBody_thumbnail_container {
|
.mx_MImageBody_thumbnail_container {
|
||||||
|
@ -43,7 +49,7 @@ limitations under the License.
|
||||||
top: 50%;
|
top: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inner img and TintableSvg should be centered around 0, 0
|
// Inner img should be centered around 0, 0
|
||||||
.mx_MImageBody_thumbnail_spinner > * {
|
.mx_MImageBody_thumbnail_spinner > * {
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
|
|
28
res/css/views/messages/_MediaBody.scss
Normal file
28
res/css/views/messages/_MediaBody.scss
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// A "media body" is any file upload looking thing, apart from images and videos (they
|
||||||
|
// have unique styles).
|
||||||
|
|
||||||
|
.mx_MediaBody {
|
||||||
|
background-color: $message-body-panel-bg-color;
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
|
color: $message-body-panel-fg-color;
|
||||||
|
font-size: $font-14px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ limitations under the License.
|
||||||
.mx_cryptoEvent_buttons {
|
.mx_cryptoEvent_buttons {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_cryptoEvent_state {
|
.mx_cryptoEvent_state {
|
||||||
|
|
|
@ -477,8 +477,7 @@ $hover-select-border: 4px;
|
||||||
|
|
||||||
pre, code {
|
pre, code {
|
||||||
font-family: $monospace-font-family !important;
|
font-family: $monospace-font-family !important;
|
||||||
// deliberate constants as we're behind an invert filter
|
background-color: $header-panel-bg-color;
|
||||||
color: #333;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
|
@ -488,11 +487,6 @@ $hover-select-border: 4px;
|
||||||
overflow-x: overlay;
|
overflow-x: overlay;
|
||||||
overflow-y: visible;
|
overflow-y: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
|
||||||
// deliberate constants as we're behind an invert filter
|
|
||||||
background-color: #f8f8f8;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_lineNumbers {
|
.mx_EventTile_lineNumbers {
|
||||||
|
|
38
res/css/views/rooms/_LinkPreviewGroup.scss
Normal file
38
res/css/views/rooms/_LinkPreviewGroup.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,38 +33,29 @@ limitations under the License.
|
||||||
.mx_LinkPreviewWidget_caption {
|
.mx_LinkPreviewWidget_caption {
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
overflow-x: hidden; // cause it to wrap rather than clip
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_LinkPreviewWidget_title {
|
.mx_LinkPreviewWidget_title {
|
||||||
display: inline;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.mx_LinkPreviewWidget_siteName {
|
.mx_LinkPreviewWidget_siteName {
|
||||||
display: inline;
|
font-weight: normal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_LinkPreviewWidget_description {
|
.mx_LinkPreviewWidget_description {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
.mx_LinkPreviewWidget_cancel {
|
-webkit-box-orient: vertical;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MatrixChat_useCompactLayout {
|
.mx_MatrixChat_useCompactLayout {
|
||||||
|
|
|
@ -30,8 +30,8 @@ limitations under the License.
|
||||||
pointer-events: initial; // restore pointer events so the user can leave/interact
|
pointer-events: initial; // restore pointer events so the user can leave/interact
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.mx_CallView_video {
|
.mx_VideoFeed_remote.mx_VideoFeed_voice {
|
||||||
width: 350px;
|
min-height: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_VideoFeed_local {
|
.mx_VideoFeed_local {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -14,17 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_PulsedAvatar {
|
.mx_CallPreview {
|
||||||
@keyframes shadow-pulse {
|
position: fixed;
|
||||||
0% {
|
left: 0;
|
||||||
box-shadow: 0 0 0 0px rgba($accent-color, 0.2);
|
top: 0;
|
||||||
}
|
|
||||||
100% {
|
|
||||||
box-shadow: 0 0 0 6px rgba($accent-color, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
animation: shadow-pulse 1s infinite;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -39,7 +39,6 @@ limitations under the License.
|
||||||
.mx_CallView_pip {
|
.mx_CallView_pip {
|
||||||
width: 320px;
|
width: 320px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
margin-top: 10px;
|
|
||||||
background-color: $voipcall-plinth-color;
|
background-color: $voipcall-plinth-color;
|
||||||
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20);
|
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.20);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
|
@ -15,8 +15,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_VideoFeed_voice {
|
.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;
|
background-color: $inverted-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -119,8 +119,6 @@ $voipcall-plinth-color: #394049;
|
||||||
|
|
||||||
$theme-button-bg-color: #e3e8f0;
|
$theme-button-bg-color: #e3e8f0;
|
||||||
$dialpad-button-bg-color: #6F7882;
|
$dialpad-button-bg-color: #6F7882;
|
||||||
;
|
|
||||||
|
|
||||||
|
|
||||||
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
|
$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;
|
$roomlist-filter-active-bg-color: $bg-color;
|
||||||
|
@ -215,8 +213,6 @@ $message-body-panel-icon-fg-color: #21262C; // "Separator"
|
||||||
$message-body-panel-icon-bg-color: $tertiary-fg-color;
|
$message-body-panel-icon-bg-color: $tertiary-fg-color;
|
||||||
|
|
||||||
$voice-record-stop-border-color: $quaternary-fg-color;
|
$voice-record-stop-border-color: $quaternary-fg-color;
|
||||||
$voice-record-waveform-bg-color: $message-body-panel-bg-color;
|
|
||||||
$voice-record-waveform-fg-color: $message-body-panel-fg-color;
|
|
||||||
$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color;
|
$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color;
|
||||||
$voice-record-icon-color: $quaternary-fg-color;
|
$voice-record-icon-color: $quaternary-fg-color;
|
||||||
$voice-playback-button-bg-color: $message-body-panel-icon-bg-color;
|
$voice-playback-button-bg-color: $message-body-panel-icon-bg-color;
|
||||||
|
@ -276,24 +272,7 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28);
|
||||||
}
|
}
|
||||||
|
|
||||||
// markdown overrides:
|
// 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 {
|
.mx_EventTile_content .markdown-body {
|
||||||
pre, code {
|
|
||||||
filter: invert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
pre code {
|
|
||||||
filter: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
table {
|
||||||
tr {
|
tr {
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
|
@ -303,18 +282,9 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28);
|
||||||
background-color: #080808;
|
background-color: #080808;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
|
||||||
color: #919191;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// diff highlight colors
|
// highlight.js overrides
|
||||||
// intentionally swapped to avoid inversion
|
.hljs-tag {
|
||||||
.hljs-addition {
|
color: inherit; // Without this they'd be weirdly blue which doesn't match the theme
|
||||||
background: #fdd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-deletion {
|
|
||||||
background: #dfd;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,3 +9,4 @@
|
||||||
@import "_dark.scss";
|
@import "_dark.scss";
|
||||||
@import "../../light/css/_mods.scss";
|
@import "../../light/css/_mods.scss";
|
||||||
@import "../../../../res/css/_components.scss";
|
@import "../../../../res/css/_components.scss";
|
||||||
|
@import url("highlight.js/styles/atom-one-dark.css");
|
||||||
|
|
|
@ -20,6 +20,9 @@ $tertiary-fg-color: $primary-fg-color;
|
||||||
$primary-bg-color: $bg-color;
|
$primary-bg-color: $bg-color;
|
||||||
$muted-fg-color: $header-panel-text-primary-color;
|
$muted-fg-color: $header-panel-text-primary-color;
|
||||||
|
|
||||||
|
// Legacy theme backports
|
||||||
|
$quaternary-fg-color: #6F7882;
|
||||||
|
|
||||||
// used for dialog box text
|
// used for dialog box text
|
||||||
$light-fg-color: $header-panel-text-secondary-color;
|
$light-fg-color: $header-panel-text-secondary-color;
|
||||||
|
|
||||||
|
@ -115,7 +118,7 @@ $voipcall-plinth-color: #394049;
|
||||||
|
|
||||||
$theme-button-bg-color: #e3e8f0;
|
$theme-button-bg-color: #e3e8f0;
|
||||||
$dialpad-button-bg-color: #6F7882;
|
$dialpad-button-bg-color: #6F7882;
|
||||||
;
|
|
||||||
|
|
||||||
$roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons
|
$roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons
|
||||||
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
|
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
|
||||||
|
@ -209,8 +212,6 @@ $message-body-panel-icon-bg-color: $secondary-fg-color;
|
||||||
|
|
||||||
// See non-legacy dark for variable information
|
// See non-legacy dark for variable information
|
||||||
$voice-record-stop-border-color: #6F7882;
|
$voice-record-stop-border-color: #6F7882;
|
||||||
$voice-record-waveform-bg-color: $message-body-panel-bg-color;
|
|
||||||
$voice-record-waveform-fg-color: $message-body-panel-fg-color;
|
|
||||||
$voice-record-waveform-incomplete-fg-color: #6F7882;
|
$voice-record-waveform-incomplete-fg-color: #6F7882;
|
||||||
$voice-record-icon-color: #6F7882;
|
$voice-record-icon-color: #6F7882;
|
||||||
$voice-playback-button-bg-color: $tertiary-fg-color;
|
$voice-playback-button-bg-color: $tertiary-fg-color;
|
||||||
|
@ -266,18 +267,7 @@ $composer-shadow-color: tranparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// markdown overrides:
|
// markdown overrides:
|
||||||
.mx_EventTile_content .markdown-body pre:hover {
|
|
||||||
border-color: #808080 !important; // inverted due to rules below
|
|
||||||
}
|
|
||||||
.mx_EventTile_content .markdown-body {
|
.mx_EventTile_content .markdown-body {
|
||||||
pre, code {
|
|
||||||
filter: invert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
pre code {
|
|
||||||
filter: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
table {
|
||||||
tr {
|
tr {
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
|
@ -289,12 +279,7 @@ $composer-shadow-color: tranparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// diff highlight colors
|
// highlight.js overrides:
|
||||||
// intentionally swapped to avoid inversion
|
.hljs-tag {
|
||||||
.hljs-addition {
|
color: inherit; // Without this they'd be weirdly blue which doesn't match the theme
|
||||||
background: #fdd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hljs-deletion {
|
|
||||||
background: #dfd;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,3 +4,4 @@
|
||||||
@import "../../legacy-light/css/_legacy-light.scss";
|
@import "../../legacy-light/css/_legacy-light.scss";
|
||||||
@import "_legacy-dark.scss";
|
@import "_legacy-dark.scss";
|
||||||
@import "../../../../res/css/_components.scss";
|
@import "../../../../res/css/_components.scss";
|
||||||
|
@import url("highlight.js/styles/atom-one-dark.css");
|
||||||
|
|
|
@ -28,6 +28,9 @@ $tertiary-fg-color: $primary-fg-color;
|
||||||
$primary-bg-color: #ffffff;
|
$primary-bg-color: #ffffff;
|
||||||
$muted-fg-color: #61708b; // Commonly used in headings and relevant alt text
|
$muted-fg-color: #61708b; // Commonly used in headings and relevant alt text
|
||||||
|
|
||||||
|
// Legacy theme backports
|
||||||
|
$quaternary-fg-color: #C1C6CD;
|
||||||
|
|
||||||
// used for dialog box text
|
// used for dialog box text
|
||||||
$light-fg-color: #747474;
|
$light-fg-color: #747474;
|
||||||
|
|
||||||
|
@ -334,8 +337,6 @@ $message-body-panel-icon-bg-color: $primary-bg-color;
|
||||||
$voice-record-stop-symbol-color: #ff4b55;
|
$voice-record-stop-symbol-color: #ff4b55;
|
||||||
$voice-record-live-circle-color: #ff4b55;
|
$voice-record-live-circle-color: #ff4b55;
|
||||||
$voice-record-stop-border-color: #E3E8F0;
|
$voice-record-stop-border-color: #E3E8F0;
|
||||||
$voice-record-waveform-bg-color: $message-body-panel-bg-color;
|
|
||||||
$voice-record-waveform-fg-color: $message-body-panel-fg-color;
|
|
||||||
$voice-record-waveform-incomplete-fg-color: #C1C6CD;
|
$voice-record-waveform-incomplete-fg-color: #C1C6CD;
|
||||||
$voice-record-icon-color: $tertiary-fg-color;
|
$voice-record-icon-color: $tertiary-fg-color;
|
||||||
$voice-playback-button-bg-color: $message-body-panel-icon-bg-color;
|
$voice-playback-button-bg-color: $message-body-panel-icon-bg-color;
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
@import "_fonts.scss";
|
@import "_fonts.scss";
|
||||||
@import "_legacy-light.scss";
|
@import "_legacy-light.scss";
|
||||||
@import "../../../../res/css/_components.scss";
|
@import "../../../../res/css/_components.scss";
|
||||||
|
@import url("highlight.js/styles/atom-one-light.css");
|
||||||
|
|
|
@ -335,8 +335,6 @@ $voice-record-stop-symbol-color: #ff4b55;
|
||||||
$voice-record-live-circle-color: #ff4b55;
|
$voice-record-live-circle-color: #ff4b55;
|
||||||
|
|
||||||
$voice-record-stop-border-color: #E3E8F0; // "Separator"
|
$voice-record-stop-border-color: #E3E8F0; // "Separator"
|
||||||
$voice-record-waveform-bg-color: $message-body-panel-bg-color;
|
|
||||||
$voice-record-waveform-fg-color: $message-body-panel-fg-color;
|
|
||||||
$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color;
|
$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color;
|
||||||
$voice-record-icon-color: $tertiary-fg-color;
|
$voice-record-icon-color: $tertiary-fg-color;
|
||||||
$voice-playback-button-bg-color: $message-body-panel-icon-bg-color;
|
$voice-playback-button-bg-color: $message-body-panel-icon-bg-color;
|
||||||
|
|
|
@ -4,3 +4,4 @@
|
||||||
@import "_light.scss";
|
@import "_light.scss";
|
||||||
@import "_mods.scss";
|
@import "_mods.scss";
|
||||||
@import "../../../../res/css/_components.scss";
|
@import "../../../../res/css/_components.scss";
|
||||||
|
@import url("highlight.js/styles/atom-one-light.css");
|
||||||
|
|
|
@ -6,8 +6,8 @@ scripts/fetchdep.sh matrix-org matrix-js-sdk
|
||||||
|
|
||||||
pushd matrix-js-sdk
|
pushd matrix-js-sdk
|
||||||
yarn link
|
yarn link
|
||||||
yarn install $@
|
yarn install --pure-lockfile $@
|
||||||
popd
|
popd
|
||||||
|
|
||||||
yarn link matrix-js-sdk
|
yarn link matrix-js-sdk
|
||||||
yarn install $@
|
yarn install --pure-lockfile $@
|
||||||
|
|
|
@ -13,13 +13,13 @@
|
||||||
scripts/fetchdep.sh matrix-org matrix-js-sdk
|
scripts/fetchdep.sh matrix-org matrix-js-sdk
|
||||||
pushd matrix-js-sdk
|
pushd matrix-js-sdk
|
||||||
yarn link
|
yarn link
|
||||||
yarn install
|
yarn install --pure-lockfile
|
||||||
popd
|
popd
|
||||||
|
|
||||||
# Now set up the react-sdk
|
# Now set up the react-sdk
|
||||||
yarn link matrix-js-sdk
|
yarn link matrix-js-sdk
|
||||||
yarn link
|
yarn link
|
||||||
yarn install
|
yarn install --pure-lockfile
|
||||||
yarn reskindex
|
yarn reskindex
|
||||||
|
|
||||||
# Finally, set up element-web
|
# Finally, set up element-web
|
||||||
|
@ -27,6 +27,6 @@ scripts/fetchdep.sh vector-im element-web
|
||||||
pushd element-web
|
pushd element-web
|
||||||
yarn link matrix-js-sdk
|
yarn link matrix-js-sdk
|
||||||
yarn link matrix-react-sdk
|
yarn link matrix-react-sdk
|
||||||
yarn install
|
yarn install --pure-lockfile
|
||||||
yarn build:res
|
yarn build:res
|
||||||
popd
|
popd
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
#
|
|
||||||
# generates .eslintignore.errorfiles to list the files which have errors in,
|
|
||||||
# so that they can be ignored in future automated linting.
|
|
||||||
|
|
||||||
out=.eslintignore.errorfiles
|
|
||||||
|
|
||||||
cd `dirname $0`/..
|
|
||||||
|
|
||||||
echo "generating $out"
|
|
||||||
|
|
||||||
{
|
|
||||||
cat <<EOF
|
|
||||||
# autogenerated file: run scripts/generate-eslint-error-ignore-file to update.
|
|
||||||
|
|
||||||
EOF
|
|
||||||
|
|
||||||
./node_modules/.bin/eslint -f json src test |
|
|
||||||
jq -r '.[] | select((.errorCount + .warningCount) > 0) | .filePath' |
|
|
||||||
sed -e 's/.*matrix-react-sdk\///';
|
|
||||||
} > "$out"
|
|
||||||
# also append rules from eslintignore file
|
|
||||||
cat .eslintignore >> $out
|
|
5
src/@types/global.d.ts
vendored
5
src/@types/global.d.ts
vendored
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import "matrix-js-sdk/src/@types/global"; // load matrix-js-sdk's type extensions first
|
import "matrix-js-sdk/src/@types/global"; // load matrix-js-sdk's type extensions first
|
||||||
import * as ModernizrStatic from "modernizr";
|
import "@types/modernizr";
|
||||||
|
|
||||||
import ContentMessages from "../ContentMessages";
|
import ContentMessages from "../ContentMessages";
|
||||||
import { IMatrixClientPeg } from "../MatrixClientPeg";
|
import { IMatrixClientPeg } from "../MatrixClientPeg";
|
||||||
|
@ -46,10 +46,10 @@ import { VoiceRecordingStore } from "../stores/VoiceRecordingStore";
|
||||||
import PerformanceMonitor from "../performance";
|
import PerformanceMonitor from "../performance";
|
||||||
import UIStore from "../stores/UIStore";
|
import UIStore from "../stores/UIStore";
|
||||||
import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
|
import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
|
||||||
|
import { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
Modernizr: ModernizrStatic;
|
|
||||||
matrixChat: ReturnType<Renderer>;
|
matrixChat: ReturnType<Renderer>;
|
||||||
mxMatrixClientPeg: IMatrixClientPeg;
|
mxMatrixClientPeg: IMatrixClientPeg;
|
||||||
Olm: {
|
Olm: {
|
||||||
|
@ -87,6 +87,7 @@ declare global {
|
||||||
mxPerformanceEntryNames: any;
|
mxPerformanceEntryNames: any;
|
||||||
mxUIStore: UIStore;
|
mxUIStore: UIStore;
|
||||||
mxSetupEncryptionStore?: SetupEncryptionStore;
|
mxSetupEncryptionStore?: SetupEncryptionStore;
|
||||||
|
mxRoomScrollStateStore?: RoomScrollStateStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Document {
|
interface Document {
|
||||||
|
|
|
@ -390,6 +390,7 @@ export class Analytics {
|
||||||
{ expl: _td('Your device resolution'), value: resolution },
|
{ expl: _td('Your device resolution'), value: resolution },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// FIXME: Using an import will result in test failures
|
||||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||||
Modal.createTrackedDialog('Analytics Details', '', ErrorDialog, {
|
Modal.createTrackedDialog('Analytics Details', '', ErrorDialog, {
|
||||||
title: _t('Analytics'),
|
title: _t('Analytics'),
|
||||||
|
|
|
@ -77,6 +77,7 @@ export default class AsyncWrapper extends React.Component<IProps, IState> {
|
||||||
const Component = this.state.component;
|
const Component = this.state.component;
|
||||||
return <Component {...this.props} />;
|
return <Component {...this.props} />;
|
||||||
} else if (this.state.error) {
|
} else if (this.state.error) {
|
||||||
|
// FIXME: Using an import will result in test failures
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
return <BaseDialog onFinished={this.props.onFinished} title={_t("Error")}>
|
return <BaseDialog onFinished={this.props.onFinished} title={_t("Error")}>
|
||||||
|
|
|
@ -124,9 +124,9 @@ interface ThirdpartyLookupResponseFields {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ThirdpartyLookupResponse {
|
interface ThirdpartyLookupResponse {
|
||||||
userid: string,
|
userid: string;
|
||||||
protocol: string,
|
protocol: string;
|
||||||
fields: ThirdpartyLookupResponseFields,
|
fields: ThirdpartyLookupResponseFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlike 'CallType' in js-sdk, this one includes screen sharing
|
// Unlike 'CallType' in js-sdk, this one includes screen sharing
|
||||||
|
|
|
@ -17,9 +17,10 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import dis from './dispatcher/dispatcher';
|
import { encode } from "blurhash";
|
||||||
import { MatrixClientPeg } from './MatrixClientPeg';
|
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
|
import dis from './dispatcher/dispatcher';
|
||||||
import * as sdk from './index';
|
import * as sdk from './index';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
|
@ -47,6 +48,8 @@ const MAX_HEIGHT = 600;
|
||||||
// 5669 px (x-axis) , 5669 px (y-axis) , per metre
|
// 5669 px (x-axis) , 5669 px (y-axis) , per metre
|
||||||
const PHYS_HIDPI = [0x00, 0x00, 0x16, 0x25, 0x00, 0x00, 0x16, 0x25, 0x01];
|
const PHYS_HIDPI = [0x00, 0x00, 0x16, 0x25, 0x00, 0x00, 0x16, 0x25, 0x01];
|
||||||
|
|
||||||
|
export const BLURHASH_FIELD = "xyz.amorgan.blurhash"; // MSC2448
|
||||||
|
|
||||||
export class UploadCanceledError extends Error {}
|
export class UploadCanceledError extends Error {}
|
||||||
|
|
||||||
type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
|
type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
|
||||||
|
@ -77,6 +80,7 @@ interface IThumbnail {
|
||||||
};
|
};
|
||||||
w: number;
|
w: number;
|
||||||
h: number;
|
h: number;
|
||||||
|
[BLURHASH_FIELD]: string;
|
||||||
};
|
};
|
||||||
thumbnail: Blob;
|
thumbnail: Blob;
|
||||||
}
|
}
|
||||||
|
@ -124,7 +128,17 @@ function createThumbnail(
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
canvas.width = targetWidth;
|
canvas.width = targetWidth;
|
||||||
canvas.height = targetHeight;
|
canvas.height = targetHeight;
|
||||||
canvas.getContext("2d").drawImage(element, 0, 0, targetWidth, targetHeight);
|
const context = canvas.getContext("2d");
|
||||||
|
context.drawImage(element, 0, 0, targetWidth, targetHeight);
|
||||||
|
const imageData = context.getImageData(0, 0, targetWidth, targetHeight);
|
||||||
|
const blurhash = encode(
|
||||||
|
imageData.data,
|
||||||
|
imageData.width,
|
||||||
|
imageData.height,
|
||||||
|
// use 4 components on the longer dimension, if square then both
|
||||||
|
imageData.width >= imageData.height ? 4 : 3,
|
||||||
|
imageData.height >= imageData.width ? 4 : 3,
|
||||||
|
);
|
||||||
canvas.toBlob(function(thumbnail) {
|
canvas.toBlob(function(thumbnail) {
|
||||||
resolve({
|
resolve({
|
||||||
info: {
|
info: {
|
||||||
|
@ -136,8 +150,9 @@ function createThumbnail(
|
||||||
},
|
},
|
||||||
w: inputWidth,
|
w: inputWidth,
|
||||||
h: inputHeight,
|
h: inputHeight,
|
||||||
|
[BLURHASH_FIELD]: blurhash,
|
||||||
},
|
},
|
||||||
thumbnail: thumbnail,
|
thumbnail,
|
||||||
});
|
});
|
||||||
}, mimeType);
|
}, mimeType);
|
||||||
});
|
});
|
||||||
|
@ -220,7 +235,8 @@ function infoForImageFile(matrixClient, roomId, imageFile) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a file into a newly created video element.
|
* Load a file into a newly created video element and pull some strings
|
||||||
|
* in an attempt to guarantee the first frame will be showing.
|
||||||
*
|
*
|
||||||
* @param {File} videoFile The file to load in an video element.
|
* @param {File} videoFile The file to load in an video element.
|
||||||
* @return {Promise} A promise that resolves with the video image element.
|
* @return {Promise} A promise that resolves with the video image element.
|
||||||
|
@ -229,20 +245,25 @@ function loadVideoElement(videoFile): Promise<HTMLVideoElement> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Load the file into an html element
|
// Load the file into an html element
|
||||||
const video = document.createElement("video");
|
const video = document.createElement("video");
|
||||||
|
video.preload = "metadata";
|
||||||
|
video.playsInline = true;
|
||||||
|
video.muted = true;
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
|
||||||
reader.onload = function(ev) {
|
reader.onload = function(ev) {
|
||||||
video.src = ev.target.result as string;
|
|
||||||
|
|
||||||
// Once ready, returns its size
|
|
||||||
// Wait until we have enough data to thumbnail the first frame.
|
// Wait until we have enough data to thumbnail the first frame.
|
||||||
video.onloadeddata = function() {
|
video.onloadeddata = async function() {
|
||||||
resolve(video);
|
resolve(video);
|
||||||
|
video.pause();
|
||||||
};
|
};
|
||||||
video.onerror = function(e) {
|
video.onerror = function(e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
video.src = ev.target.result as string;
|
||||||
|
video.load();
|
||||||
|
video.play();
|
||||||
};
|
};
|
||||||
reader.onerror = function(e) {
|
reader.onerror = function(e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
|
@ -347,7 +368,7 @@ export function uploadFile(
|
||||||
});
|
});
|
||||||
(prom as IAbortablePromise<any>).abort = () => {
|
(prom as IAbortablePromise<any>).abort = () => {
|
||||||
canceled = true;
|
canceled = true;
|
||||||
if (uploadPromise) MatrixClientPeg.get().cancelUpload(uploadPromise);
|
if (uploadPromise) matrixClient.cancelUpload(uploadPromise);
|
||||||
};
|
};
|
||||||
return prom;
|
return prom;
|
||||||
} else {
|
} else {
|
||||||
|
@ -357,11 +378,11 @@ export function uploadFile(
|
||||||
const promise1 = basePromise.then(function(url) {
|
const promise1 = basePromise.then(function(url) {
|
||||||
if (canceled) throw new UploadCanceledError();
|
if (canceled) throw new UploadCanceledError();
|
||||||
// If the attachment isn't encrypted then include the URL directly.
|
// If the attachment isn't encrypted then include the URL directly.
|
||||||
return { "url": url };
|
return { url };
|
||||||
});
|
});
|
||||||
(promise1 as any).abort = () => {
|
(promise1 as any).abort = () => {
|
||||||
canceled = true;
|
canceled = true;
|
||||||
MatrixClientPeg.get().cancelUpload(basePromise);
|
matrixClient.cancelUpload(basePromise);
|
||||||
};
|
};
|
||||||
return promise1;
|
return promise1;
|
||||||
}
|
}
|
||||||
|
@ -373,7 +394,7 @@ export default class ContentMessages {
|
||||||
|
|
||||||
sendStickerContentToRoom(url: string, roomId: string, info: IImageInfo, text: string, matrixClient: MatrixClient) {
|
sendStickerContentToRoom(url: string, roomId: string, info: IImageInfo, text: string, matrixClient: MatrixClient) {
|
||||||
const startTime = CountlyAnalytics.getTimestamp();
|
const startTime = CountlyAnalytics.getTimestamp();
|
||||||
const prom = MatrixClientPeg.get().sendStickerMessage(roomId, url, info, text).catch((e) => {
|
const prom = matrixClient.sendStickerMessage(roomId, url, info, text).catch((e) => {
|
||||||
console.warn(`Failed to send content with URL ${url} to room ${roomId}`, e);
|
console.warn(`Failed to send content with URL ${url} to room ${roomId}`, e);
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
|
@ -397,6 +418,7 @@ export default class ContentMessages {
|
||||||
|
|
||||||
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
|
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
|
||||||
if (isQuoting) {
|
if (isQuoting) {
|
||||||
|
// FIXME: Using an import will result in Element crashing
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
const { finished } = Modal.createTrackedDialog<[boolean]>('Upload Reply Warning', '', QuestionDialog, {
|
const { finished } = Modal.createTrackedDialog<[boolean]>('Upload Reply Warning', '', QuestionDialog, {
|
||||||
title: _t('Replying With Files'),
|
title: _t('Replying With Files'),
|
||||||
|
@ -415,7 +437,7 @@ export default class ContentMessages {
|
||||||
|
|
||||||
if (!this.mediaConfig) { // hot-path optimization to not flash a spinner if we don't need to
|
if (!this.mediaConfig) { // hot-path optimization to not flash a spinner if we don't need to
|
||||||
const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
|
const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
|
||||||
await this.ensureMediaConfigFetched();
|
await this.ensureMediaConfigFetched(matrixClient);
|
||||||
modal.close();
|
modal.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,6 +453,7 @@ export default class ContentMessages {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tooBigFiles.length > 0) {
|
if (tooBigFiles.length > 0) {
|
||||||
|
// FIXME: Using an import will result in Element crashing
|
||||||
const UploadFailureDialog = sdk.getComponent("dialogs.UploadFailureDialog");
|
const UploadFailureDialog = sdk.getComponent("dialogs.UploadFailureDialog");
|
||||||
const { finished } = Modal.createTrackedDialog<[boolean]>('Upload Failure', '', UploadFailureDialog, {
|
const { finished } = Modal.createTrackedDialog<[boolean]>('Upload Failure', '', UploadFailureDialog, {
|
||||||
badFiles: tooBigFiles,
|
badFiles: tooBigFiles,
|
||||||
|
@ -441,7 +464,6 @@ export default class ContentMessages {
|
||||||
if (!shouldContinue) return;
|
if (!shouldContinue) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
|
|
||||||
let uploadAll = false;
|
let uploadAll = false;
|
||||||
// Promise to complete before sending next file into room, used for synchronisation of file-sending
|
// Promise to complete before sending next file into room, used for synchronisation of file-sending
|
||||||
// to match the order the files were specified in
|
// to match the order the files were specified in
|
||||||
|
@ -449,6 +471,8 @@ export default class ContentMessages {
|
||||||
for (let i = 0; i < okFiles.length; ++i) {
|
for (let i = 0; i < okFiles.length; ++i) {
|
||||||
const file = okFiles[i];
|
const file = okFiles[i];
|
||||||
if (!uploadAll) {
|
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',
|
const { finished } = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation',
|
||||||
'', UploadConfirmDialog, {
|
'', UploadConfirmDialog, {
|
||||||
file,
|
file,
|
||||||
|
@ -470,7 +494,7 @@ export default class ContentMessages {
|
||||||
return this.inprogress.filter(u => !u.canceled);
|
return this.inprogress.filter(u => !u.canceled);
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelUpload(promise: Promise<any>) {
|
cancelUpload(promise: Promise<any>, matrixClient: MatrixClient) {
|
||||||
let upload: IUpload;
|
let upload: IUpload;
|
||||||
for (let i = 0; i < this.inprogress.length; ++i) {
|
for (let i = 0; i < this.inprogress.length; ++i) {
|
||||||
if (this.inprogress[i].promise === promise) {
|
if (this.inprogress[i].promise === promise) {
|
||||||
|
@ -480,7 +504,7 @@ export default class ContentMessages {
|
||||||
}
|
}
|
||||||
if (upload) {
|
if (upload) {
|
||||||
upload.canceled = true;
|
upload.canceled = true;
|
||||||
MatrixClientPeg.get().cancelUpload(upload.promise);
|
matrixClient.cancelUpload(upload.promise);
|
||||||
dis.dispatch<UploadCanceledPayload>({ action: Action.UploadCanceled, upload });
|
dis.dispatch<UploadCanceledPayload>({ action: Action.UploadCanceled, upload });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -545,7 +569,7 @@ export default class ContentMessages {
|
||||||
dis.dispatch<UploadStartedPayload>({ action: Action.UploadStarted, upload });
|
dis.dispatch<UploadStartedPayload>({ action: Action.UploadStarted, upload });
|
||||||
|
|
||||||
// Focus the composer view
|
// Focus the composer view
|
||||||
dis.fire(Action.FocusComposer);
|
dis.fire(Action.FocusSendMessageComposer);
|
||||||
|
|
||||||
function onProgress(ev) {
|
function onProgress(ev) {
|
||||||
upload.total = ev.total;
|
upload.total = ev.total;
|
||||||
|
@ -584,6 +608,7 @@ export default class ContentMessages {
|
||||||
{ fileName: upload.fileName },
|
{ fileName: upload.fileName },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// FIXME: Using an import will result in Element crashing
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createTrackedDialog('Upload failed', '', ErrorDialog, {
|
Modal.createTrackedDialog('Upload failed', '', ErrorDialog, {
|
||||||
title: _t('Upload Failed'),
|
title: _t('Upload Failed'),
|
||||||
|
@ -621,11 +646,11 @@ export default class ContentMessages {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ensureMediaConfigFetched() {
|
private ensureMediaConfigFetched(matrixClient: MatrixClient) {
|
||||||
if (this.mediaConfig !== null) return;
|
if (this.mediaConfig !== null) return;
|
||||||
|
|
||||||
console.log("[Media Config] Fetching");
|
console.log("[Media Config] Fetching");
|
||||||
return MatrixClientPeg.get().getMediaConfig().then((config) => {
|
return matrixClient.getMediaConfig().then((config) => {
|
||||||
console.log("[Media Config] Fetched config:", config);
|
console.log("[Media Config] Fetched config:", config);
|
||||||
return config;
|
return config;
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
|
|
|
@ -15,12 +15,13 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||||
|
import { IContent } from "matrix-js-sdk/src/models/event";
|
||||||
|
import { sleep } from "matrix-js-sdk/src/utils";
|
||||||
|
|
||||||
import { getCurrentLanguage } from './languageHandler';
|
import { getCurrentLanguage } from './languageHandler';
|
||||||
import PlatformPeg from './PlatformPeg';
|
import PlatformPeg from './PlatformPeg';
|
||||||
import SdkConfig from './SdkConfig';
|
import SdkConfig from './SdkConfig';
|
||||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||||
import { sleep } from "./utils/promise";
|
|
||||||
import RoomViewStore from "./stores/RoomViewStore";
|
import RoomViewStore from "./stores/RoomViewStore";
|
||||||
import { Action } from "./dispatcher/actions";
|
import { Action } from "./dispatcher/actions";
|
||||||
|
|
||||||
|
@ -255,7 +256,7 @@ interface ICreateRoomEvent extends IEvent {
|
||||||
num_users: number;
|
num_users: number;
|
||||||
is_encrypted: boolean;
|
is_encrypted: boolean;
|
||||||
is_public: boolean;
|
is_public: boolean;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IJoinRoomEvent extends IEvent {
|
interface IJoinRoomEvent extends IEvent {
|
||||||
|
@ -868,7 +869,7 @@ export default class CountlyAnalytics {
|
||||||
roomId: string,
|
roomId: string,
|
||||||
isEdit: boolean,
|
isEdit: boolean,
|
||||||
isReply: boolean,
|
isReply: boolean,
|
||||||
content: {format?: string, msgtype: string},
|
content: IContent,
|
||||||
) {
|
) {
|
||||||
if (this.disabled) return;
|
if (this.disabled) return;
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
|
@ -160,7 +160,8 @@ export default class DeviceListener {
|
||||||
// which result in account data changes affecting checks below.
|
// which result in account data changes affecting checks below.
|
||||||
if (
|
if (
|
||||||
ev.getType().startsWith('m.secret_storage.') ||
|
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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -358,11 +358,11 @@ interface IOpts {
|
||||||
stripReplyFallback?: boolean;
|
stripReplyFallback?: boolean;
|
||||||
returnString?: boolean;
|
returnString?: boolean;
|
||||||
forComposerQuote?: boolean;
|
forComposerQuote?: boolean;
|
||||||
ref?: React.Ref<any>;
|
ref?: React.Ref<HTMLSpanElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IOptsReturnNode extends IOpts {
|
export interface IOptsReturnNode extends IOpts {
|
||||||
returnString: false;
|
returnString: false | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IOptsReturnString extends IOpts {
|
export interface IOptsReturnString extends IOpts {
|
||||||
|
@ -403,9 +403,14 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
|
||||||
try {
|
try {
|
||||||
if (highlights && highlights.length > 0) {
|
if (highlights && highlights.length > 0) {
|
||||||
const highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink);
|
const highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink);
|
||||||
const safeHighlights = highlights.map(function(highlight) {
|
const safeHighlights = highlights
|
||||||
return sanitizeHtml(highlight, sanitizeParams);
|
// sanitizeHtml can hang if an unclosed HTML tag is thrown at it
|
||||||
});
|
// A search for `<foo` will make the browser crash
|
||||||
|
// an alternative would be to escape HTML special characters
|
||||||
|
// but that would bring no additional benefit as the highlighter
|
||||||
|
// does not work with those special chars
|
||||||
|
.filter((highlight: string): boolean => !highlight.includes("<"))
|
||||||
|
.map((highlight: string): string => sanitizeHtml(highlight, sanitizeParams));
|
||||||
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure.
|
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure.
|
||||||
sanitizeParams.textFilter = function(safeText) {
|
sanitizeParams.textFilter = function(safeText) {
|
||||||
return highlighter.applyHighlights(safeText, safeHighlights).join('');
|
return highlighter.applyHighlights(safeText, safeHighlights).join('');
|
||||||
|
|
|
@ -33,7 +33,6 @@ import Presence from './Presence';
|
||||||
import dis from './dispatcher/dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import DMRoomMap from './utils/DMRoomMap';
|
import DMRoomMap from './utils/DMRoomMap';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import * as sdk from './index';
|
|
||||||
import ActiveWidgetStore from './stores/ActiveWidgetStore';
|
import ActiveWidgetStore from './stores/ActiveWidgetStore';
|
||||||
import PlatformPeg from "./PlatformPeg";
|
import PlatformPeg from "./PlatformPeg";
|
||||||
import { sendLoginRequest } from "./Login";
|
import { sendLoginRequest } from "./Login";
|
||||||
|
@ -52,6 +51,10 @@ import CallHandler from './CallHandler';
|
||||||
import LifecycleCustomisations from "./customisations/Lifecycle";
|
import LifecycleCustomisations from "./customisations/Lifecycle";
|
||||||
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
||||||
import { _t } from "./languageHandler";
|
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 HOMESERVER_URL_KEY = "mx_hs_url";
|
||||||
const ID_SERVER_URL_KEY = "mx_is_url";
|
const ID_SERVER_URL_KEY = "mx_is_url";
|
||||||
|
@ -238,8 +241,6 @@ export function handleInvalidStoreError(e: InvalidStoreError): Promise<void> {
|
||||||
return Promise.resolve().then(() => {
|
return Promise.resolve().then(() => {
|
||||||
const lazyLoadEnabled = e.value;
|
const lazyLoadEnabled = e.value;
|
||||||
if (lazyLoadEnabled) {
|
if (lazyLoadEnabled) {
|
||||||
const LazyLoadingResyncDialog =
|
|
||||||
sdk.getComponent("views.dialogs.LazyLoadingResyncDialog");
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
Modal.createDialog(LazyLoadingResyncDialog, {
|
Modal.createDialog(LazyLoadingResyncDialog, {
|
||||||
onFinished: resolve,
|
onFinished: resolve,
|
||||||
|
@ -250,8 +251,6 @@ export function handleInvalidStoreError(e: InvalidStoreError): Promise<void> {
|
||||||
// between LL/non-LL version on same host.
|
// between LL/non-LL version on same host.
|
||||||
// as disabling LL when previously enabled
|
// as disabling LL when previously enabled
|
||||||
// is a strong indicator of this (/develop & /app)
|
// is a strong indicator of this (/develop & /app)
|
||||||
const LazyLoadingDisabledDialog =
|
|
||||||
sdk.getComponent("views.dialogs.LazyLoadingDisabledDialog");
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
Modal.createDialog(LazyLoadingDisabledDialog, {
|
Modal.createDialog(LazyLoadingDisabledDialog, {
|
||||||
onFinished: resolve,
|
onFinished: resolve,
|
||||||
|
@ -451,9 +450,6 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
|
||||||
async function handleLoadSessionFailure(e: Error): Promise<boolean> {
|
async function handleLoadSessionFailure(e: Error): Promise<boolean> {
|
||||||
console.error("Unable to load session", e);
|
console.error("Unable to load session", e);
|
||||||
|
|
||||||
const SessionRestoreErrorDialog =
|
|
||||||
sdk.getComponent('views.dialogs.SessionRestoreErrorDialog');
|
|
||||||
|
|
||||||
const modal = Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, {
|
const modal = Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, {
|
||||||
error: e.message,
|
error: e.message,
|
||||||
});
|
});
|
||||||
|
@ -612,7 +608,6 @@ async function doSetLoggedIn(
|
||||||
}
|
}
|
||||||
|
|
||||||
function showStorageEvictedDialog(): Promise<boolean> {
|
function showStorageEvictedDialog(): Promise<boolean> {
|
||||||
const StorageEvictedDialog = sdk.getComponent('views.dialogs.StorageEvictedDialog');
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
Modal.createTrackedDialog('Storage evicted', '', StorageEvictedDialog, {
|
Modal.createTrackedDialog('Storage evicted', '', StorageEvictedDialog, {
|
||||||
onFinished: resolve,
|
onFinished: resolve,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -22,9 +23,17 @@ const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u'];
|
||||||
// These types of node are definitely text
|
// These types of node are definitely text
|
||||||
const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document'];
|
const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document'];
|
||||||
|
|
||||||
function is_allowed_html_tag(node) {
|
// As far as @types/commonmark is concerned, these are not public, so add them
|
||||||
|
interface CommonmarkHtmlRendererInternal extends commonmark.HtmlRenderer {
|
||||||
|
paragraph: (node: commonmark.Node, entering: boolean) => void;
|
||||||
|
link: (node: commonmark.Node, entering: boolean) => void;
|
||||||
|
html_inline: (node: commonmark.Node) => void; // eslint-disable-line camelcase
|
||||||
|
html_block: (node: commonmark.Node) => void; // eslint-disable-line camelcase
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAllowedHtmlTag(node: commonmark.Node): boolean {
|
||||||
if (node.literal != null &&
|
if (node.literal != null &&
|
||||||
node.literal.match('^<((div|span) data-mx-maths="[^"]*"|\/(div|span))>$') != null) {
|
node.literal.match('^<((div|span) data-mx-maths="[^"]*"|/(div|span))>$') != null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,21 +48,12 @@ function is_allowed_html_tag(node) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function html_if_tag_allowed(node) {
|
|
||||||
if (is_allowed_html_tag(node)) {
|
|
||||||
this.lit(node.literal);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.lit(escape(node.literal));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns true if the parse output containing the node
|
* Returns true if the parse output containing the node
|
||||||
* comprises multiple block level elements (ie. lines),
|
* comprises multiple block level elements (ie. lines),
|
||||||
* or false if it is only a single line.
|
* or false if it is only a single line.
|
||||||
*/
|
*/
|
||||||
function is_multi_line(node) {
|
function isMultiLine(node: commonmark.Node): boolean {
|
||||||
let par = node;
|
let par = node;
|
||||||
while (par.parent) {
|
while (par.parent) {
|
||||||
par = par.parent;
|
par = par.parent;
|
||||||
|
@ -67,6 +67,9 @@ function is_multi_line(node) {
|
||||||
* it's plain text.
|
* it's plain text.
|
||||||
*/
|
*/
|
||||||
export default class Markdown {
|
export default class Markdown {
|
||||||
|
private input: string;
|
||||||
|
private parsed: commonmark.Node;
|
||||||
|
|
||||||
constructor(input) {
|
constructor(input) {
|
||||||
this.input = input;
|
this.input = input;
|
||||||
|
|
||||||
|
@ -74,7 +77,7 @@ export default class Markdown {
|
||||||
this.parsed = parser.parse(this.input);
|
this.parsed = parser.parse(this.input);
|
||||||
}
|
}
|
||||||
|
|
||||||
isPlainText() {
|
isPlainText(): boolean {
|
||||||
const walker = this.parsed.walker();
|
const walker = this.parsed.walker();
|
||||||
|
|
||||||
let ev;
|
let ev;
|
||||||
|
@ -87,7 +90,7 @@ export default class Markdown {
|
||||||
// if it's an allowed html tag, we need to render it and therefore
|
// if it's an allowed html tag, we need to render it and therefore
|
||||||
// we will need to use HTML. If it's not allowed, it's not HTML since
|
// we will need to use HTML. If it's not allowed, it's not HTML since
|
||||||
// we'll just be treating it as text.
|
// we'll just be treating it as text.
|
||||||
if (is_allowed_html_tag(node)) {
|
if (isAllowedHtmlTag(node)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -97,7 +100,7 @@ export default class Markdown {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
toHTML({ externalLinks = false } = {}) {
|
toHTML({ externalLinks = false } = {}): string {
|
||||||
const renderer = new commonmark.HtmlRenderer({
|
const renderer = new commonmark.HtmlRenderer({
|
||||||
safe: false,
|
safe: false,
|
||||||
|
|
||||||
|
@ -107,7 +110,7 @@ export default class Markdown {
|
||||||
// block quote ends up all on one line
|
// block quote ends up all on one line
|
||||||
// (https://github.com/vector-im/element-web/issues/3154)
|
// (https://github.com/vector-im/element-web/issues/3154)
|
||||||
softbreak: '<br />',
|
softbreak: '<br />',
|
||||||
});
|
}) as CommonmarkHtmlRendererInternal;
|
||||||
|
|
||||||
// Trying to strip out the wrapping <p/> causes a lot more complication
|
// Trying to strip out the wrapping <p/> causes a lot more complication
|
||||||
// than it's worth, i think. For instance, this code will go and strip
|
// than it's worth, i think. For instance, this code will go and strip
|
||||||
|
@ -118,16 +121,16 @@ export default class Markdown {
|
||||||
//
|
//
|
||||||
// Let's try sending with <p/>s anyway for now, though.
|
// Let's try sending with <p/>s anyway for now, though.
|
||||||
|
|
||||||
const real_paragraph = renderer.paragraph;
|
const realParagraph = renderer.paragraph;
|
||||||
|
|
||||||
renderer.paragraph = function(node, entering) {
|
renderer.paragraph = function(node: commonmark.Node, entering: boolean) {
|
||||||
// If there is only one top level node, just return the
|
// If there is only one top level node, just return the
|
||||||
// bare text: it's a single line of text and so should be
|
// bare text: it's a single line of text and so should be
|
||||||
// 'inline', rather than unnecessarily wrapped in its own
|
// 'inline', rather than unnecessarily wrapped in its own
|
||||||
// p tag. If, however, we have multiple nodes, each gets
|
// p tag. If, however, we have multiple nodes, each gets
|
||||||
// its own p tag to keep them as separate paragraphs.
|
// its own p tag to keep them as separate paragraphs.
|
||||||
if (is_multi_line(node)) {
|
if (isMultiLine(node)) {
|
||||||
real_paragraph.call(this, node, entering);
|
realParagraph.call(this, node, entering);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -150,16 +153,23 @@ export default class Markdown {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
renderer.html_inline = html_if_tag_allowed;
|
renderer.html_inline = function(node: commonmark.Node) {
|
||||||
|
if (isAllowedHtmlTag(node)) {
|
||||||
|
this.lit(node.literal);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.lit(escape(node.literal));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
renderer.html_block = function(node) {
|
renderer.html_block = function(node: commonmark.Node) {
|
||||||
/*
|
/*
|
||||||
// as with `paragraph`, we only insert line breaks
|
// as with `paragraph`, we only insert line breaks
|
||||||
// if there are multiple lines in the markdown.
|
// if there are multiple lines in the markdown.
|
||||||
const isMultiLine = is_multi_line(node);
|
const isMultiLine = is_multi_line(node);
|
||||||
if (isMultiLine) this.cr();
|
if (isMultiLine) this.cr();
|
||||||
*/
|
*/
|
||||||
html_if_tag_allowed.call(this, node);
|
renderer.html_inline(node);
|
||||||
/*
|
/*
|
||||||
if (isMultiLine) this.cr();
|
if (isMultiLine) this.cr();
|
||||||
*/
|
*/
|
||||||
|
@ -177,23 +187,22 @@ export default class Markdown {
|
||||||
* N.B. this does **NOT** render arbitrary MD to plain text - only MD
|
* N.B. this does **NOT** render arbitrary MD to plain text - only MD
|
||||||
* which has no formatting. Otherwise it emits HTML(!).
|
* which has no formatting. Otherwise it emits HTML(!).
|
||||||
*/
|
*/
|
||||||
toPlaintext() {
|
toPlaintext(): string {
|
||||||
const renderer = new commonmark.HtmlRenderer({safe: false});
|
const renderer = new commonmark.HtmlRenderer({ safe: false }) as CommonmarkHtmlRendererInternal;
|
||||||
const real_paragraph = renderer.paragraph;
|
|
||||||
|
|
||||||
renderer.paragraph = function(node, entering) {
|
renderer.paragraph = function(node: commonmark.Node, entering: boolean) {
|
||||||
// as with toHTML, only append lines to paragraphs if there are
|
// as with toHTML, only append lines to paragraphs if there are
|
||||||
// multiple paragraphs
|
// multiple paragraphs
|
||||||
if (is_multi_line(node)) {
|
if (isMultiLine(node)) {
|
||||||
if (!entering && node.next) {
|
if (!entering && node.next) {
|
||||||
this.lit('\n\n');
|
this.lit('\n\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
renderer.html_block = function(node) {
|
renderer.html_block = function(node: commonmark.Node) {
|
||||||
this.lit(node.literal);
|
this.lit(node.literal);
|
||||||
if (is_multi_line(node) && node.next) this.lit('\n\n');
|
if (isMultiLine(node) && node.next) this.lit('\n\n');
|
||||||
};
|
};
|
||||||
|
|
||||||
return renderer.render(this.parsed);
|
return renderer.render(this.parsed);
|
|
@ -219,6 +219,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e && e.name === 'InvalidCryptoStoreError') {
|
if (e && e.name === 'InvalidCryptoStoreError') {
|
||||||
// The js-sdk found a crypto DB too new for it to use
|
// The js-sdk found a crypto DB too new for it to use
|
||||||
|
// FIXME: Using an import will result in test failures
|
||||||
const CryptoStoreTooNewDialog =
|
const CryptoStoreTooNewDialog =
|
||||||
sdk.getComponent("views.dialogs.CryptoStoreTooNewDialog");
|
sdk.getComponent("views.dialogs.CryptoStoreTooNewDialog");
|
||||||
Modal.createDialog(CryptoStoreTooNewDialog);
|
Modal.createDialog(CryptoStoreTooNewDialog);
|
||||||
|
|
|
@ -20,12 +20,15 @@ import { SettingLevel } from "./settings/SettingLevel";
|
||||||
import { setMatrixCallAudioInput, setMatrixCallVideoInput } from "matrix-js-sdk/src/matrix";
|
import { setMatrixCallAudioInput, setMatrixCallVideoInput } from "matrix-js-sdk/src/matrix";
|
||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
|
|
||||||
interface IMediaDevices {
|
// XXX: MediaDeviceKind is a union type, so we make our own enum
|
||||||
audioOutput: Array<MediaDeviceInfo>;
|
export enum MediaDeviceKindEnum {
|
||||||
audioInput: Array<MediaDeviceInfo>;
|
AudioOutput = "audiooutput",
|
||||||
videoInput: Array<MediaDeviceInfo>;
|
AudioInput = "audioinput",
|
||||||
|
VideoInput = "videoinput",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IMediaDevices = Record<MediaDeviceKindEnum, Array<MediaDeviceInfo>>;
|
||||||
|
|
||||||
export enum MediaDeviceHandlerEvent {
|
export enum MediaDeviceHandlerEvent {
|
||||||
AudioOutputChanged = "audio_output_changed",
|
AudioOutputChanged = "audio_output_changed",
|
||||||
}
|
}
|
||||||
|
@ -51,20 +54,14 @@ export default class MediaDeviceHandler extends EventEmitter {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||||
|
const output = {
|
||||||
|
[MediaDeviceKindEnum.AudioOutput]: [],
|
||||||
|
[MediaDeviceKindEnum.AudioInput]: [],
|
||||||
|
[MediaDeviceKindEnum.VideoInput]: [],
|
||||||
|
};
|
||||||
|
|
||||||
const audioOutput = [];
|
devices.forEach((device) => output[device.kind].push(device));
|
||||||
const audioInput = [];
|
return output;
|
||||||
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 };
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Unable to refresh WebRTC Devices: ', error);
|
console.warn('Unable to refresh WebRTC Devices: ', error);
|
||||||
}
|
}
|
||||||
|
@ -106,6 +103,14 @@ export default class MediaDeviceHandler extends EventEmitter {
|
||||||
setMatrixCallVideoInput(deviceId);
|
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 {
|
public static getAudioOutput(): string {
|
||||||
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audiooutput");
|
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audiooutput");
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,10 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { defer } from "matrix-js-sdk/src/utils";
|
||||||
|
|
||||||
import Analytics from './Analytics';
|
import Analytics from './Analytics';
|
||||||
import dis from './dispatcher/dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import { defer } from './utils/promise';
|
|
||||||
import AsyncWrapper from './AsyncWrapper';
|
import AsyncWrapper from './AsyncWrapper';
|
||||||
|
|
||||||
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||||
|
|
|
@ -27,7 +27,6 @@ import * as TextForEvent from './TextForEvent';
|
||||||
import Analytics from './Analytics';
|
import Analytics from './Analytics';
|
||||||
import * as Avatar from './Avatar';
|
import * as Avatar from './Avatar';
|
||||||
import dis from './dispatcher/dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import * as sdk from './index';
|
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
|
@ -37,6 +36,7 @@ import { isPushNotifyDisabled } from "./settings/controllers/NotificationControl
|
||||||
import RoomViewStore from "./stores/RoomViewStore";
|
import RoomViewStore from "./stores/RoomViewStore";
|
||||||
import UserActivity from "./UserActivity";
|
import UserActivity from "./UserActivity";
|
||||||
import { mediaFromMxc } from "./customisations/Media";
|
import { mediaFromMxc } from "./customisations/Media";
|
||||||
|
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dispatches:
|
* Dispatches:
|
||||||
|
@ -240,7 +240,6 @@ export const Notifier = {
|
||||||
? _t('%(brand)s does not have permission to send you notifications - ' +
|
? _t('%(brand)s does not have permission to send you notifications - ' +
|
||||||
'please check your browser settings', { brand })
|
'please check your browser settings', { brand })
|
||||||
: _t('%(brand)s was not given permission to send notifications - please try again', { 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, {
|
Modal.createTrackedDialog('Unable to enable Notifications', result, ErrorDialog, {
|
||||||
title: _t('Unable to enable Notifications'),
|
title: _t('Unable to enable Notifications'),
|
||||||
description,
|
description,
|
||||||
|
|
|
@ -22,13 +22,13 @@ import { User } from "matrix-js-sdk/src/models/user";
|
||||||
import { MatrixClientPeg } from './MatrixClientPeg';
|
import { MatrixClientPeg } from './MatrixClientPeg';
|
||||||
import MultiInviter, { CompletionStates } from './utils/MultiInviter';
|
import MultiInviter, { CompletionStates } from './utils/MultiInviter';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import * as sdk from './';
|
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import InviteDialog, { KIND_DM, KIND_INVITE, Member } from "./components/views/dialogs/InviteDialog";
|
import InviteDialog, { KIND_DM, KIND_INVITE, Member } from "./components/views/dialogs/InviteDialog";
|
||||||
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
|
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
|
||||||
import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
|
import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
|
||||||
import BaseAvatar from "./components/views/avatars/BaseAvatar";
|
import BaseAvatar from "./components/views/avatars/BaseAvatar";
|
||||||
import { mediaFromMxc } from "./customisations/Media";
|
import { mediaFromMxc } from "./customisations/Media";
|
||||||
|
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
||||||
|
|
||||||
export interface IInviteResult {
|
export interface IInviteResult {
|
||||||
states: CompletionStates;
|
states: CompletionStates;
|
||||||
|
@ -51,7 +51,6 @@ export function inviteMultipleToRoom(roomId: string, addresses: string[]): Promi
|
||||||
|
|
||||||
export function showStartChatInviteDialog(initialText = ""): void {
|
export function showStartChatInviteDialog(initialText = ""): void {
|
||||||
// This dialog handles the room creation internally - we don't need to worry about it.
|
// This dialog handles the room creation internally - we don't need to worry about it.
|
||||||
const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
|
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
'Start DM', '', InviteDialog, { kind: KIND_DM, initialText },
|
'Start DM', '', InviteDialog, { kind: KIND_DM, initialText },
|
||||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
/*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);
|
showAnyInviteErrors(result.states, room, result.inviter);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, {
|
||||||
title: _t("Failed to invite"),
|
title: _t("Failed to invite"),
|
||||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
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
|
// 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
|
// user. This usually means that no other users were attempted, making it
|
||||||
// pointless for us to list who failed exactly.
|
// pointless for us to list who failed exactly.
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Failed to invite users to the room', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to invite users to the room', '', ErrorDialog, {
|
||||||
title: _t("Failed to invite users to the room:", { roomName: room.name }),
|
title: _t("Failed to invite users to the room:", { roomName: room.name }),
|
||||||
description: inviter.getErrorText(failedUsers[0]),
|
description: inviter.getErrorText(failedUsers[0]),
|
||||||
|
@ -178,7 +175,6 @@ export function showAnyInviteErrors(
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog("Some invites could not be sent", "", ErrorDialog, {
|
Modal.createTrackedDialog("Some invites could not be sent", "", ErrorDialog, {
|
||||||
title: _t("Some invites couldn't be sent"),
|
title: _t("Some invites couldn't be sent"),
|
||||||
description,
|
description,
|
||||||
|
|
|
@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
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 { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import * as sdk from './index';
|
import * as sdk from './index';
|
||||||
|
@ -42,8 +43,8 @@ let secretStorageBeingAccessed = false;
|
||||||
let nonInteractive = false;
|
let nonInteractive = false;
|
||||||
|
|
||||||
let dehydrationCache: {
|
let dehydrationCache: {
|
||||||
key?: Uint8Array,
|
key?: Uint8Array;
|
||||||
keyInfo?: ISecretStorageKeyInfo,
|
keyInfo?: ISecretStorageKeyInfo;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
function isCachingAllowed(): boolean {
|
function isCachingAllowed(): boolean {
|
||||||
|
@ -354,6 +355,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
|
||||||
throw new Error("Secret storage creation canceled");
|
throw new Error("Secret storage creation canceled");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// FIXME: Using an import will result in test failures
|
||||||
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
||||||
await cli.bootstrapCrossSigning({
|
await cli.bootstrapCrossSigning({
|
||||||
authUploadDeviceSigningKeys: async (makeRequest) => {
|
authUploadDeviceSigningKeys: async (makeRequest) => {
|
||||||
|
|
|
@ -23,7 +23,6 @@ import { User } from "matrix-js-sdk/src/models/user";
|
||||||
import * as ContentHelpers from 'matrix-js-sdk/src/content-helpers';
|
import * as ContentHelpers from 'matrix-js-sdk/src/content-helpers';
|
||||||
import { MatrixClientPeg } from './MatrixClientPeg';
|
import { MatrixClientPeg } from './MatrixClientPeg';
|
||||||
import dis from './dispatcher/dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import * as sdk from './index';
|
|
||||||
import { _t, _td } from './languageHandler';
|
import { _t, _td } from './languageHandler';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import MultiInviter from './utils/MultiInviter';
|
import MultiInviter from './utils/MultiInviter';
|
||||||
|
@ -50,6 +49,12 @@ import { UIFeature } from "./settings/UIFeature";
|
||||||
import { CHAT_EFFECTS } from "./effects";
|
import { CHAT_EFFECTS } from "./effects";
|
||||||
import CallHandler from "./CallHandler";
|
import CallHandler from "./CallHandler";
|
||||||
import { guessAndSetDMRoom } from "./Rooms";
|
import { guessAndSetDMRoom } from "./Rooms";
|
||||||
|
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
|
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
|
||||||
interface HTMLInputEvent extends Event {
|
interface HTMLInputEvent extends Event {
|
||||||
|
@ -63,7 +68,6 @@ const singleMxcUpload = async (): Promise<any> => {
|
||||||
fileSelector.onchange = (ev: HTMLInputEvent) => {
|
fileSelector.onchange = (ev: HTMLInputEvent) => {
|
||||||
const file = ev.target.files[0];
|
const file = ev.target.files[0];
|
||||||
|
|
||||||
const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
|
|
||||||
Modal.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, {
|
Modal.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, {
|
||||||
file,
|
file,
|
||||||
onFinished: (shouldContinue) => {
|
onFinished: (shouldContinue) => {
|
||||||
|
@ -246,7 +250,6 @@ export const Commands = [
|
||||||
args: '<query>',
|
args: '<query>',
|
||||||
description: _td('Searches DuckDuckGo for results'),
|
description: _td('Searches DuckDuckGo for results'),
|
||||||
runFn: function() {
|
runFn: function() {
|
||||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
|
||||||
// TODO Don't explain this away, actually show a search UI here.
|
// TODO Don't explain this away, actually show a search UI here.
|
||||||
Modal.createTrackedDialog('Slash Commands', '/ddg is not a command', ErrorDialog, {
|
Modal.createTrackedDialog('Slash Commands', '/ddg is not a command', ErrorDialog, {
|
||||||
title: _t('/ddg is not a command'),
|
title: _t('/ddg is not a command'),
|
||||||
|
@ -269,8 +272,6 @@ export const Commands = [
|
||||||
return reject(_t("You do not have the required permissions to use this command."));
|
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',
|
const { finished } = Modal.createTrackedDialog('Slash Commands', 'upgrade room confirmation',
|
||||||
RoomUpgradeWarningDialog, { roomId: roomId, targetVersion: args }, /*className=*/null,
|
RoomUpgradeWarningDialog, { roomId: roomId, targetVersion: args }, /*className=*/null,
|
||||||
/*isPriority=*/false, /*isStatic=*/true);
|
/*isPriority=*/false, /*isStatic=*/true);
|
||||||
|
@ -314,7 +315,6 @@ export const Commands = [
|
||||||
|
|
||||||
if (checkForUpgradeFn) cli.removeListener('Room', checkForUpgradeFn);
|
if (checkForUpgradeFn) cli.removeListener('Room', checkForUpgradeFn);
|
||||||
|
|
||||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
|
||||||
Modal.createTrackedDialog('Slash Commands', 'room upgrade error', ErrorDialog, {
|
Modal.createTrackedDialog('Slash Commands', 'room upgrade error', ErrorDialog, {
|
||||||
title: _t('Error upgrading room'),
|
title: _t('Error upgrading room'),
|
||||||
description: _t(
|
description: _t(
|
||||||
|
@ -434,7 +434,6 @@ export const Commands = [
|
||||||
const topic = topicEvents && topicEvents.getContent().topic;
|
const topic = topicEvents && topicEvents.getContent().topic;
|
||||||
const topicHtml = topic ? linkifyAndSanitizeHtml(topic) : _t('This room has no topic.');
|
const topicHtml = topic ? linkifyAndSanitizeHtml(topic) : _t('This room has no topic.');
|
||||||
|
|
||||||
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
|
||||||
Modal.createTrackedDialog('Slash Commands', 'Topic', InfoDialog, {
|
Modal.createTrackedDialog('Slash Commands', 'Topic', InfoDialog, {
|
||||||
title: room.name,
|
title: room.name,
|
||||||
description: <div dangerouslySetInnerHTML={{ __html: topicHtml }} />,
|
description: <div dangerouslySetInnerHTML={{ __html: topicHtml }} />,
|
||||||
|
@ -737,7 +736,6 @@ export const Commands = [
|
||||||
ignoredUsers.push(userId); // de-duped internally in the js-sdk
|
ignoredUsers.push(userId); // de-duped internally in the js-sdk
|
||||||
return success(
|
return success(
|
||||||
cli.setIgnoredUsers(ignoredUsers).then(() => {
|
cli.setIgnoredUsers(ignoredUsers).then(() => {
|
||||||
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
|
||||||
Modal.createTrackedDialog('Slash Commands', 'User ignored', InfoDialog, {
|
Modal.createTrackedDialog('Slash Commands', 'User ignored', InfoDialog, {
|
||||||
title: _t('Ignored user'),
|
title: _t('Ignored user'),
|
||||||
description: <div>
|
description: <div>
|
||||||
|
@ -768,7 +766,6 @@ export const Commands = [
|
||||||
if (index !== -1) ignoredUsers.splice(index, 1);
|
if (index !== -1) ignoredUsers.splice(index, 1);
|
||||||
return success(
|
return success(
|
||||||
cli.setIgnoredUsers(ignoredUsers).then(() => {
|
cli.setIgnoredUsers(ignoredUsers).then(() => {
|
||||||
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
|
||||||
Modal.createTrackedDialog('Slash Commands', 'User unignored', InfoDialog, {
|
Modal.createTrackedDialog('Slash Commands', 'User unignored', InfoDialog, {
|
||||||
title: _t('Unignored user'),
|
title: _t('Unignored user'),
|
||||||
description: <div>
|
description: <div>
|
||||||
|
@ -838,7 +835,6 @@ export const Commands = [
|
||||||
command: 'devtools',
|
command: 'devtools',
|
||||||
description: _td('Opens the Developer Tools dialog'),
|
description: _td('Opens the Developer Tools dialog'),
|
||||||
runFn: function(roomId) {
|
runFn: function(roomId) {
|
||||||
const DevtoolsDialog = sdk.getComponent('dialogs.DevtoolsDialog');
|
|
||||||
Modal.createDialog(DevtoolsDialog, { roomId });
|
Modal.createDialog(DevtoolsDialog, { roomId });
|
||||||
return success();
|
return success();
|
||||||
},
|
},
|
||||||
|
@ -943,7 +939,6 @@ export const Commands = [
|
||||||
await cli.setDeviceVerified(userId, deviceId, true);
|
await cli.setDeviceVerified(userId, deviceId, true);
|
||||||
|
|
||||||
// Tell the user we verified everything
|
// Tell the user we verified everything
|
||||||
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
|
||||||
Modal.createTrackedDialog('Slash Commands', 'Verified key', InfoDialog, {
|
Modal.createTrackedDialog('Slash Commands', 'Verified key', InfoDialog, {
|
||||||
title: _t('Verified key'),
|
title: _t('Verified key'),
|
||||||
description: <div>
|
description: <div>
|
||||||
|
@ -1000,8 +995,6 @@ export const Commands = [
|
||||||
command: "help",
|
command: "help",
|
||||||
description: _td("Displays list of commands with usages and descriptions"),
|
description: _td("Displays list of commands with usages and descriptions"),
|
||||||
runFn: function() {
|
runFn: function() {
|
||||||
const SlashCommandHelpDialog = sdk.getComponent('dialogs.SlashCommandHelpDialog');
|
|
||||||
|
|
||||||
Modal.createTrackedDialog('Slash Commands', 'Help', SlashCommandHelpDialog);
|
Modal.createTrackedDialog('Slash Commands', 'Help', SlashCommandHelpDialog);
|
||||||
return success();
|
return success();
|
||||||
},
|
},
|
||||||
|
@ -1181,7 +1174,7 @@ export const Commands = [
|
||||||
];
|
];
|
||||||
|
|
||||||
// build a map from names and aliases to the Command objects.
|
// build a map from names and aliases to the Command objects.
|
||||||
export const CommandMap = new Map();
|
export const CommandMap = new Map<string, Command>();
|
||||||
Commands.forEach(cmd => {
|
Commands.forEach(cmd => {
|
||||||
CommandMap.set(cmd.command, cmd);
|
CommandMap.set(cmd.command, cmd);
|
||||||
cmd.aliases.forEach(alias => {
|
cmd.aliases.forEach(alias => {
|
||||||
|
@ -1189,15 +1182,15 @@ Commands.forEach(cmd => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function parseCommandString(input: string) {
|
export function parseCommandString(input: string): { cmd?: string, args?: string } {
|
||||||
// trim any trailing whitespace, as it can confuse the parser for
|
// trim any trailing whitespace, as it can confuse the parser for
|
||||||
// IRC-style commands
|
// IRC-style commands
|
||||||
input = input.replace(/\s+$/, '');
|
input = input.replace(/\s+$/, '');
|
||||||
if (input[0] !== '/') return {}; // not a command
|
if (input[0] !== '/') return {}; // not a command
|
||||||
|
|
||||||
const bits = input.match(/^(\S+?)(?:[ \n]+((.|\n)*))?$/);
|
const bits = input.match(/^(\S+?)(?:[ \n]+((.|\n)*))?$/);
|
||||||
let cmd;
|
let cmd: string;
|
||||||
let args;
|
let args: string;
|
||||||
if (bits) {
|
if (bits) {
|
||||||
cmd = bits[1].substring(1).toLowerCase();
|
cmd = bits[1].substring(1).toLowerCase();
|
||||||
args = bits[2];
|
args = bits[2];
|
||||||
|
@ -1208,6 +1201,11 @@ export function parseCommandString(input: string) {
|
||||||
return { cmd, args };
|
return { cmd, args };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ICmd {
|
||||||
|
cmd?: Command;
|
||||||
|
args?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process the given text for /commands and return a bound method to perform them.
|
* Process the given text for /commands and return a bound method to perform them.
|
||||||
* @param {string} roomId The room in which the command was performed.
|
* @param {string} roomId The room in which the command was performed.
|
||||||
|
@ -1216,7 +1214,7 @@ export function parseCommandString(input: string) {
|
||||||
* processing the command, or 'promise' if a request was sent out.
|
* processing the command, or 'promise' if a request was sent out.
|
||||||
* Returns null if the input didn't match a command.
|
* Returns null if the input didn't match a command.
|
||||||
*/
|
*/
|
||||||
export function getCommand(input: string) {
|
export function getCommand(input: string): ICmd {
|
||||||
const { cmd, args } = parseCommandString(input);
|
const { cmd, args } = parseCommandString(input);
|
||||||
|
|
||||||
if (CommandMap.has(cmd) && CommandMap.get(cmd).isEnabled()) {
|
if (CommandMap.has(cmd) && CommandMap.get(cmd).isEnabled()) {
|
||||||
|
|
14
src/Terms.ts
14
src/Terms.ts
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { SERVICE_TYPES } from 'matrix-js-sdk/src/service-types';
|
||||||
|
|
||||||
import { MatrixClientPeg } from './MatrixClientPeg';
|
import { MatrixClientPeg } from './MatrixClientPeg';
|
||||||
import * as sdk from '.';
|
import * as sdk from '.';
|
||||||
|
@ -32,7 +33,7 @@ export class Service {
|
||||||
* @param {string} baseUrl The Base URL of the service (ie. before '/_matrix')
|
* @param {string} baseUrl The Base URL of the service (ie. before '/_matrix')
|
||||||
* @param {string} accessToken The user's access token for the service
|
* @param {string} accessToken The user's access token for the service
|
||||||
*/
|
*/
|
||||||
constructor(public serviceType: string, public baseUrl: string, public accessToken: string) {
|
constructor(public serviceType: SERVICE_TYPES, public baseUrl: string, public accessToken: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,13 +49,13 @@ export interface Policy {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Policies = {
|
export type Policies = {
|
||||||
[policy: string]: Policy,
|
[policy: string]: Policy;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TermsInteractionCallback = (
|
export type TermsInteractionCallback = (
|
||||||
policiesAndServicePairs: {
|
policiesAndServicePairs: {
|
||||||
service: Service,
|
service: Service;
|
||||||
policies: Policies,
|
policies: Policies;
|
||||||
}[],
|
}[],
|
||||||
agreedUrls: string[],
|
agreedUrls: string[],
|
||||||
extraClassNames?: string,
|
extraClassNames?: string,
|
||||||
|
@ -180,14 +181,15 @@ export async function startTermsFlow(
|
||||||
|
|
||||||
export function dialogTermsInteractionCallback(
|
export function dialogTermsInteractionCallback(
|
||||||
policiesAndServicePairs: {
|
policiesAndServicePairs: {
|
||||||
service: Service,
|
service: Service;
|
||||||
policies: { [policy: string]: Policy },
|
policies: { [policy: string]: Policy };
|
||||||
}[],
|
}[],
|
||||||
agreedUrls: string[],
|
agreedUrls: string[],
|
||||||
extraClassNames?: string,
|
extraClassNames?: string,
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
console.log("Terms that need agreement", policiesAndServicePairs);
|
console.log("Terms that need agreement", policiesAndServicePairs);
|
||||||
|
// FIXME: Using an import will result in test failures
|
||||||
const TermsDialog = sdk.getComponent("views.dialogs.TermsDialog");
|
const TermsDialog = sdk.getComponent("views.dialogs.TermsDialog");
|
||||||
|
|
||||||
Modal.createTrackedDialog('Terms of Service', '', TermsDialog, {
|
Modal.createTrackedDialog('Terms of Service', '', TermsDialog, {
|
||||||
|
|
|
@ -17,10 +17,10 @@ limitations under the License.
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import * as sdk from "../index";
|
|
||||||
import Modal from "../Modal";
|
import Modal from "../Modal";
|
||||||
import { _t, _td } from "../languageHandler";
|
import { _t, _td } from "../languageHandler";
|
||||||
import { isMac, Key } from "../Keyboard";
|
import { isMac, Key } from "../Keyboard";
|
||||||
|
import InfoDialog from "../components/views/dialogs/InfoDialog";
|
||||||
|
|
||||||
// TS: once languageHandler is TS we can probably inline this into the enum
|
// TS: once languageHandler is TS we can probably inline this into the enum
|
||||||
_td("Navigation");
|
_td("Navigation");
|
||||||
|
@ -375,7 +375,6 @@ export const toggleDialog = () => {
|
||||||
</div>;
|
</div>;
|
||||||
});
|
});
|
||||||
|
|
||||||
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
|
||||||
activeModal = Modal.createTrackedDialog("Keyboard Shortcuts", "", InfoDialog, {
|
activeModal = Modal.createTrackedDialog("Keyboard Shortcuts", "", InfoDialog, {
|
||||||
className: "mx_KeyboardShortcutsDialog",
|
className: "mx_KeyboardShortcutsDialog",
|
||||||
title: _t("Keyboard Shortcuts"),
|
title: _t("Keyboard Shortcuts"),
|
||||||
|
|
|
@ -19,13 +19,13 @@ import { asyncAction } from './actionCreators';
|
||||||
import Modal from '../Modal';
|
import Modal from '../Modal';
|
||||||
import * as Rooms from '../Rooms';
|
import * as Rooms from '../Rooms';
|
||||||
import { _t } from '../languageHandler';
|
import { _t } from '../languageHandler';
|
||||||
import * as sdk from '../index';
|
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { AsyncActionPayload } from "../dispatcher/payloads";
|
import { AsyncActionPayload } from "../dispatcher/payloads";
|
||||||
import RoomListStore from "../stores/room-list/RoomListStore";
|
import RoomListStore from "../stores/room-list/RoomListStore";
|
||||||
import { SortAlgorithm } from "../stores/room-list/algorithms/models";
|
import { SortAlgorithm } from "../stores/room-list/algorithms/models";
|
||||||
import { DefaultTagID } from "../stores/room-list/models";
|
import { DefaultTagID } from "../stores/room-list/models";
|
||||||
|
import ErrorDialog from '../components/views/dialogs/ErrorDialog';
|
||||||
|
|
||||||
export default class RoomListActions {
|
export default class RoomListActions {
|
||||||
/**
|
/**
|
||||||
|
@ -88,7 +88,6 @@ export default class RoomListActions {
|
||||||
return Rooms.guessAndSetDMRoom(
|
return Rooms.guessAndSetDMRoom(
|
||||||
room, newTag === DefaultTagID.DM,
|
room, newTag === DefaultTagID.DM,
|
||||||
).catch((err) => {
|
).catch((err) => {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
console.error("Failed to set direct chat tag " + err);
|
console.error("Failed to set direct chat tag " + err);
|
||||||
Modal.createTrackedDialog('Failed to set direct chat tag', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to set direct chat tag', '', ErrorDialog, {
|
||||||
title: _t('Failed to set direct chat tag'),
|
title: _t('Failed to set direct chat tag'),
|
||||||
|
@ -109,7 +108,6 @@ export default class RoomListActions {
|
||||||
const promiseToDelete = matrixClient.deleteRoomTag(
|
const promiseToDelete = matrixClient.deleteRoomTag(
|
||||||
roomId, oldTag,
|
roomId, oldTag,
|
||||||
).catch(function(err) {
|
).catch(function(err) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
console.error("Failed to remove tag " + oldTag + " from room: " + err);
|
console.error("Failed to remove tag " + oldTag + " from room: " + err);
|
||||||
Modal.createTrackedDialog('Failed to remove tag from room', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to remove tag from room', '', ErrorDialog, {
|
||||||
title: _t('Failed to remove tag %(tagName)s from room', { tagName: oldTag }),
|
title: _t('Failed to remove tag %(tagName)s from room', { tagName: oldTag }),
|
||||||
|
@ -129,7 +127,6 @@ export default class RoomListActions {
|
||||||
metaData = metaData || {};
|
metaData = metaData || {};
|
||||||
|
|
||||||
const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function(err) {
|
const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function(err) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
console.error("Failed to add tag " + newTag + " to room: " + err);
|
console.error("Failed to add tag " + newTag + " to room: " + err);
|
||||||
Modal.createTrackedDialog('Failed to add tag to room', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to add tag to room', '', ErrorDialog, {
|
||||||
title: _t('Failed to add tag %(tagName)s to room', { tagName: newTag }),
|
title: _t('Failed to add tag %(tagName)s to room', { tagName: newTag }),
|
||||||
|
|
|
@ -15,7 +15,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import * as sdk from '../../../../index';
|
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
import SdkConfig from '../../../../SdkConfig';
|
import SdkConfig from '../../../../SdkConfig';
|
||||||
import SettingsStore from "../../../../settings/SettingsStore";
|
import SettingsStore from "../../../../settings/SettingsStore";
|
||||||
|
@ -24,6 +23,9 @@ import Modal from '../../../../Modal';
|
||||||
import { formatBytes, formatCountLong } from "../../../../utils/FormattingUtils";
|
import { formatBytes, formatCountLong } from "../../../../utils/FormattingUtils";
|
||||||
import EventIndexPeg from "../../../../indexing/EventIndexPeg";
|
import EventIndexPeg from "../../../../indexing/EventIndexPeg";
|
||||||
import { SettingLevel } from "../../../../settings/SettingLevel";
|
import { SettingLevel } from "../../../../settings/SettingLevel";
|
||||||
|
import Field from '../../../../components/views/elements/Field';
|
||||||
|
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||||
|
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
onFinished: (confirmed: boolean) => void;
|
onFinished: (confirmed: boolean) => void;
|
||||||
|
@ -145,7 +147,6 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const brand = SdkConfig.get().brand;
|
const brand = SdkConfig.get().brand;
|
||||||
const Field = sdk.getComponent('views.elements.Field');
|
|
||||||
|
|
||||||
let crawlerState;
|
let crawlerState;
|
||||||
if (this.state.currentRoom === null) {
|
if (this.state.currentRoom === null) {
|
||||||
|
@ -176,15 +177,12 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
|
||||||
<Field
|
<Field
|
||||||
label={_t('Message downloading sleep time(ms)')}
|
label={_t('Message downloading sleep time(ms)')}
|
||||||
type='number'
|
type='number'
|
||||||
value={this.state.crawlerSleepTime}
|
value={this.state.crawlerSleepTime.toString()}
|
||||||
onChange={this.onCrawlerSleepTimeChange} />
|
onChange={this.onCrawlerSleepTimeChange} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_ManageEventIndexDialog'
|
<BaseDialog className='mx_ManageEventIndexDialog'
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
|
|
|
@ -22,12 +22,12 @@ import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||||
import QueryMatcher from './QueryMatcher';
|
import QueryMatcher from './QueryMatcher';
|
||||||
import { PillCompletion } from './Components';
|
import { PillCompletion } from './Components';
|
||||||
import * as sdk from '../index';
|
|
||||||
import { sortBy } from "lodash";
|
import { sortBy } from "lodash";
|
||||||
import { makeGroupPermalink } from "../utils/permalinks/Permalinks";
|
import { makeGroupPermalink } from "../utils/permalinks/Permalinks";
|
||||||
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
||||||
import FlairStore from "../stores/FlairStore";
|
import FlairStore from "../stores/FlairStore";
|
||||||
import { mediaFromMxc } from "../customisations/Media";
|
import { mediaFromMxc } from "../customisations/Media";
|
||||||
|
import BaseAvatar from '../components/views/avatars/BaseAvatar';
|
||||||
|
|
||||||
const COMMUNITY_REGEX = /\B\+\S*/g;
|
const COMMUNITY_REGEX = /\B\+\S*/g;
|
||||||
|
|
||||||
|
@ -56,8 +56,6 @@ export default class CommunityProvider extends AutocompleteProvider {
|
||||||
force = false,
|
force = false,
|
||||||
limit = -1,
|
limit = -1,
|
||||||
): Promise<ICompletion[]> {
|
): Promise<ICompletion[]> {
|
||||||
const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar');
|
|
||||||
|
|
||||||
// Disable autocompletions when composing commands because of various issues
|
// Disable autocompletions when composing commands because of various issues
|
||||||
// (see https://github.com/vector-im/element-web/issues/4762)
|
// (see https://github.com/vector-im/element-web/issues/4762)
|
||||||
if (/^(\/join|\/leave)/.test(query)) {
|
if (/^(\/join|\/leave)/.test(query)) {
|
||||||
|
|
|
@ -21,8 +21,8 @@ import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import { _t } from '../languageHandler';
|
import { _t } from '../languageHandler';
|
||||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||||
import { PillCompletion } from './Components';
|
import { PillCompletion } from './Components';
|
||||||
import * as sdk from '../index';
|
|
||||||
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
||||||
|
import RoomAvatar from '../components/views/avatars/RoomAvatar';
|
||||||
|
|
||||||
const AT_ROOM_REGEX = /@\S*/g;
|
const AT_ROOM_REGEX = /@\S*/g;
|
||||||
|
|
||||||
|
@ -40,8 +40,6 @@ export default class NotifProvider extends AutocompleteProvider {
|
||||||
force = false,
|
force = false,
|
||||||
limit = -1,
|
limit = -1,
|
||||||
): Promise<ICompletion[]> {
|
): Promise<ICompletion[]> {
|
||||||
const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
|
|
||||||
|
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
|
|
||||||
if (!this.room.currentState.mayTriggerNotifOfType('room', client.credentials.userId)) return [];
|
if (!this.room.currentState.mayTriggerNotifOfType('room', client.credentials.userId)) return [];
|
||||||
|
|
|
@ -21,7 +21,6 @@ import React from 'react';
|
||||||
import { _t } from '../languageHandler';
|
import { _t } from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import { PillCompletion } from './Components';
|
import { PillCompletion } from './Components';
|
||||||
import * as sdk from '../index';
|
|
||||||
import QueryMatcher from './QueryMatcher';
|
import QueryMatcher from './QueryMatcher';
|
||||||
import { sortBy } from 'lodash';
|
import { sortBy } from 'lodash';
|
||||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||||
|
@ -33,6 +32,7 @@ import { RoomState } from "matrix-js-sdk/src/models/room-state";
|
||||||
import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
|
import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
|
||||||
import { makeUserPermalink } from "../utils/permalinks/Permalinks";
|
import { makeUserPermalink } from "../utils/permalinks/Permalinks";
|
||||||
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
import { ICompletion, ISelectionRange } from "./Autocompleter";
|
||||||
|
import MemberAvatar from '../components/views/avatars/MemberAvatar';
|
||||||
|
|
||||||
const USER_REGEX = /\B@\S*/g;
|
const USER_REGEX = /\B@\S*/g;
|
||||||
|
|
||||||
|
@ -108,8 +108,6 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
force = false,
|
force = false,
|
||||||
limit = -1,
|
limit = -1,
|
||||||
): Promise<ICompletion[]> {
|
): Promise<ICompletion[]> {
|
||||||
const MemberAvatar = sdk.getComponent('views.avatars.MemberAvatar');
|
|
||||||
|
|
||||||
// lazy-load user list into matcher
|
// lazy-load user list into matcher
|
||||||
if (!this.users) this._makeUsers();
|
if (!this.users) this._makeUsers();
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,8 @@ interface IProps extends Omit<HTMLAttributes<HTMLDivElement>, "onScroll"> {
|
||||||
className?: string;
|
className?: string;
|
||||||
onScroll?: (event: Event) => void;
|
onScroll?: (event: Event) => void;
|
||||||
onWheel?: (event: WheelEvent) => void;
|
onWheel?: (event: WheelEvent) => void;
|
||||||
style?: React.CSSProperties
|
style?: React.CSSProperties;
|
||||||
tabIndex?: number,
|
tabIndex?: number;
|
||||||
wrappedRef?: (ref: HTMLDivElement) => void;
|
wrappedRef?: (ref: HTMLDivElement) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,11 @@ import React from 'react';
|
||||||
|
|
||||||
import { Filter } from 'matrix-js-sdk/src/filter';
|
import { Filter } from 'matrix-js-sdk/src/filter';
|
||||||
import { EventTimelineSet } from "matrix-js-sdk/src/models/event-timeline-set";
|
import { EventTimelineSet } from "matrix-js-sdk/src/models/event-timeline-set";
|
||||||
|
import { Direction } from "matrix-js-sdk/src/models/event-timeline";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { Room } from 'matrix-js-sdk/src/models/room';
|
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||||
import { TimelineWindow } from 'matrix-js-sdk/src/timeline-window';
|
import { TimelineWindow } from 'matrix-js-sdk/src/timeline-window';
|
||||||
|
|
||||||
import * as sdk from '../../index';
|
|
||||||
import { MatrixClientPeg } from '../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../MatrixClientPeg';
|
||||||
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
@ -33,11 +33,14 @@ import DesktopBuildsNotice, { WarningKind } from "../views/elements/DesktopBuild
|
||||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
import ResizeNotifier from '../../utils/ResizeNotifier';
|
import ResizeNotifier from '../../utils/ResizeNotifier';
|
||||||
|
import TimelinePanel from "./TimelinePanel";
|
||||||
|
import Spinner from "../views/elements/Spinner";
|
||||||
|
import { TileShape } from '../views/rooms/EventTile';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
resizeNotifier: ResizeNotifier
|
resizeNotifier: ResizeNotifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -129,7 +132,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async fetchFileEventsServer(room: Room): Promise<void> {
|
public async fetchFileEventsServer(room: Room): Promise<EventTimelineSet> {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
|
|
||||||
const filter = new Filter(client.credentials.userId);
|
const filter = new Filter(client.credentials.userId);
|
||||||
|
@ -153,7 +156,11 @@ class FilePanel extends React.Component<IProps, IState> {
|
||||||
return timelineSet;
|
return timelineSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onPaginationRequest = (timelineWindow: TimelineWindow, direction: string, limit: number): void => {
|
private onPaginationRequest = (
|
||||||
|
timelineWindow: TimelineWindow,
|
||||||
|
direction: Direction,
|
||||||
|
limit: number,
|
||||||
|
): Promise<boolean> => {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const eventIndex = EventIndexPeg.get();
|
const eventIndex = EventIndexPeg.get();
|
||||||
const roomId = this.props.roomId;
|
const roomId = this.props.roomId;
|
||||||
|
@ -232,8 +239,6 @@ class FilePanel extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
||||||
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
|
||||||
|
|
||||||
const emptyState = (<div className="mx_RightPanel_empty mx_FilePanel_empty">
|
const emptyState = (<div className="mx_RightPanel_empty mx_FilePanel_empty">
|
||||||
<h2>{_t('No files visible in this room')}</h2>
|
<h2>{_t('No files visible in this room')}</h2>
|
||||||
|
@ -259,7 +264,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
||||||
timelineSet={this.state.timelineSet}
|
timelineSet={this.state.timelineSet}
|
||||||
showUrlPreview = {false}
|
showUrlPreview = {false}
|
||||||
onPaginationRequest={this.onPaginationRequest}
|
onPaginationRequest={this.onPaginationRequest}
|
||||||
tileShape="file_grid"
|
tileShape={TileShape.FileGrid}
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
empty={emptyState}
|
empty={emptyState}
|
||||||
/>
|
/>
|
||||||
|
@ -272,7 +277,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
||||||
onClose={this.props.onClose}
|
onClose={this.props.onClose}
|
||||||
previousPhase={RightPanelPhases.RoomSummary}
|
previousPhase={RightPanelPhases.RoomSummary}
|
||||||
>
|
>
|
||||||
<Loader />
|
<Spinner />
|
||||||
</BaseCard>
|
</BaseCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ import FlairStore from '../../stores/FlairStore';
|
||||||
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
||||||
import { makeGroupPermalink, makeUserPermalink } from "../../utils/permalinks/Permalinks";
|
import { makeGroupPermalink, makeUserPermalink } from "../../utils/permalinks/Permalinks";
|
||||||
import { Group } from "matrix-js-sdk/src/models/group";
|
import { Group } from "matrix-js-sdk/src/models/group";
|
||||||
import { sleep } from "../../utils/promise";
|
import { sleep } from "matrix-js-sdk/src/utils";
|
||||||
import RightPanelStore from "../../stores/RightPanelStore";
|
import RightPanelStore from "../../stores/RightPanelStore";
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
import { mediaFromMxc } from "../../customisations/Media";
|
import { mediaFromMxc } from "../../customisations/Media";
|
||||||
|
|
|
@ -96,6 +96,7 @@ const HomePage: React.FC<IProps> = ({ justRegistered = false }) => {
|
||||||
const pageUrl = getHomePageUrl(config);
|
const pageUrl = getHomePageUrl(config);
|
||||||
|
|
||||||
if (pageUrl) {
|
if (pageUrl) {
|
||||||
|
// FIXME: Using an import will result in wrench-element-tests failures
|
||||||
const EmbeddedPage = sdk.getComponent('structures.EmbeddedPage');
|
const EmbeddedPage = sdk.getComponent('structures.EmbeddedPage');
|
||||||
return <EmbeddedPage className="mx_HomePage" url={pageUrl} scrollbar={true} />;
|
return <EmbeddedPage className="mx_HomePage" url={pageUrl} scrollbar={true} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import { Key } from '../../Keyboard';
|
||||||
import PageTypes from '../../PageTypes';
|
import PageTypes from '../../PageTypes';
|
||||||
import MediaDeviceHandler from '../../MediaDeviceHandler';
|
import MediaDeviceHandler from '../../MediaDeviceHandler';
|
||||||
import { fixupColorFonts } from '../../utils/FontManager';
|
import { fixupColorFonts } from '../../utils/FontManager';
|
||||||
import * as sdk from '../../index';
|
|
||||||
import dis from '../../dispatcher/dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import { IMatrixClientCreds } from '../../MatrixClientPeg';
|
import { IMatrixClientCreds } from '../../MatrixClientPeg';
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
|
@ -48,7 +47,7 @@ import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPay
|
||||||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||||
import NonUrgentToastContainer from "./NonUrgentToastContainer";
|
import NonUrgentToastContainer from "./NonUrgentToastContainer";
|
||||||
import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload";
|
import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload";
|
||||||
import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
||||||
import Modal from "../../Modal";
|
import Modal from "../../Modal";
|
||||||
import { ICollapseConfig } from "../../resizer/distributors/collapse";
|
import { ICollapseConfig } from "../../resizer/distributors/collapse";
|
||||||
import HostSignupContainer from '../views/host_signup/HostSignupContainer';
|
import HostSignupContainer from '../views/host_signup/HostSignupContainer';
|
||||||
|
@ -59,6 +58,11 @@ import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
|
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
|
||||||
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
import AudioFeedArrayForCall from '../views/voip/AudioFeedArrayForCall';
|
import AudioFeedArrayForCall from '../views/voip/AudioFeedArrayForCall';
|
||||||
|
import RoomView from './RoomView';
|
||||||
|
import ToastContainer from './ToastContainer';
|
||||||
|
import MyGroups from "./MyGroups";
|
||||||
|
import UserView from "./UserView";
|
||||||
|
import GroupView from "./GroupView";
|
||||||
|
|
||||||
// We need to fetch each pinned message individually (if we don't already have it)
|
// We need to fetch each pinned message individually (if we don't already have it)
|
||||||
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||||
|
@ -78,17 +82,17 @@ interface IProps {
|
||||||
hideToSRUsers: boolean;
|
hideToSRUsers: boolean;
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
page_type: string;
|
page_type?: string;
|
||||||
autoJoin: boolean;
|
autoJoin?: boolean;
|
||||||
threepidInvite?: IThreepidInvite;
|
threepidInvite?: IThreepidInvite;
|
||||||
roomOobData?: object;
|
roomOobData?: IOOBData;
|
||||||
currentRoomId: string;
|
currentRoomId: string;
|
||||||
collapseLhs: boolean;
|
collapseLhs: boolean;
|
||||||
config: {
|
config: {
|
||||||
piwik: {
|
piwik: {
|
||||||
policyUrl: string;
|
policyUrl: string;
|
||||||
},
|
};
|
||||||
[key: string]: any,
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
currentUserId?: string;
|
currentUserId?: string;
|
||||||
currentGroupId?: string;
|
currentGroupId?: string;
|
||||||
|
@ -394,7 +398,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
// refocusing during a paste event will make the
|
// refocusing during a paste event will make the
|
||||||
// paste end up in the newly focused element,
|
// paste end up in the newly focused element,
|
||||||
// so dispatch synchronously before paste happens
|
// so dispatch synchronously before paste happens
|
||||||
dis.fire(Action.FocusComposer, true);
|
dis.fire(Action.FocusSendMessageComposer, true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -548,7 +552,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
if (!isClickShortcut && ev.key !== Key.TAB && !canElementReceiveInput(ev.target)) {
|
if (!isClickShortcut && ev.key !== Key.TAB && !canElementReceiveInput(ev.target)) {
|
||||||
// synchronous dispatch so we focus before key generates input
|
// synchronous dispatch so we focus before key generates input
|
||||||
dis.fire(Action.FocusComposer, true);
|
dis.fire(Action.FocusSendMessageComposer, true);
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
// we should *not* preventDefault() here as
|
// we should *not* preventDefault() here as
|
||||||
// that would prevent typing in the now-focussed composer
|
// that would prevent typing in the now-focussed composer
|
||||||
|
@ -567,12 +571,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const RoomView = sdk.getComponent('structures.RoomView');
|
|
||||||
const UserView = sdk.getComponent('structures.UserView');
|
|
||||||
const GroupView = sdk.getComponent('structures.GroupView');
|
|
||||||
const MyGroups = sdk.getComponent('structures.MyGroups');
|
|
||||||
const ToastContainer = sdk.getComponent('structures.ToastContainer');
|
|
||||||
|
|
||||||
let pageElement;
|
let pageElement;
|
||||||
|
|
||||||
switch (this.props.page_type) {
|
switch (this.props.page_type) {
|
||||||
|
|
|
@ -19,6 +19,8 @@ import { createClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
import { sleep, defer, IDeferred } from "matrix-js-sdk/src/utils";
|
||||||
|
|
||||||
// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss
|
// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss
|
||||||
import 'focus-visible';
|
import 'focus-visible';
|
||||||
// what-input helps improve keyboard accessibility
|
// what-input helps improve keyboard accessibility
|
||||||
|
@ -34,7 +36,6 @@ import dis from "../../dispatcher/dispatcher";
|
||||||
import Notifier from '../../Notifier';
|
import Notifier from '../../Notifier';
|
||||||
|
|
||||||
import Modal from "../../Modal";
|
import Modal from "../../Modal";
|
||||||
import * as sdk from '../../index';
|
|
||||||
import { showRoomInviteDialog, showStartChatInviteDialog } from '../../RoomInvite';
|
import { showRoomInviteDialog, showStartChatInviteDialog } from '../../RoomInvite';
|
||||||
import * as Rooms from '../../Rooms';
|
import * as Rooms from '../../Rooms';
|
||||||
import linkifyMatrix from "../../linkify-matrix";
|
import linkifyMatrix from "../../linkify-matrix";
|
||||||
|
@ -55,7 +56,6 @@ import DMRoomMap from '../../utils/DMRoomMap';
|
||||||
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
|
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
|
||||||
import { FontWatcher } from '../../settings/watchers/FontWatcher';
|
import { FontWatcher } from '../../settings/watchers/FontWatcher';
|
||||||
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
||||||
import { defer, IDeferred, sleep } from "../../utils/promise";
|
|
||||||
import ToastStore from "../../stores/ToastStore";
|
import ToastStore from "../../stores/ToastStore";
|
||||||
import * as StorageManager from "../../utils/StorageManager";
|
import * as StorageManager from "../../utils/StorageManager";
|
||||||
import type LoggedInViewType from "./LoggedInView";
|
import type LoggedInViewType from "./LoggedInView";
|
||||||
|
@ -84,9 +84,27 @@ import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||||
import { RoomUpdateCause } from "../../stores/room-list/models";
|
import { RoomUpdateCause } from "../../stores/room-list/models";
|
||||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
import SecurityCustomisations from "../../customisations/Security";
|
import SecurityCustomisations from "../../customisations/Security";
|
||||||
|
import Spinner from "../views/elements/Spinner";
|
||||||
|
import QuestionDialog from "../views/dialogs/QuestionDialog";
|
||||||
|
import UserSettingsDialog from '../views/dialogs/UserSettingsDialog';
|
||||||
|
import CreateGroupDialog from '../views/dialogs/CreateGroupDialog';
|
||||||
|
import CreateRoomDialog from '../views/dialogs/CreateRoomDialog';
|
||||||
|
import RoomDirectory from './RoomDirectory';
|
||||||
|
import KeySignatureUploadFailedDialog from "../views/dialogs/KeySignatureUploadFailedDialog";
|
||||||
|
import IncomingSasDialog from "../views/dialogs/IncomingSasDialog";
|
||||||
|
import CompleteSecurity from "./auth/CompleteSecurity";
|
||||||
|
import LoggedInView from './LoggedInView';
|
||||||
|
import Welcome from "../views/auth/Welcome";
|
||||||
|
import ForgotPassword from "./auth/ForgotPassword";
|
||||||
|
import E2eSetup from "./auth/E2eSetup";
|
||||||
|
import Registration from './auth/Registration';
|
||||||
|
import Login from "./auth/Login";
|
||||||
|
import ErrorBoundary from '../views/elements/ErrorBoundary';
|
||||||
|
import VerificationRequestToast from '../views/toasts/VerificationRequestToast';
|
||||||
|
|
||||||
import PerformanceMonitor, { PerformanceEntryNames } from "../../performance";
|
import PerformanceMonitor, { PerformanceEntryNames } from "../../performance";
|
||||||
import UIStore, { UI_EVENTS } from "../../stores/UIStore";
|
import UIStore, { UI_EVENTS } from "../../stores/UIStore";
|
||||||
|
import SoftLogout from './auth/SoftLogout';
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
export enum Views {
|
export enum Views {
|
||||||
|
@ -155,7 +173,12 @@ interface IRoomInfo {
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
interface IProps { // TODO type things better
|
interface IProps { // TODO type things better
|
||||||
config: Record<string, any>;
|
config: {
|
||||||
|
piwik: {
|
||||||
|
policyUrl: string;
|
||||||
|
};
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
serverConfig?: ValidatedServerConfig;
|
serverConfig?: ValidatedServerConfig;
|
||||||
onNewScreen: (screen: string, replaceLast: boolean) => void;
|
onNewScreen: (screen: string, replaceLast: boolean) => void;
|
||||||
enableGuest?: boolean;
|
enableGuest?: boolean;
|
||||||
|
@ -203,7 +226,7 @@ interface IState {
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
serverConfig?: ValidatedServerConfig;
|
serverConfig?: ValidatedServerConfig;
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
threepidInvite?: IThreepidInvite,
|
threepidInvite?: IThreepidInvite;
|
||||||
roomOobData?: object;
|
roomOobData?: object;
|
||||||
pendingInitialSync?: boolean;
|
pendingInitialSync?: boolean;
|
||||||
justRegistered?: boolean;
|
justRegistered?: boolean;
|
||||||
|
@ -420,7 +443,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
CountlyAnalytics.instance.trackPageChange(durationMs);
|
CountlyAnalytics.instance.trackPageChange(durationMs);
|
||||||
}
|
}
|
||||||
if (this.focusComposer) {
|
if (this.focusComposer) {
|
||||||
dis.fire(Action.FocusComposer);
|
dis.fire(Action.FocusSendMessageComposer);
|
||||||
this.focusComposer = false;
|
this.focusComposer = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -518,7 +541,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
onAction = (payload) => {
|
onAction = (payload) => {
|
||||||
// console.log(`MatrixClientPeg.onAction: ${payload.action}`);
|
// console.log(`MatrixClientPeg.onAction: ${payload.action}`);
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
||||||
|
|
||||||
// Start the onboarding process for certain actions
|
// Start the onboarding process for certain actions
|
||||||
if (MatrixClientPeg.get() && MatrixClientPeg.get().isGuest() &&
|
if (MatrixClientPeg.get() && MatrixClientPeg.get().isGuest() &&
|
||||||
|
@ -612,8 +634,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
onFinished: (confirm) => {
|
onFinished: (confirm) => {
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
// FIXME: controller shouldn't be loading a view :(
|
// FIXME: controller shouldn't be loading a view :(
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
|
||||||
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
|
||||||
|
|
||||||
MatrixClientPeg.get().leave(payload.room_id).then(() => {
|
MatrixClientPeg.get().leave(payload.room_id).then(() => {
|
||||||
modal.close();
|
modal.close();
|
||||||
|
@ -649,7 +670,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
case Action.ViewUserSettings: {
|
case Action.ViewUserSettings: {
|
||||||
const tabPayload = payload as OpenToTabPayload;
|
const tabPayload = payload as OpenToTabPayload;
|
||||||
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
|
|
||||||
Modal.createTrackedDialog('User settings', '', UserSettingsDialog,
|
Modal.createTrackedDialog('User settings', '', UserSettingsDialog,
|
||||||
{ initialTabId: tabPayload.initialTabId },
|
{ initialTabId: tabPayload.initialTabId },
|
||||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||||
|
@ -662,11 +682,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.createRoom(payload.public, payload.defaultName);
|
this.createRoom(payload.public, payload.defaultName);
|
||||||
break;
|
break;
|
||||||
case 'view_create_group': {
|
case 'view_create_group': {
|
||||||
let CreateGroupDialog = sdk.getComponent("dialogs.CreateGroupDialog");
|
const prototype = SettingsStore.getValue("feature_communities_v2_prototypes");
|
||||||
if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
|
Modal.createTrackedDialog(
|
||||||
CreateGroupDialog = CreateCommunityPrototypeDialog;
|
'Create Community',
|
||||||
}
|
'',
|
||||||
Modal.createTrackedDialog('Create Community', '', CreateGroupDialog);
|
prototype ? CreateCommunityPrototypeDialog : CreateGroupDialog,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Action.ViewRoomDirectory: {
|
case Action.ViewRoomDirectory: {
|
||||||
|
@ -676,7 +697,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
room_id: SpaceStore.instance.activeSpace.roomId,
|
room_id: SpaceStore.instance.activeSpace.roomId,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const RoomDirectory = sdk.getComponent("structures.RoomDirectory");
|
|
||||||
Modal.createTrackedDialog('Room directory', '', RoomDirectory, {
|
Modal.createTrackedDialog('Room directory', '', RoomDirectory, {
|
||||||
initialText: payload.initialText,
|
initialText: payload.initialText,
|
||||||
}, 'mx_RoomDirectory_dialogWrapper', false, true);
|
}, 'mx_RoomDirectory_dialogWrapper', false, true);
|
||||||
|
@ -1018,7 +1038,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog');
|
|
||||||
const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog, {
|
const modal = Modal.createTrackedDialog('Create Room', '', CreateRoomDialog, {
|
||||||
defaultPublic,
|
defaultPublic,
|
||||||
defaultName,
|
defaultName,
|
||||||
|
@ -1115,7 +1134,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private leaveRoom(roomId: string) {
|
private leaveRoom(roomId: string) {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
||||||
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
||||||
const warnings = this.leaveRoomWarnings(roomId);
|
const warnings = this.leaveRoomWarnings(roomId);
|
||||||
|
|
||||||
|
@ -1142,8 +1160,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
const d = leaveRoomBehaviour(roomId);
|
const d = leaveRoomBehaviour(roomId);
|
||||||
|
|
||||||
// FIXME: controller shouldn't be loading a view :(
|
// FIXME: controller shouldn't be loading a view :(
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
|
||||||
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
|
||||||
|
|
||||||
d.finally(() => modal.close());
|
d.finally(() => modal.close());
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
|
@ -1410,7 +1427,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
showNotificationsToast(false);
|
showNotificationsToast(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
dis.fire(Action.FocusComposer);
|
dis.fire(Action.FocusSendMessageComposer);
|
||||||
this.setState({
|
this.setState({
|
||||||
ready: true,
|
ready: true,
|
||||||
});
|
});
|
||||||
|
@ -1438,7 +1455,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
cli.on('no_consent', function(message, consentUri) {
|
cli.on('no_consent', function(message, consentUri) {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
||||||
Modal.createTrackedDialog('No Consent Dialog', '', QuestionDialog, {
|
Modal.createTrackedDialog('No Consent Dialog', '', QuestionDialog, {
|
||||||
title: _t('Terms and Conditions'),
|
title: _t('Terms and Conditions'),
|
||||||
description: <div>
|
description: <div>
|
||||||
|
@ -1547,8 +1563,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
});
|
});
|
||||||
|
|
||||||
cli.on("crypto.keySignatureUploadFailure", (failures, source, continuation) => {
|
cli.on("crypto.keySignatureUploadFailure", (failures, source, continuation) => {
|
||||||
const KeySignatureUploadFailedDialog =
|
|
||||||
sdk.getComponent('views.dialogs.KeySignatureUploadFailedDialog');
|
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
'Failed to upload key signatures',
|
'Failed to upload key signatures',
|
||||||
'Failed to upload key signatures',
|
'Failed to upload key signatures',
|
||||||
|
@ -1558,7 +1572,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
cli.on("crypto.verification.request", request => {
|
cli.on("crypto.verification.request", request => {
|
||||||
if (request.verifier) {
|
if (request.verifier) {
|
||||||
const IncomingSasDialog = sdk.getComponent("views.dialogs.IncomingSasDialog");
|
|
||||||
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
|
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
|
||||||
verifier: request.verifier,
|
verifier: request.verifier,
|
||||||
}, null, /* priority = */ false, /* static = */ true);
|
}, null, /* priority = */ false, /* static = */ true);
|
||||||
|
@ -1568,7 +1581,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
title: _t("Verification requested"),
|
title: _t("Verification requested"),
|
||||||
icon: "verification",
|
icon: "verification",
|
||||||
props: { request },
|
props: { request },
|
||||||
component: sdk.getComponent("toasts.VerificationRequestToast"),
|
component: VerificationRequestToast,
|
||||||
priority: 90,
|
priority: 90,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1976,21 +1989,18 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
let view = null;
|
let view = null;
|
||||||
|
|
||||||
if (this.state.view === Views.LOADING) {
|
if (this.state.view === Views.LOADING) {
|
||||||
const Spinner = sdk.getComponent('elements.Spinner');
|
|
||||||
view = (
|
view = (
|
||||||
<div className="mx_MatrixChat_splash">
|
<div className="mx_MatrixChat_splash">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (this.state.view === Views.COMPLETE_SECURITY) {
|
} else if (this.state.view === Views.COMPLETE_SECURITY) {
|
||||||
const CompleteSecurity = sdk.getComponent('structures.auth.CompleteSecurity');
|
|
||||||
view = (
|
view = (
|
||||||
<CompleteSecurity
|
<CompleteSecurity
|
||||||
onFinished={this.onCompleteSecurityE2eSetupFinished}
|
onFinished={this.onCompleteSecurityE2eSetupFinished}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (this.state.view === Views.E2E_SETUP) {
|
} else if (this.state.view === Views.E2E_SETUP) {
|
||||||
const E2eSetup = sdk.getComponent('structures.auth.E2eSetup');
|
|
||||||
view = (
|
view = (
|
||||||
<E2eSetup
|
<E2eSetup
|
||||||
onFinished={this.onCompleteSecurityE2eSetupFinished}
|
onFinished={this.onCompleteSecurityE2eSetupFinished}
|
||||||
|
@ -2011,7 +2021,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
* we should go through and figure out what we actually need to pass down, as well
|
* we should go through and figure out what we actually need to pass down, as well
|
||||||
* as using something like redux to avoid having a billion bits of state kicking around.
|
* as using something like redux to avoid having a billion bits of state kicking around.
|
||||||
*/
|
*/
|
||||||
const LoggedInView = sdk.getComponent('structures.LoggedInView');
|
|
||||||
view = (
|
view = (
|
||||||
<LoggedInView
|
<LoggedInView
|
||||||
{...this.props}
|
{...this.props}
|
||||||
|
@ -2019,14 +2028,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
ref={this.loggedInView}
|
ref={this.loggedInView}
|
||||||
matrixClient={MatrixClientPeg.get()}
|
matrixClient={MatrixClientPeg.get()}
|
||||||
onRoomCreated={this.onRoomCreated}
|
onRoomCreated={this.onRoomCreated}
|
||||||
onCloseAllSettings={this.onCloseAllSettings}
|
|
||||||
onRegistered={this.onRegistered}
|
onRegistered={this.onRegistered}
|
||||||
currentRoomId={this.state.currentRoomId}
|
currentRoomId={this.state.currentRoomId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// we think we are logged in, but are still waiting for the /sync to complete
|
// we think we are logged in, but are still waiting for the /sync to complete
|
||||||
const Spinner = sdk.getComponent('elements.Spinner');
|
|
||||||
let errorBox;
|
let errorBox;
|
||||||
if (this.state.syncError && !isStoreError) {
|
if (this.state.syncError && !isStoreError) {
|
||||||
errorBox = <div className="mx_MatrixChat_syncError">
|
errorBox = <div className="mx_MatrixChat_syncError">
|
||||||
|
@ -2044,10 +2051,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (this.state.view === Views.WELCOME) {
|
} else if (this.state.view === Views.WELCOME) {
|
||||||
const Welcome = sdk.getComponent('auth.Welcome');
|
|
||||||
view = <Welcome />;
|
view = <Welcome />;
|
||||||
} else if (this.state.view === Views.REGISTER && SettingsStore.getValue(UIFeature.Registration)) {
|
} else if (this.state.view === Views.REGISTER && SettingsStore.getValue(UIFeature.Registration)) {
|
||||||
const Registration = sdk.getComponent('structures.auth.Registration');
|
|
||||||
const email = ThreepidInviteStore.instance.pickBestInvite()?.toEmail;
|
const email = ThreepidInviteStore.instance.pickBestInvite()?.toEmail;
|
||||||
view = (
|
view = (
|
||||||
<Registration
|
<Registration
|
||||||
|
@ -2066,7 +2071,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (this.state.view === Views.FORGOT_PASSWORD && SettingsStore.getValue(UIFeature.PasswordReset)) {
|
} else if (this.state.view === Views.FORGOT_PASSWORD && SettingsStore.getValue(UIFeature.PasswordReset)) {
|
||||||
const ForgotPassword = sdk.getComponent('structures.auth.ForgotPassword');
|
|
||||||
view = (
|
view = (
|
||||||
<ForgotPassword
|
<ForgotPassword
|
||||||
onComplete={this.onLoginClick}
|
onComplete={this.onLoginClick}
|
||||||
|
@ -2077,7 +2081,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
);
|
);
|
||||||
} else if (this.state.view === Views.LOGIN) {
|
} else if (this.state.view === Views.LOGIN) {
|
||||||
const showPasswordReset = SettingsStore.getValue(UIFeature.PasswordReset);
|
const showPasswordReset = SettingsStore.getValue(UIFeature.PasswordReset);
|
||||||
const Login = sdk.getComponent('structures.auth.Login');
|
|
||||||
view = (
|
view = (
|
||||||
<Login
|
<Login
|
||||||
isSyncing={this.state.pendingInitialSync}
|
isSyncing={this.state.pendingInitialSync}
|
||||||
|
@ -2093,7 +2096,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (this.state.view === Views.SOFT_LOGOUT) {
|
} else if (this.state.view === Views.SOFT_LOGOUT) {
|
||||||
const SoftLogout = sdk.getComponent('structures.auth.SoftLogout');
|
|
||||||
view = (
|
view = (
|
||||||
<SoftLogout
|
<SoftLogout
|
||||||
realQueryParams={this.props.realQueryParams}
|
realQueryParams={this.props.realQueryParams}
|
||||||
|
@ -2105,7 +2107,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
console.error(`Unknown view ${this.state.view}`);
|
console.error(`Unknown view ${this.state.view}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ErrorBoundary = sdk.getComponent('elements.ErrorBoundary');
|
|
||||||
return <ErrorBoundary>
|
return <ErrorBoundary>
|
||||||
{view}
|
{view}
|
||||||
</ErrorBoundary>;
|
</ErrorBoundary>;
|
||||||
|
|
|
@ -24,7 +24,7 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
toasts: ComponentClass[],
|
toasts: ComponentClass[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("structures.NonUrgentToastContainer")
|
@replaceableComponent("structures.NonUrgentToastContainer")
|
||||||
|
|
|
@ -23,7 +23,6 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||||
|
|
||||||
import dis from '../../dispatcher/dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import RateLimitedFunc from '../../ratelimitedfunc';
|
|
||||||
import GroupStore from '../../stores/GroupStore';
|
import GroupStore from '../../stores/GroupStore';
|
||||||
import {
|
import {
|
||||||
RIGHT_PANEL_PHASES_NO_ARGS,
|
RIGHT_PANEL_PHASES_NO_ARGS,
|
||||||
|
@ -48,6 +47,7 @@ import FilePanel from "./FilePanel";
|
||||||
import NotificationPanel from "./NotificationPanel";
|
import NotificationPanel from "./NotificationPanel";
|
||||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||||
import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard";
|
import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard";
|
||||||
|
import { throttle } from 'lodash';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room?: Room; // if showing panels for a given room, this is set
|
room?: Room; // if showing panels for a given room, this is set
|
||||||
|
@ -73,7 +73,6 @@ interface IState {
|
||||||
export default class RightPanel extends React.Component<IProps, IState> {
|
export default class RightPanel extends React.Component<IProps, IState> {
|
||||||
static contextType = MatrixClientContext;
|
static contextType = MatrixClientContext;
|
||||||
|
|
||||||
private readonly delayedUpdate: RateLimitedFunc;
|
|
||||||
private dispatcherRef: string;
|
private dispatcherRef: string;
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
|
@ -84,12 +83,12 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
||||||
isUserPrivilegedInGroup: null,
|
isUserPrivilegedInGroup: null,
|
||||||
member: this.getUserForPanel(),
|
member: this.getUserForPanel(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.delayedUpdate = new RateLimitedFunc(() => {
|
|
||||||
this.forceUpdate();
|
|
||||||
}, 500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly delayedUpdate = throttle((): void => {
|
||||||
|
this.forceUpdate();
|
||||||
|
}, 500, { leading: true, trailing: true });
|
||||||
|
|
||||||
// Helper function to split out the logic for getPhaseFromProps() and the constructor
|
// Helper function to split out the logic for getPhaseFromProps() and the constructor
|
||||||
// as both are called at the same time in the constructor.
|
// as both are called at the same time in the constructor.
|
||||||
private getUserForPanel() {
|
private getUserForPanel() {
|
||||||
|
|
|
@ -48,6 +48,9 @@ import { ActionPayload } from "../../dispatcher/payloads";
|
||||||
const MAX_NAME_LENGTH = 80;
|
const MAX_NAME_LENGTH = 80;
|
||||||
const MAX_TOPIC_LENGTH = 800;
|
const MAX_TOPIC_LENGTH = 800;
|
||||||
|
|
||||||
|
const LAST_SERVER_KEY = "mx_last_room_directory_server";
|
||||||
|
const LAST_INSTANCE_KEY = "mx_last_room_directory_instance";
|
||||||
|
|
||||||
function track(action: string) {
|
function track(action: string) {
|
||||||
Analytics.trackEvent('RoomDirectory', action);
|
Analytics.trackEvent('RoomDirectory', action);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +64,7 @@ interface IState {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
protocolsLoading: boolean;
|
protocolsLoading: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
instanceId: string | symbol;
|
instanceId: string;
|
||||||
roomServer: string;
|
roomServer: string;
|
||||||
filterString: string;
|
filterString: string;
|
||||||
selectedCommunityId?: string;
|
selectedCommunityId?: string;
|
||||||
|
@ -116,6 +119,36 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
} else if (!selectedCommunityId) {
|
} else if (!selectedCommunityId) {
|
||||||
MatrixClientPeg.get().getThirdpartyProtocols().then((response) => {
|
MatrixClientPeg.get().getThirdpartyProtocols().then((response) => {
|
||||||
this.protocols = response;
|
this.protocols = response;
|
||||||
|
const myHomeserver = MatrixClientPeg.getHomeserverName();
|
||||||
|
const lsRoomServer = localStorage.getItem(LAST_SERVER_KEY);
|
||||||
|
const lsInstanceId = localStorage.getItem(LAST_INSTANCE_KEY);
|
||||||
|
|
||||||
|
let roomServer = myHomeserver;
|
||||||
|
if (
|
||||||
|
SdkConfig.get().roomDirectory?.servers?.includes(lsRoomServer) ||
|
||||||
|
SettingsStore.getValue("room_directory_servers")?.includes(lsRoomServer)
|
||||||
|
) {
|
||||||
|
roomServer = lsRoomServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
let instanceId: string = null;
|
||||||
|
if (roomServer === myHomeserver && (
|
||||||
|
lsInstanceId === ALL_ROOMS ||
|
||||||
|
Object.values(this.protocols).some(p => p.instances.some(i => i.instance_id === lsInstanceId))
|
||||||
|
)) {
|
||||||
|
instanceId = lsInstanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the room list only if validation failed and we had to change these
|
||||||
|
if (this.state.instanceId !== instanceId || this.state.roomServer !== roomServer) {
|
||||||
|
this.setState({
|
||||||
|
protocolsLoading: false,
|
||||||
|
instanceId,
|
||||||
|
roomServer,
|
||||||
|
});
|
||||||
|
this.refreshRoomList();
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.setState({ protocolsLoading: false });
|
this.setState({ protocolsLoading: false });
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.warn(`error loading third party protocols: ${err}`);
|
console.warn(`error loading third party protocols: ${err}`);
|
||||||
|
@ -150,8 +183,8 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
publicRooms: [],
|
publicRooms: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
error: null,
|
error: null,
|
||||||
instanceId: undefined,
|
instanceId: localStorage.getItem(LAST_INSTANCE_KEY),
|
||||||
roomServer: MatrixClientPeg.getHomeserverName(),
|
roomServer: localStorage.getItem(LAST_SERVER_KEY),
|
||||||
filterString: this.props.initialText || "",
|
filterString: this.props.initialText || "",
|
||||||
selectedCommunityId,
|
selectedCommunityId,
|
||||||
communityName: null,
|
communityName: null,
|
||||||
|
@ -342,7 +375,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onOptionChange = (server: string, instanceId?: string | symbol) => {
|
private onOptionChange = (server: string, instanceId?: string) => {
|
||||||
// clear next batch so we don't try to load more rooms
|
// clear next batch so we don't try to load more rooms
|
||||||
this.nextBatch = null;
|
this.nextBatch = null;
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -360,6 +393,14 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
// find the five gitter ones, at which point we do not want
|
// find the five gitter ones, at which point we do not want
|
||||||
// to render all those rooms when switching back to 'all networks'.
|
// to render all those rooms when switching back to 'all networks'.
|
||||||
// Easiest to just blow away the state & re-fetch.
|
// Easiest to just blow away the state & re-fetch.
|
||||||
|
|
||||||
|
// We have to be careful here so that we don't set instanceId = "undefined"
|
||||||
|
localStorage.setItem(LAST_SERVER_KEY, server);
|
||||||
|
if (instanceId) {
|
||||||
|
localStorage.setItem(LAST_INSTANCE_KEY, instanceId);
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem(LAST_INSTANCE_KEY);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onFillRequest = (backwards: boolean) => {
|
private onFillRequest = (backwards: boolean) => {
|
||||||
|
@ -370,7 +411,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onFilterChange = (alias: string) => {
|
private onFilterChange = (alias: string) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
filterString: alias || null,
|
filterString: alias || "",
|
||||||
});
|
});
|
||||||
|
|
||||||
// don't send the request for a little bit,
|
// don't send the request for a little bit,
|
||||||
|
@ -389,7 +430,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
private onFilterClear = () => {
|
private onFilterClear = () => {
|
||||||
// update immediately
|
// update immediately
|
||||||
this.setState({
|
this.setState({
|
||||||
filterString: null,
|
filterString: "",
|
||||||
}, this.refreshRoomList);
|
}, this.refreshRoomList);
|
||||||
|
|
||||||
if (this.filterTimeout) {
|
if (this.filterTimeout) {
|
||||||
|
|
|
@ -131,7 +131,7 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case RoomListAction.ClearSearch:
|
case RoomListAction.ClearSearch:
|
||||||
this.clearInput();
|
this.clearInput();
|
||||||
defaultDispatcher.fire(Action.FocusComposer);
|
defaultDispatcher.fire(Action.FocusSendMessageComposer);
|
||||||
break;
|
break;
|
||||||
case RoomListAction.NextRoom:
|
case RoomListAction.NextRoom:
|
||||||
case RoomListAction.PrevRoom:
|
case RoomListAction.PrevRoom:
|
||||||
|
|
|
@ -118,12 +118,12 @@ export default class RoomStatusBar extends React.PureComponent {
|
||||||
this.setState({ isResending: false });
|
this.setState({ isResending: false });
|
||||||
});
|
});
|
||||||
this.setState({ isResending: true });
|
this.setState({ isResending: true });
|
||||||
dis.fire(Action.FocusComposer);
|
dis.fire(Action.FocusSendMessageComposer);
|
||||||
};
|
};
|
||||||
|
|
||||||
_onCancelAllClick = () => {
|
_onCancelAllClick = () => {
|
||||||
Resend.cancelUnsentEvents(this.props.room);
|
Resend.cancelUnsentEvents(this.props.room);
|
||||||
dis.fire(Action.FocusComposer);
|
dis.fire(Action.FocusSendMessageComposer);
|
||||||
};
|
};
|
||||||
|
|
||||||
_onRoomLocalEchoUpdated = (event, room, oldEventId, oldStatus) => {
|
_onRoomLocalEchoUpdated = (event, room, oldEventId, oldStatus) => {
|
||||||
|
|
|
@ -34,16 +34,14 @@ import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
|
||||||
import ResizeNotifier from '../../utils/ResizeNotifier';
|
import ResizeNotifier from '../../utils/ResizeNotifier';
|
||||||
import ContentMessages from '../../ContentMessages';
|
import ContentMessages from '../../ContentMessages';
|
||||||
import Modal from '../../Modal';
|
import Modal from '../../Modal';
|
||||||
import * as sdk from '../../index';
|
|
||||||
import CallHandler, { PlaceCallType } from '../../CallHandler';
|
import CallHandler, { PlaceCallType } from '../../CallHandler';
|
||||||
import dis from '../../dispatcher/dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import rateLimitedFunc from '../../ratelimitedfunc';
|
|
||||||
import * as Rooms from '../../Rooms';
|
import * as Rooms from '../../Rooms';
|
||||||
import eventSearch, { searchPagination } from '../../Searching';
|
import eventSearch, { searchPagination } from '../../Searching';
|
||||||
import MainSplit from './MainSplit';
|
import MainSplit from './MainSplit';
|
||||||
import RightPanel from './RightPanel';
|
import RightPanel from './RightPanel';
|
||||||
import RoomViewStore from '../../stores/RoomViewStore';
|
import RoomViewStore from '../../stores/RoomViewStore';
|
||||||
import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
|
import RoomScrollStateStore, { ScrollState } from '../../stores/RoomScrollStateStore';
|
||||||
import WidgetEchoStore from '../../stores/WidgetEchoStore';
|
import WidgetEchoStore from '../../stores/WidgetEchoStore';
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import { Layout } from "../../settings/Layout";
|
import { Layout } from "../../settings/Layout";
|
||||||
|
@ -64,7 +62,7 @@ import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar";
|
||||||
import AuxPanel from "../views/rooms/AuxPanel";
|
import AuxPanel from "../views/rooms/AuxPanel";
|
||||||
import RoomHeader from "../views/rooms/RoomHeader";
|
import RoomHeader from "../views/rooms/RoomHeader";
|
||||||
import { XOR } from "../../@types/common";
|
import { XOR } from "../../@types/common";
|
||||||
import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
import { IOOBData, IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
||||||
import EffectsOverlay from "../views/elements/EffectsOverlay";
|
import EffectsOverlay from "../views/elements/EffectsOverlay";
|
||||||
import { containsEmoji } from '../../effects/utils';
|
import { containsEmoji } from '../../effects/utils';
|
||||||
import { CHAT_EFFECTS } from '../../effects';
|
import { CHAT_EFFECTS } from '../../effects';
|
||||||
|
@ -82,6 +80,15 @@ import { IOpts } from "../../createRoom";
|
||||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
import UIStore from "../../stores/UIStore";
|
import UIStore from "../../stores/UIStore";
|
||||||
import EditorStateTransfer from "../../utils/EditorStateTransfer";
|
import EditorStateTransfer from "../../utils/EditorStateTransfer";
|
||||||
|
import { throttle } from "lodash";
|
||||||
|
import ErrorDialog from '../views/dialogs/ErrorDialog';
|
||||||
|
import SearchResultTile from '../views/rooms/SearchResultTile';
|
||||||
|
import Spinner from "../views/elements/Spinner";
|
||||||
|
import UploadBar from './UploadBar';
|
||||||
|
import RoomStatusBar from "./RoomStatusBar";
|
||||||
|
import MessageComposer from '../views/rooms/MessageComposer';
|
||||||
|
import JumpToBottomButton from "../views/rooms/JumpToBottomButton";
|
||||||
|
import TopUnreadMessagesBar from "../views/rooms/TopUnreadMessagesBar";
|
||||||
|
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
let debuglog = function(msg: string) {};
|
let debuglog = function(msg: string) {};
|
||||||
|
@ -94,22 +101,8 @@ if (DEBUG) {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
threepidInvite: IThreepidInvite,
|
threepidInvite: IThreepidInvite;
|
||||||
|
oobData?: IOOBData;
|
||||||
// Any data about the room that would normally come from the homeserver
|
|
||||||
// but has been passed out-of-band, eg. the room name and avatar URL
|
|
||||||
// from an email invite (a workaround for the fact that we can't
|
|
||||||
// get this information from the HS using an email invite).
|
|
||||||
// Fields:
|
|
||||||
// * name (string) The room's name
|
|
||||||
// * avatarUrl (string) The mxc:// avatar URL for the room
|
|
||||||
// * inviterName (string) The display name of the person who
|
|
||||||
// * invited us to the room
|
|
||||||
oobData?: {
|
|
||||||
name?: string;
|
|
||||||
avatarUrl?: string;
|
|
||||||
inviterName?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
justCreatedOpts?: IOpts;
|
justCreatedOpts?: IOpts;
|
||||||
|
@ -680,8 +673,8 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// cancel any pending calls to the rate_limited_funcs
|
// cancel any pending calls to the throttled updated
|
||||||
this.updateRoomMembers.cancelPendingCall();
|
this.updateRoomMembers.cancel();
|
||||||
|
|
||||||
for (const watcher of this.settingWatchers) {
|
for (const watcher of this.settingWatchers) {
|
||||||
SettingsStore.unwatchSetting(watcher);
|
SettingsStore.unwatchSetting(watcher);
|
||||||
|
@ -830,17 +823,16 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
case Action.ComposerInsert: {
|
case Action.ComposerInsert: {
|
||||||
// re-dispatch to the correct composer
|
// re-dispatch to the correct composer
|
||||||
if (this.state.editState) {
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
...payload,
|
...payload,
|
||||||
action: "edit_composer_insert",
|
action: this.state.editState ? "edit_composer_insert" : "send_composer_insert",
|
||||||
});
|
|
||||||
} else {
|
|
||||||
dis.dispatch({
|
|
||||||
...payload,
|
|
||||||
action: "send_composer_insert",
|
|
||||||
});
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case Action.FocusAComposer: {
|
||||||
|
// re-dispatch to the correct composer
|
||||||
|
dis.fire(this.state.editState ? Action.FocusEditMessageComposer : Action.FocusSendMessageComposer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1059,11 +1051,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateTint() {
|
|
||||||
const room = this.state.room;
|
|
||||||
if (!room) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
private onAccountData = (event: MatrixEvent) => {
|
private onAccountData = (event: MatrixEvent) => {
|
||||||
const type = event.getType();
|
const type = event.getType();
|
||||||
if ((type === "org.matrix.preview_urls" || type === "im.vector.web.settings") && this.state.room) {
|
if ((type === "org.matrix.preview_urls" || type === "im.vector.web.settings") && this.state.room) {
|
||||||
|
@ -1102,7 +1089,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateRoomMembers(member);
|
this.updateRoomMembers();
|
||||||
};
|
};
|
||||||
|
|
||||||
private onMyMembership = (room: Room, membership: string, oldMembership: string) => {
|
private onMyMembership = (room: Room, membership: string, oldMembership: string) => {
|
||||||
|
@ -1124,10 +1111,10 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// rate limited because a power level change will emit an event for every member in the room.
|
// rate limited because a power level change will emit an event for every member in the room.
|
||||||
private updateRoomMembers = rateLimitedFunc(() => {
|
private updateRoomMembers = throttle(() => {
|
||||||
this.updateDMState();
|
this.updateDMState();
|
||||||
this.updateE2EStatus(this.state.room);
|
this.updateE2EStatus(this.state.room);
|
||||||
}, 500);
|
}, 500, { leading: true, trailing: true });
|
||||||
|
|
||||||
private checkDesktopNotifications() {
|
private checkDesktopNotifications() {
|
||||||
const memberCount = this.state.room.getJoinedMemberCount() + this.state.room.getInvitedMemberCount();
|
const memberCount = this.state.room.getJoinedMemberCount() + this.state.room.getInvitedMemberCount();
|
||||||
|
@ -1263,7 +1250,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
ContentMessages.sharedInstance().sendContentListToRoom(
|
ContentMessages.sharedInstance().sendContentListToRoom(
|
||||||
ev.dataTransfer.files, this.state.room.roomId, this.context,
|
ev.dataTransfer.files, this.state.room.roomId, this.context,
|
||||||
);
|
);
|
||||||
dis.fire(Action.FocusComposer);
|
dis.fire(Action.FocusSendMessageComposer);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
draggingFile: false,
|
draggingFile: false,
|
||||||
|
@ -1271,7 +1258,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private injectSticker(url, info, text) {
|
private injectSticker(url: string, info: object, text: string) {
|
||||||
if (this.context.isGuest()) {
|
if (this.context.isGuest()) {
|
||||||
dis.dispatch({ action: 'require_registration' });
|
dis.dispatch({ action: 'require_registration' });
|
||||||
return;
|
return;
|
||||||
|
@ -1352,7 +1339,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
searchResults: results,
|
searchResults: results,
|
||||||
});
|
});
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
console.error("Search failed", error);
|
console.error("Search failed", error);
|
||||||
Modal.createTrackedDialog('Search failed', '', ErrorDialog, {
|
Modal.createTrackedDialog('Search failed', '', ErrorDialog, {
|
||||||
title: _t("Search failed"),
|
title: _t("Search failed"),
|
||||||
|
@ -1368,9 +1354,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSearchResultTiles() {
|
private getSearchResultTiles() {
|
||||||
const SearchResultTile = sdk.getComponent('rooms.SearchResultTile');
|
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
|
||||||
|
|
||||||
// XXX: todo: merge overlapping results somehow?
|
// XXX: todo: merge overlapping results somehow?
|
||||||
// XXX: why doesn't searching on name work?
|
// XXX: why doesn't searching on name work?
|
||||||
|
|
||||||
|
@ -1470,13 +1453,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onLeaveClick = () => {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'leave_room',
|
|
||||||
room_id: this.state.room.roomId,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
private onForgetClick = () => {
|
private onForgetClick = () => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'forget_room',
|
action: 'forget_room',
|
||||||
|
@ -1497,7 +1473,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
console.error("Failed to reject invite: %s", error);
|
console.error("Failed to reject invite: %s", error);
|
||||||
|
|
||||||
const msg = error.message ? error.message : JSON.stringify(error);
|
const msg = error.message ? error.message : JSON.stringify(error);
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Failed to reject invite', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to reject invite', '', ErrorDialog, {
|
||||||
title: _t("Failed to reject invite"),
|
title: _t("Failed to reject invite"),
|
||||||
description: msg,
|
description: msg,
|
||||||
|
@ -1531,7 +1506,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
console.error("Failed to reject invite: %s", error);
|
console.error("Failed to reject invite: %s", error);
|
||||||
|
|
||||||
const msg = error.message ? error.message : JSON.stringify(error);
|
const msg = error.message ? error.message : JSON.stringify(error);
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Failed to reject invite', '', ErrorDialog, {
|
Modal.createTrackedDialog('Failed to reject invite', '', ErrorDialog, {
|
||||||
title: _t("Failed to reject invite"),
|
title: _t("Failed to reject invite"),
|
||||||
description: msg,
|
description: msg,
|
||||||
|
@ -1578,7 +1552,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
} else {
|
} else {
|
||||||
// Otherwise we have to jump manually
|
// Otherwise we have to jump manually
|
||||||
this.messagePanel.jumpToLiveTimeline();
|
this.messagePanel.jumpToLiveTimeline();
|
||||||
dis.fire(Action.FocusComposer);
|
dis.fire(Action.FocusSendMessageComposer);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1608,7 +1582,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
// get the current scroll position of the room, so that it can be
|
// get the current scroll position of the room, so that it can be
|
||||||
// restored when we switch back to it.
|
// restored when we switch back to it.
|
||||||
//
|
//
|
||||||
private getScrollState() {
|
private getScrollState(): ScrollState {
|
||||||
const messagePanel = this.messagePanel;
|
const messagePanel = this.messagePanel;
|
||||||
if (!messagePanel) return null;
|
if (!messagePanel) return null;
|
||||||
|
|
||||||
|
@ -1710,10 +1684,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
// otherwise react calls it with null on each update.
|
// otherwise react calls it with null on each update.
|
||||||
private gatherTimelinePanelRef = r => {
|
private gatherTimelinePanelRef = r => {
|
||||||
this.messagePanel = r;
|
this.messagePanel = r;
|
||||||
if (r) {
|
|
||||||
console.log("updateTint from RoomView.gatherTimelinePanelRef");
|
|
||||||
this.updateTint();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private getOldRoom() {
|
private getOldRoom() {
|
||||||
|
@ -1869,10 +1839,8 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
let isStatusAreaExpanded = true;
|
let isStatusAreaExpanded = true;
|
||||||
|
|
||||||
if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) {
|
if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) {
|
||||||
const UploadBar = sdk.getComponent('structures.UploadBar');
|
|
||||||
statusBar = <UploadBar room={this.state.room} />;
|
statusBar = <UploadBar room={this.state.room} />;
|
||||||
} else if (!this.state.searchResults) {
|
} else if (!this.state.searchResults) {
|
||||||
const RoomStatusBar = sdk.getComponent('structures.RoomStatusBar');
|
|
||||||
isStatusAreaExpanded = this.state.statusBarVisible;
|
isStatusAreaExpanded = this.state.statusBarVisible;
|
||||||
statusBar = <RoomStatusBar
|
statusBar = <RoomStatusBar
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
|
@ -1978,12 +1946,9 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
myMembership === 'join' && !this.state.searchResults
|
myMembership === 'join' && !this.state.searchResults
|
||||||
);
|
);
|
||||||
if (canSpeak) {
|
if (canSpeak) {
|
||||||
const MessageComposer = sdk.getComponent('rooms.MessageComposer');
|
|
||||||
messageComposer =
|
messageComposer =
|
||||||
<MessageComposer
|
<MessageComposer
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
callState={this.state.callState}
|
|
||||||
showApps={this.state.showApps}
|
|
||||||
e2eStatus={this.state.e2eStatus}
|
e2eStatus={this.state.e2eStatus}
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
replyToEvent={this.state.replyToEvent}
|
replyToEvent={this.state.replyToEvent}
|
||||||
|
@ -2069,7 +2034,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
let topUnreadMessagesBar = null;
|
let topUnreadMessagesBar = null;
|
||||||
// Do not show TopUnreadMessagesBar if we have search results showing, it makes no sense
|
// Do not show TopUnreadMessagesBar if we have search results showing, it makes no sense
|
||||||
if (this.state.showTopUnreadMessagesBar && !this.state.searchResults) {
|
if (this.state.showTopUnreadMessagesBar && !this.state.searchResults) {
|
||||||
const TopUnreadMessagesBar = sdk.getComponent('rooms.TopUnreadMessagesBar');
|
|
||||||
topUnreadMessagesBar = (
|
topUnreadMessagesBar = (
|
||||||
<TopUnreadMessagesBar onScrollUpClick={this.jumpToReadMarker} onCloseClick={this.forgetReadMarker} />
|
<TopUnreadMessagesBar onScrollUpClick={this.jumpToReadMarker} onCloseClick={this.forgetReadMarker} />
|
||||||
);
|
);
|
||||||
|
@ -2077,7 +2041,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
let jumpToBottom;
|
let jumpToBottom;
|
||||||
// Do not show JumpToBottomButton if we have search results showing, it makes no sense
|
// Do not show JumpToBottomButton if we have search results showing, it makes no sense
|
||||||
if (!this.state.atEndOfLiveTimeline && !this.state.searchResults) {
|
if (!this.state.atEndOfLiveTimeline && !this.state.searchResults) {
|
||||||
const JumpToBottomButton = sdk.getComponent('rooms.JumpToBottomButton');
|
|
||||||
jumpToBottom = (<JumpToBottomButton
|
jumpToBottom = (<JumpToBottomButton
|
||||||
highlight={this.state.room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0}
|
highlight={this.state.room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0}
|
||||||
numUnreadMessages={this.state.numUnreadMessages}
|
numUnreadMessages={this.state.numUnreadMessages}
|
||||||
|
@ -2120,7 +2083,6 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
onSearchClick={this.onSearchClick}
|
onSearchClick={this.onSearchClick}
|
||||||
onSettingsClick={this.onSettingsClick}
|
onSettingsClick={this.onSettingsClick}
|
||||||
onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
|
onForgetClick={(myMembership === "leave") ? this.onForgetClick : null}
|
||||||
onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null}
|
|
||||||
e2eStatus={this.state.e2eStatus}
|
e2eStatus={this.state.e2eStatus}
|
||||||
onAppsClick={this.state.hasPinnedWidgets ? this.onAppsClick : null}
|
onAppsClick={this.state.hasPinnedWidgets ? this.onAppsClick : null}
|
||||||
appsShown={this.state.showApps}
|
appsShown={this.state.showApps}
|
||||||
|
|
|
@ -58,7 +58,7 @@ export interface ISpaceSummaryRoom {
|
||||||
avatar_url?: string;
|
avatar_url?: string;
|
||||||
guest_can_join: boolean;
|
guest_can_join: boolean;
|
||||||
name?: string;
|
name?: string;
|
||||||
num_joined_members: number
|
num_joined_members: number;
|
||||||
room_id: string;
|
room_id: string;
|
||||||
topic?: string;
|
topic?: string;
|
||||||
world_readable: boolean;
|
world_readable: boolean;
|
||||||
|
|
|
@ -18,9 +18,9 @@ limitations under the License.
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import * as sdk from "../../index";
|
|
||||||
import AutoHideScrollbar from './AutoHideScrollbar';
|
import AutoHideScrollbar from './AutoHideScrollbar';
|
||||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
|
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a tab for the TabbedView.
|
* Represents a tab for the TabbedView.
|
||||||
|
@ -82,8 +82,6 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderTabLabel(tab: Tab) {
|
private _renderTabLabel(tab: Tab) {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
|
|
||||||
let classes = "mx_TabbedView_tabLabel ";
|
let classes = "mx_TabbedView_tabLabel ";
|
||||||
|
|
||||||
const idx = this.props.tabs.indexOf(tab);
|
const idx = this.props.tabs.indexOf(tab);
|
||||||
|
|
|
@ -16,11 +16,13 @@ limitations under the License.
|
||||||
|
|
||||||
import React, { createRef, ReactNode, SyntheticEvent } from 'react';
|
import React, { createRef, ReactNode, SyntheticEvent } from 'react';
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { TimelineSet } from "matrix-js-sdk/src/models/event-timeline-set";
|
import { EventTimelineSet } from "matrix-js-sdk/src/models/event-timeline-set";
|
||||||
import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
|
import { Direction, EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
|
||||||
import { TimelineWindow } from "matrix-js-sdk/src/timeline-window";
|
import { TimelineWindow } from "matrix-js-sdk/src/timeline-window";
|
||||||
|
import { EventType, RelationType } from 'matrix-js-sdk/src/@types/event';
|
||||||
|
import { SyncState } from 'matrix-js-sdk/src/sync.api';
|
||||||
|
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import { Layout } from "../../settings/Layout";
|
import { Layout } from "../../settings/Layout";
|
||||||
|
@ -30,7 +32,6 @@ import RoomContext from "../../contexts/RoomContext";
|
||||||
import UserActivity from "../../UserActivity";
|
import UserActivity from "../../UserActivity";
|
||||||
import Modal from "../../Modal";
|
import Modal from "../../Modal";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import * as sdk from "../../index";
|
|
||||||
import { Key } from '../../Keyboard';
|
import { Key } from '../../Keyboard';
|
||||||
import Timer from '../../utils/Timer';
|
import Timer from '../../utils/Timer';
|
||||||
import shouldHideEvent from '../../shouldHideEvent';
|
import shouldHideEvent from '../../shouldHideEvent';
|
||||||
|
@ -39,14 +40,13 @@ import { UIFeature } from "../../settings/UIFeature";
|
||||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
import { arrayFastClone } from "../../utils/arrays";
|
import { arrayFastClone } from "../../utils/arrays";
|
||||||
import MessagePanel from "./MessagePanel";
|
import MessagePanel from "./MessagePanel";
|
||||||
import { SyncState } from 'matrix-js-sdk/src/sync.api';
|
|
||||||
import { IScrollState } from "./ScrollPanel";
|
import { IScrollState } from "./ScrollPanel";
|
||||||
import { ActionPayload } from "../../dispatcher/payloads";
|
import { ActionPayload } from "../../dispatcher/payloads";
|
||||||
import { EventType } from 'matrix-js-sdk/src/@types/event';
|
|
||||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||||
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
|
import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
|
||||||
import Spinner from "../views/elements/Spinner";
|
import Spinner from "../views/elements/Spinner";
|
||||||
import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
||||||
|
import ErrorDialog from '../views/dialogs/ErrorDialog';
|
||||||
|
|
||||||
const PAGINATE_SIZE = 20;
|
const PAGINATE_SIZE = 20;
|
||||||
const INITIAL_SIZE = 20;
|
const INITIAL_SIZE = 20;
|
||||||
|
@ -65,7 +65,7 @@ interface IProps {
|
||||||
// representing. This may or may not have a room, depending on what it's
|
// representing. This may or may not have a room, depending on what it's
|
||||||
// a timeline representing. If it has a room, we maintain RRs etc for
|
// a timeline representing. If it has a room, we maintain RRs etc for
|
||||||
// that room.
|
// that room.
|
||||||
timelineSet: TimelineSet;
|
timelineSet: EventTimelineSet;
|
||||||
showReadReceipts?: boolean;
|
showReadReceipts?: boolean;
|
||||||
// Enable managing RRs and RMs. These require the timelineSet to have a room.
|
// Enable managing RRs and RMs. These require the timelineSet to have a room.
|
||||||
manageReadReceipts?: boolean;
|
manageReadReceipts?: boolean;
|
||||||
|
@ -125,7 +125,7 @@ interface IProps {
|
||||||
onReadMarkerUpdated?(): void;
|
onReadMarkerUpdated?(): void;
|
||||||
|
|
||||||
// callback which is called when we wish to paginate the timeline window.
|
// callback which is called when we wish to paginate the timeline window.
|
||||||
onPaginationRequest?(timelineWindow: TimelineWindow, direction: string, size: number): Promise<boolean>,
|
onPaginationRequest?(timelineWindow: TimelineWindow, direction: string, size: number): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -388,7 +388,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onPaginationRequest = (
|
private onPaginationRequest = (
|
||||||
timelineWindow: TimelineWindow,
|
timelineWindow: TimelineWindow,
|
||||||
direction: string,
|
direction: Direction,
|
||||||
size: number,
|
size: number,
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
if (this.props.onPaginationRequest) {
|
if (this.props.onPaginationRequest) {
|
||||||
|
@ -579,7 +579,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRoomTimelineReset = (room: Room, timelineSet: TimelineSet): void => {
|
private onRoomTimelineReset = (room: Room, timelineSet: EventTimelineSet): void => {
|
||||||
if (timelineSet !== this.props.timelineSet) return;
|
if (timelineSet !== this.props.timelineSet) return;
|
||||||
|
|
||||||
if (this.messagePanel.current && this.messagePanel.current.isAtBottom()) {
|
if (this.messagePanel.current && this.messagePanel.current.isAtBottom()) {
|
||||||
|
@ -792,8 +792,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
// that sending an RR for the latest message will set our notif counter
|
// that sending an RR for the latest message will set our notif counter
|
||||||
// to zero: it may not do this if we send an RR for somewhere before the end.
|
// to zero: it may not do this if we send an RR for somewhere before the end.
|
||||||
if (this.isAtEndOfLiveTimeline()) {
|
if (this.isAtEndOfLiveTimeline()) {
|
||||||
this.props.timelineSet.room.setUnreadNotificationCount('total', 0);
|
this.props.timelineSet.room.setUnreadNotificationCount(NotificationCountType.Total, 0);
|
||||||
this.props.timelineSet.room.setUnreadNotificationCount('highlight', 0);
|
this.props.timelineSet.room.setUnreadNotificationCount(NotificationCountType.Highlight, 0);
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'on_room_read',
|
action: 'on_room_read',
|
||||||
roomId: this.props.timelineSet.room.roomId,
|
roomId: this.props.timelineSet.room.roomId,
|
||||||
|
@ -1096,7 +1096,6 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
console.error(
|
console.error(
|
||||||
`Error loading timeline panel at ${eventId}: ${error}`,
|
`Error loading timeline panel at ${eventId}: ${error}`,
|
||||||
);
|
);
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
|
|
||||||
let onFinished;
|
let onFinished;
|
||||||
|
|
||||||
|
@ -1417,7 +1416,11 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRelationsForEvent = (...args) => this.props.timelineSet.getRelationsForEvent(...args);
|
private getRelationsForEvent = (
|
||||||
|
eventId: string,
|
||||||
|
relationType: RelationType,
|
||||||
|
eventType: EventType | string,
|
||||||
|
) => this.props.timelineSet.getRelationsForEvent(eventId, relationType, eventType);
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// just show a spinner while the timeline loads.
|
// just show a spinner while the timeline loads.
|
||||||
|
|
|
@ -26,6 +26,7 @@ import ProgressBar from "../views/elements/ProgressBar";
|
||||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
import { IUpload } from "../../models/IUpload";
|
import { IUpload } from "../../models/IUpload";
|
||||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -38,6 +39,8 @@ interface IState {
|
||||||
|
|
||||||
@replaceableComponent("structures.UploadBar")
|
@replaceableComponent("structures.UploadBar")
|
||||||
export default class UploadBar extends React.Component<IProps, IState> {
|
export default class UploadBar extends React.Component<IProps, IState> {
|
||||||
|
static contextType = MatrixClientContext;
|
||||||
|
|
||||||
private dispatcherRef: string;
|
private dispatcherRef: string;
|
||||||
private mounted: boolean;
|
private mounted: boolean;
|
||||||
|
|
||||||
|
@ -82,7 +85,7 @@ export default class UploadBar extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onCancelClick = (ev) => {
|
private onCancelClick = (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ContentMessages.sharedInstance().cancelUpload(this.state.currentUpload.promise);
|
ContentMessages.sharedInstance().cancelUpload(this.state.currentUpload.promise, this.context);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -15,39 +15,42 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
|
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
|
||||||
import SetupEncryptionBody from "./SetupEncryptionBody";
|
import SetupEncryptionBody from "./SetupEncryptionBody";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
@replaceableComponent("structures.auth.CompleteSecurity")
|
interface IProps {
|
||||||
export default class CompleteSecurity extends React.Component {
|
onFinished: () => void;
|
||||||
static propTypes = {
|
}
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
interface IState {
|
||||||
super();
|
phase: Phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.auth.CompleteSecurity")
|
||||||
|
export default class CompleteSecurity extends React.Component<IProps, IState> {
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
store.on("update", this._onStoreUpdate);
|
store.on("update", this.onStoreUpdate);
|
||||||
store.start();
|
store.start();
|
||||||
this.state = { phase: store.phase };
|
this.state = { phase: store.phase };
|
||||||
}
|
}
|
||||||
|
|
||||||
_onStoreUpdate = () => {
|
private onStoreUpdate = (): void => {
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
this.setState({ phase: store.phase });
|
this.setState({ phase: store.phase });
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount(): void {
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
store.off("update", this._onStoreUpdate);
|
store.off("update", this.onStoreUpdate);
|
||||||
store.stop();
|
store.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
public render() {
|
||||||
const AuthPage = sdk.getComponent("auth.AuthPage");
|
const AuthPage = sdk.getComponent("auth.AuthPage");
|
||||||
const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody");
|
const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody");
|
||||||
const { phase } = this.state;
|
const { phase } = this.state;
|
|
@ -15,20 +15,19 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import AuthPage from '../../views/auth/AuthPage';
|
import AuthPage from '../../views/auth/AuthPage';
|
||||||
import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody';
|
import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody';
|
||||||
import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog';
|
import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
@replaceableComponent("structures.auth.E2eSetup")
|
interface IProps {
|
||||||
export default class E2eSetup extends React.Component {
|
onFinished: () => void;
|
||||||
static propTypes = {
|
accountPassword?: string;
|
||||||
onFinished: PropTypes.func.isRequired,
|
tokenLogin?: boolean;
|
||||||
accountPassword: PropTypes.string,
|
}
|
||||||
tokenLogin: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
@replaceableComponent("structures.auth.E2eSetup")
|
||||||
|
export default class E2eSetup extends React.Component<IProps> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<AuthPage>
|
<AuthPage>
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { _t, _td } from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
|
@ -31,27 +30,50 @@ import PassphraseField from '../../views/auth/PassphraseField';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm';
|
import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm';
|
||||||
|
|
||||||
// Phases
|
import { IValidationResult } from "../../views/elements/Validation";
|
||||||
|
|
||||||
|
enum Phase {
|
||||||
// Show the forgot password inputs
|
// Show the forgot password inputs
|
||||||
const PHASE_FORGOT = 1;
|
Forgot = 1,
|
||||||
// Email is in the process of being sent
|
// Email is in the process of being sent
|
||||||
const PHASE_SENDING_EMAIL = 2;
|
SendingEmail = 2,
|
||||||
// Email has been sent
|
// Email has been sent
|
||||||
const PHASE_EMAIL_SENT = 3;
|
EmailSent = 3,
|
||||||
// User has clicked the link in email and completed reset
|
// User has clicked the link in email and completed reset
|
||||||
const PHASE_DONE = 4;
|
Done = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
serverConfig: ValidatedServerConfig;
|
||||||
|
onServerConfigChange: (serverConfig: ValidatedServerConfig) => void;
|
||||||
|
onLoginClick?: () => void;
|
||||||
|
onComplete: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
phase: Phase;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
password2: string;
|
||||||
|
errorText: string;
|
||||||
|
|
||||||
|
// We perform liveliness checks later, but for now suppress the errors.
|
||||||
|
// We also track the server dead errors independently of the regular errors so
|
||||||
|
// that we can render it differently, and override any other error the user may
|
||||||
|
// be seeing.
|
||||||
|
serverIsAlive: boolean;
|
||||||
|
serverErrorIsFatal: boolean;
|
||||||
|
serverDeadError: string;
|
||||||
|
|
||||||
|
passwordFieldValid: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
@replaceableComponent("structures.auth.ForgotPassword")
|
@replaceableComponent("structures.auth.ForgotPassword")
|
||||||
export default class ForgotPassword extends React.Component {
|
export default class ForgotPassword extends React.Component<IProps, IState> {
|
||||||
static propTypes = {
|
private reset: PasswordReset;
|
||||||
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
|
|
||||||
onServerConfigChange: PropTypes.func.isRequired,
|
|
||||||
onLoginClick: PropTypes.func,
|
|
||||||
onComplete: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
phase: PHASE_FORGOT,
|
phase: Phase.Forgot,
|
||||||
email: "",
|
email: "",
|
||||||
password: "",
|
password: "",
|
||||||
password2: "",
|
password2: "",
|
||||||
|
@ -64,30 +86,31 @@ export default class ForgotPassword extends React.Component {
|
||||||
serverIsAlive: true,
|
serverIsAlive: true,
|
||||||
serverErrorIsFatal: false,
|
serverErrorIsFatal: false,
|
||||||
serverDeadError: "",
|
serverDeadError: "",
|
||||||
|
passwordFieldValid: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
CountlyAnalytics.instance.track("onboarding_forgot_password_begin");
|
CountlyAnalytics.instance.track("onboarding_forgot_password_begin");
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
public componentDidMount() {
|
||||||
this.reset = null;
|
this.reset = null;
|
||||||
this._checkServerLiveliness(this.props.serverConfig);
|
this.checkServerLiveliness(this.props.serverConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
UNSAFE_componentWillReceiveProps(newProps) {
|
public UNSAFE_componentWillReceiveProps(newProps: IProps): void {
|
||||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
||||||
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
||||||
|
|
||||||
// Do a liveliness check on the new URLs
|
// Do a liveliness check on the new URLs
|
||||||
this._checkServerLiveliness(newProps.serverConfig);
|
this.checkServerLiveliness(newProps.serverConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _checkServerLiveliness(serverConfig) {
|
private async checkServerLiveliness(serverConfig): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(
|
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(
|
||||||
serverConfig.hsUrl,
|
serverConfig.hsUrl,
|
||||||
|
@ -98,28 +121,28 @@ export default class ForgotPassword extends React.Component {
|
||||||
serverIsAlive: true,
|
serverIsAlive: true,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setState(AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password"));
|
this.setState(AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password") as IState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
submitPasswordReset(email, password) {
|
public submitPasswordReset(email: string, password: string): void {
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: PHASE_SENDING_EMAIL,
|
phase: Phase.SendingEmail,
|
||||||
});
|
});
|
||||||
this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl);
|
this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl);
|
||||||
this.reset.resetPassword(email, password).then(() => {
|
this.reset.resetPassword(email, password).then(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: PHASE_EMAIL_SENT,
|
phase: Phase.EmailSent,
|
||||||
});
|
});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
this.showErrorDialog(_t('Failed to send email') + ": " + err.message);
|
this.showErrorDialog(_t('Failed to send email') + ": " + err.message);
|
||||||
this.setState({
|
this.setState({
|
||||||
phase: PHASE_FORGOT,
|
phase: Phase.Forgot,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onVerify = async ev => {
|
private onVerify = async (ev: React.MouseEvent): Promise<void> => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
if (!this.reset) {
|
if (!this.reset) {
|
||||||
console.error("onVerify called before submitPasswordReset!");
|
console.error("onVerify called before submitPasswordReset!");
|
||||||
|
@ -127,17 +150,17 @@ export default class ForgotPassword extends React.Component {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.reset.checkEmailLinkClicked();
|
await this.reset.checkEmailLinkClicked();
|
||||||
this.setState({ phase: PHASE_DONE });
|
this.setState({ phase: Phase.Done });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.showErrorDialog(err.message);
|
this.showErrorDialog(err.message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onSubmitForm = async ev => {
|
private onSubmitForm = async (ev: React.FormEvent): Promise<void> => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
// refresh the server errors, just in case the server came back online
|
// refresh the server errors, just in case the server came back online
|
||||||
await this._checkServerLiveliness(this.props.serverConfig);
|
await this.checkServerLiveliness(this.props.serverConfig);
|
||||||
|
|
||||||
await this['password_field'].validate({ allowEmpty: false });
|
await this['password_field'].validate({ allowEmpty: false });
|
||||||
|
|
||||||
|
@ -172,27 +195,27 @@ export default class ForgotPassword extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onInputChanged = (stateKey, ev) => {
|
private onInputChanged = (stateKey: string, ev: React.FormEvent<HTMLInputElement>) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
[stateKey]: ev.target.value,
|
[stateKey]: ev.currentTarget.value,
|
||||||
});
|
} as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
onLoginClick = ev => {
|
private onLoginClick = (ev: React.MouseEvent): void => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.props.onLoginClick();
|
this.props.onLoginClick();
|
||||||
};
|
};
|
||||||
|
|
||||||
showErrorDialog(body, title) {
|
public showErrorDialog(description: string, title?: string) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, {
|
Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, {
|
||||||
title: title,
|
title,
|
||||||
description: body,
|
description,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onPasswordValidate(result) {
|
private onPasswordValidate(result: IValidationResult) {
|
||||||
this.setState({
|
this.setState({
|
||||||
passwordFieldValid: result.valid,
|
passwordFieldValid: result.valid,
|
||||||
});
|
});
|
||||||
|
@ -316,16 +339,16 @@ export default class ForgotPassword extends React.Component {
|
||||||
|
|
||||||
let resetPasswordJsx;
|
let resetPasswordJsx;
|
||||||
switch (this.state.phase) {
|
switch (this.state.phase) {
|
||||||
case PHASE_FORGOT:
|
case Phase.Forgot:
|
||||||
resetPasswordJsx = this.renderForgot();
|
resetPasswordJsx = this.renderForgot();
|
||||||
break;
|
break;
|
||||||
case PHASE_SENDING_EMAIL:
|
case Phase.SendingEmail:
|
||||||
resetPasswordJsx = this.renderSendingEmail();
|
resetPasswordJsx = this.renderSendingEmail();
|
||||||
break;
|
break;
|
||||||
case PHASE_EMAIL_SENT:
|
case Phase.EmailSent:
|
||||||
resetPasswordJsx = this.renderEmailSent();
|
resetPasswordJsx = this.renderEmailSent();
|
||||||
break;
|
break;
|
||||||
case PHASE_DONE:
|
case Phase.Done:
|
||||||
resetPasswordJsx = this.renderDone();
|
resetPasswordJsx = this.renderDone();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
|
@ -18,7 +18,6 @@ import React, { ReactNode } from 'react';
|
||||||
import { MatrixError } from "matrix-js-sdk/src/http-api";
|
import { MatrixError } from "matrix-js-sdk/src/http-api";
|
||||||
|
|
||||||
import { _t, _td } from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import Login, { ISSOFlow, LoginFlow } from '../../../Login';
|
import Login, { ISSOFlow, LoginFlow } from '../../../Login';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
|
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
|
||||||
|
@ -36,6 +35,8 @@ import Spinner from "../../views/elements/Spinner";
|
||||||
import SSOButtons from "../../views/elements/SSOButtons";
|
import SSOButtons from "../../views/elements/SSOButtons";
|
||||||
import ServerPicker from "../../views/elements/ServerPicker";
|
import ServerPicker from "../../views/elements/ServerPicker";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import AuthBody from "../../views/auth/AuthBody";
|
||||||
|
import AuthHeader from "../../views/auth/AuthHeader";
|
||||||
|
|
||||||
// These are used in several places, and come from the js-sdk's autodiscovery
|
// These are used in several places, and come from the js-sdk's autodiscovery
|
||||||
// stuff. We define them here so that they'll be picked up by i18n.
|
// stuff. We define them here so that they'll be picked up by i18n.
|
||||||
|
@ -541,8 +542,6 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const AuthHeader = sdk.getComponent("auth.AuthHeader");
|
|
||||||
const AuthBody = sdk.getComponent("auth.AuthBody");
|
|
||||||
const loader = this.isBusy() && !this.state.busyLoggingIn ?
|
const loader = this.isBusy() && !this.state.busyLoggingIn ?
|
||||||
<div className="mx_Login_loader"><Spinner /></div> : null;
|
<div className="mx_Login_loader"><Spinner /></div> : null;
|
||||||
|
|
||||||
|
|
|
@ -18,19 +18,24 @@ import { createClient } from 'matrix-js-sdk/src/matrix';
|
||||||
import React, { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import { _t, _td } from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
|
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
|
||||||
import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils";
|
import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import * as Lifecycle from '../../../Lifecycle';
|
import * as Lifecycle from '../../../Lifecycle';
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { IMatrixClientCreds, MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import AuthPage from "../../views/auth/AuthPage";
|
import AuthPage from "../../views/auth/AuthPage";
|
||||||
import Login, { ISSOFlow } from "../../../Login";
|
import Login, { ISSOFlow } from "../../../Login";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import SSOButtons from "../../views/elements/SSOButtons";
|
import SSOButtons from "../../views/elements/SSOButtons";
|
||||||
import ServerPicker from '../../views/elements/ServerPicker';
|
import ServerPicker from '../../views/elements/ServerPicker';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import RegistrationForm from '../../views/auth/RegistrationForm';
|
||||||
|
import AccessibleButton from '../../views/elements/AccessibleButton';
|
||||||
|
import AuthBody from "../../views/auth/AuthBody";
|
||||||
|
import AuthHeader from "../../views/auth/AuthHeader";
|
||||||
|
import InteractiveAuth from "../InteractiveAuth";
|
||||||
|
import Spinner from "../../views/elements/Spinner";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
serverConfig: ValidatedServerConfig;
|
serverConfig: ValidatedServerConfig;
|
||||||
|
@ -47,13 +52,7 @@ interface IProps {
|
||||||
// - The user's password, if available and applicable (may be cached in memory
|
// - The user's password, if available and applicable (may be cached in memory
|
||||||
// for a short time so the user is not required to re-enter their password
|
// for a short time so the user is not required to re-enter their password
|
||||||
// for operations like uploading cross-signing keys).
|
// for operations like uploading cross-signing keys).
|
||||||
onLoggedIn(params: {
|
onLoggedIn(params: IMatrixClientCreds, password: string): void;
|
||||||
userId: string;
|
|
||||||
deviceId: string
|
|
||||||
homeserverUrl: string;
|
|
||||||
identityServerUrl?: string;
|
|
||||||
accessToken: string;
|
|
||||||
}, password: string): void;
|
|
||||||
makeRegistrationUrl(params: {
|
makeRegistrationUrl(params: {
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
client_secret: string;
|
client_secret: string;
|
||||||
|
@ -246,7 +245,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFormSubmit = formVals => {
|
private onFormSubmit = async (formVals): Promise<void> => {
|
||||||
this.setState({
|
this.setState({
|
||||||
errorText: "",
|
errorText: "",
|
||||||
busy: true,
|
busy: true,
|
||||||
|
@ -442,10 +441,6 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderRegisterComponent() {
|
private renderRegisterComponent() {
|
||||||
const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth');
|
|
||||||
const Spinner = sdk.getComponent('elements.Spinner');
|
|
||||||
const RegistrationForm = sdk.getComponent('auth.RegistrationForm');
|
|
||||||
|
|
||||||
if (this.state.matrixClient && this.state.doingUIAuth) {
|
if (this.state.matrixClient && this.state.doingUIAuth) {
|
||||||
return <InteractiveAuth
|
return <InteractiveAuth
|
||||||
matrixClient={this.state.matrixClient}
|
matrixClient={this.state.matrixClient}
|
||||||
|
@ -516,10 +511,6 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const AuthHeader = sdk.getComponent('auth.AuthHeader');
|
|
||||||
const AuthBody = sdk.getComponent("auth.AuthBody");
|
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
|
|
||||||
let errorText;
|
let errorText;
|
||||||
const err = this.state.errorText;
|
const err = this.state.errorText;
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,33 +15,43 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import VerificationRequestDialog from '../../views/dialogs/VerificationRequestDialog';
|
import VerificationRequestDialog from '../../views/dialogs/VerificationRequestDialog';
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
|
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import { ISecretStorageKeyInfo } from 'matrix-js-sdk/src/crypto/api';
|
||||||
|
import EncryptionPanel from "../../views/right_panel/EncryptionPanel";
|
||||||
|
import AccessibleButton from '../../views/elements/AccessibleButton';
|
||||||
|
import Spinner from '../../views/elements/Spinner';
|
||||||
|
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
||||||
|
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||||
|
|
||||||
function keyHasPassphrase(keyInfo) {
|
function keyHasPassphrase(keyInfo: ISecretStorageKeyInfo): boolean {
|
||||||
return (
|
return Boolean(
|
||||||
keyInfo.passphrase &&
|
keyInfo.passphrase &&
|
||||||
keyInfo.passphrase.salt &&
|
keyInfo.passphrase.salt &&
|
||||||
keyInfo.passphrase.iterations
|
keyInfo.passphrase.iterations,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("structures.auth.SetupEncryptionBody")
|
interface IProps {
|
||||||
export default class SetupEncryptionBody extends React.Component {
|
onFinished: (boolean) => void;
|
||||||
static propTypes = {
|
}
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor() {
|
interface IState {
|
||||||
super();
|
phase: Phase;
|
||||||
|
verificationRequest: VerificationRequest;
|
||||||
|
backupInfo: IKeyBackupInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("structures.auth.SetupEncryptionBody")
|
||||||
|
export default class SetupEncryptionBody extends React.Component<IProps, IState> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
store.on("update", this._onStoreUpdate);
|
store.on("update", this.onStoreUpdate);
|
||||||
store.start();
|
store.start();
|
||||||
this.state = {
|
this.state = {
|
||||||
phase: store.phase,
|
phase: store.phase,
|
||||||
|
@ -53,10 +63,10 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_onStoreUpdate = () => {
|
private onStoreUpdate = () => {
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
if (store.phase === Phase.Finished) {
|
if (store.phase === Phase.Finished) {
|
||||||
this.props.onFinished();
|
this.props.onFinished(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -66,18 +76,18 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
store.off("update", this._onStoreUpdate);
|
store.off("update", this.onStoreUpdate);
|
||||||
store.stop();
|
store.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUsePassphraseClick = async () => {
|
private onUsePassphraseClick = async () => {
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
store.usePassPhrase();
|
store.usePassPhrase();
|
||||||
}
|
};
|
||||||
|
|
||||||
_onVerifyClick = () => {
|
private onVerifyClick = () => {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const userId = cli.getUserId();
|
const userId = cli.getUserId();
|
||||||
const requestPromise = cli.requestVerification(userId);
|
const requestPromise = cli.requestVerification(userId);
|
||||||
|
@ -91,42 +101,44 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
request.cancel();
|
request.cancel();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onSkipClick = () => {
|
private onSkipClick = () => {
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
store.skip();
|
store.skip();
|
||||||
}
|
};
|
||||||
|
|
||||||
onSkipConfirmClick = () => {
|
private onSkipConfirmClick = () => {
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
store.skipConfirm();
|
store.skipConfirm();
|
||||||
}
|
};
|
||||||
|
|
||||||
onSkipBackClick = () => {
|
private onSkipBackClick = () => {
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
store.returnAfterSkip();
|
store.returnAfterSkip();
|
||||||
}
|
};
|
||||||
|
|
||||||
onDoneClick = () => {
|
private onDoneClick = () => {
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
store.done();
|
store.done();
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
private onEncryptionPanelClose = () => {
|
||||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
this.props.onFinished(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
public render() {
|
||||||
const {
|
const {
|
||||||
phase,
|
phase,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
if (this.state.verificationRequest) {
|
if (this.state.verificationRequest) {
|
||||||
const EncryptionPanel = sdk.getComponent("views.right_panel.EncryptionPanel");
|
|
||||||
return <EncryptionPanel
|
return <EncryptionPanel
|
||||||
layout="dialog"
|
layout="dialog"
|
||||||
verificationRequest={this.state.verificationRequest}
|
verificationRequest={this.state.verificationRequest}
|
||||||
onClose={this.props.onFinished}
|
onClose={this.onEncryptionPanelClose}
|
||||||
member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)}
|
member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)}
|
||||||
|
isRoomEncrypted={false}
|
||||||
/>;
|
/>;
|
||||||
} else if (phase === Phase.Intro) {
|
} else if (phase === Phase.Intro) {
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
@ -139,14 +151,14 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
|
|
||||||
let useRecoveryKeyButton;
|
let useRecoveryKeyButton;
|
||||||
if (recoveryKeyPrompt) {
|
if (recoveryKeyPrompt) {
|
||||||
useRecoveryKeyButton = <AccessibleButton kind="link" onClick={this._onUsePassphraseClick}>
|
useRecoveryKeyButton = <AccessibleButton kind="link" onClick={this.onUsePassphraseClick}>
|
||||||
{recoveryKeyPrompt}
|
{recoveryKeyPrompt}
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let verifyButton;
|
let verifyButton;
|
||||||
if (store.hasDevicesToVerifyAgainst) {
|
if (store.hasDevicesToVerifyAgainst) {
|
||||||
verifyButton = <AccessibleButton kind="primary" onClick={this._onVerifyClick}>
|
verifyButton = <AccessibleButton kind="primary" onClick={this.onVerifyClick}>
|
||||||
{ _t("Use another login") }
|
{ _t("Use another login") }
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
}
|
}
|
||||||
|
@ -217,7 +229,6 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (phase === Phase.Busy || phase === Phase.Loading) {
|
} else if (phase === Phase.Busy || phase === Phase.Loading) {
|
||||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
} else {
|
} else {
|
||||||
console.log(`SetupEncryptionBody: Unknown phase ${phase}`);
|
console.log(`SetupEncryptionBody: Unknown phase ${phase}`);
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import * as Lifecycle from '../../../Lifecycle';
|
import * as Lifecycle from '../../../Lifecycle';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
@ -26,6 +25,12 @@ import AuthPage from "../../views/auth/AuthPage";
|
||||||
import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY } from "../../../BasePlatform";
|
import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY } from "../../../BasePlatform";
|
||||||
import SSOButtons from "../../views/elements/SSOButtons";
|
import SSOButtons from "../../views/elements/SSOButtons";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import ConfirmWipeDeviceDialog from '../../views/dialogs/ConfirmWipeDeviceDialog';
|
||||||
|
import Field from '../../views/elements/Field';
|
||||||
|
import AccessibleButton from '../../views/elements/AccessibleButton';
|
||||||
|
import Spinner from "../../views/elements/Spinner";
|
||||||
|
import AuthHeader from "../../views/auth/AuthHeader";
|
||||||
|
import AuthBody from "../../views/auth/AuthBody";
|
||||||
|
|
||||||
const LOGIN_VIEW = {
|
const LOGIN_VIEW = {
|
||||||
LOADING: 1,
|
LOADING: 1,
|
||||||
|
@ -49,7 +54,7 @@ interface IProps {
|
||||||
fragmentAfterLogin?: string;
|
fragmentAfterLogin?: string;
|
||||||
|
|
||||||
// Called when the SSO login completes
|
// Called when the SSO login completes
|
||||||
onTokenLoginCompleted: () => void,
|
onTokenLoginCompleted: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -94,7 +99,6 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
onClearAll = () => {
|
onClearAll = () => {
|
||||||
const ConfirmWipeDeviceDialog = sdk.getComponent('dialogs.ConfirmWipeDeviceDialog');
|
|
||||||
Modal.createTrackedDialog('Clear Data', 'Soft Logout', ConfirmWipeDeviceDialog, {
|
Modal.createTrackedDialog('Clear Data', 'Soft Logout', ConfirmWipeDeviceDialog, {
|
||||||
onFinished: (wipeData) => {
|
onFinished: (wipeData) => {
|
||||||
if (!wipeData) return;
|
if (!wipeData) return;
|
||||||
|
@ -202,7 +206,6 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private renderSignInSection() {
|
private renderSignInSection() {
|
||||||
if (this.state.loginView === LOGIN_VIEW.LOADING) {
|
if (this.state.loginView === LOGIN_VIEW.LOADING) {
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,9 +217,6 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.loginView === LOGIN_VIEW.PASSWORD) {
|
if (this.state.loginView === LOGIN_VIEW.PASSWORD) {
|
||||||
const Field = sdk.getComponent("elements.Field");
|
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
|
|
||||||
let error = null;
|
let error = null;
|
||||||
if (this.state.errorText) {
|
if (this.state.errorText) {
|
||||||
error = <span className='mx_Login_error'>{this.state.errorText}</span>;
|
error = <span className='mx_Login_error'>{this.state.errorText}</span>;
|
||||||
|
@ -286,10 +286,6 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const AuthHeader = sdk.getComponent("auth.AuthHeader");
|
|
||||||
const AuthBody = sdk.getComponent("auth.AuthBody");
|
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthPage>
|
<AuthPage>
|
||||||
<AuthHeader />
|
<AuthHeader />
|
||||||
|
|
124
src/components/views/audio_messages/AudioPlayer.tsx
Normal file
124
src/components/views/audio_messages/AudioPlayer.tsx
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
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 { Playback, PlaybackState } from "../../../voice/Playback";
|
||||||
|
import React, { createRef, ReactNode, RefObject } from "react";
|
||||||
|
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||||
|
import PlayPauseButton from "./PlayPauseButton";
|
||||||
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import { formatBytes } from "../../../utils/FormattingUtils";
|
||||||
|
import DurationClock from "./DurationClock";
|
||||||
|
import { Key } from "../../../Keyboard";
|
||||||
|
import { _t } from "../../../languageHandler";
|
||||||
|
import SeekBar from "./SeekBar";
|
||||||
|
import PlaybackClock from "./PlaybackClock";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
// Playback instance to render. Cannot change during component lifecycle: create
|
||||||
|
// an all-new component instead.
|
||||||
|
playback: Playback;
|
||||||
|
|
||||||
|
mediaName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
playbackPhase: PlaybackState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("views.audio_messages.AudioPlayer")
|
||||||
|
export default class AudioPlayer extends React.PureComponent<IProps, IState> {
|
||||||
|
private playPauseRef: RefObject<PlayPauseButton> = createRef();
|
||||||
|
private seekRef: RefObject<SeekBar> = createRef();
|
||||||
|
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
playbackPhase: PlaybackState.Decoding, // default assumption
|
||||||
|
};
|
||||||
|
|
||||||
|
// We don't need to de-register: the class handles this for us internally
|
||||||
|
this.props.playback.on(UPDATE_EVENT, this.onPlaybackUpdate);
|
||||||
|
|
||||||
|
// Don't wait for the promise to complete - it will emit a progress update when it
|
||||||
|
// is done, and it's not meant to take long anyhow.
|
||||||
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
this.props.playback.prepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPlaybackUpdate = (ev: PlaybackState) => {
|
||||||
|
this.setState({ playbackPhase: ev });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onKeyDown = (ev: React.KeyboardEvent) => {
|
||||||
|
// stopPropagation() prevents the FocusComposer catch-all from triggering,
|
||||||
|
// but we need to do it on key down instead of press (even though the user
|
||||||
|
// interaction is typically on press).
|
||||||
|
if (ev.key === Key.SPACE) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.playPauseRef.current?.toggleState();
|
||||||
|
} else if (ev.key === Key.ARROW_LEFT) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.seekRef.current?.left();
|
||||||
|
} else if (ev.key === Key.ARROW_RIGHT) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.seekRef.current?.right();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected renderFileSize(): string {
|
||||||
|
const bytes = this.props.playback.sizeBytes;
|
||||||
|
if (!bytes) return null;
|
||||||
|
|
||||||
|
// Not translated here - we're just presenting the data which should already
|
||||||
|
// be translated if needed.
|
||||||
|
return `(${formatBytes(bytes)})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): ReactNode {
|
||||||
|
// tabIndex=0 to ensure that the whole component becomes a tab stop, where we handle keyboard
|
||||||
|
// events for accessibility
|
||||||
|
return <div className='mx_MediaBody mx_AudioPlayer_container' tabIndex={0} onKeyDown={this.onKeyDown}>
|
||||||
|
<div className='mx_AudioPlayer_primaryContainer'>
|
||||||
|
<PlayPauseButton
|
||||||
|
playback={this.props.playback}
|
||||||
|
playbackPhase={this.state.playbackPhase}
|
||||||
|
tabIndex={-1} // prevent tabbing into the button
|
||||||
|
ref={this.playPauseRef}
|
||||||
|
/>
|
||||||
|
<div className='mx_AudioPlayer_mediaInfo'>
|
||||||
|
<span className='mx_AudioPlayer_mediaName'>
|
||||||
|
{this.props.mediaName || _t("Unnamed audio")}
|
||||||
|
</span>
|
||||||
|
<div className='mx_AudioPlayer_byline'>
|
||||||
|
<DurationClock playback={this.props.playback} />
|
||||||
|
{/* easiest way to introduce a gap between the components */}
|
||||||
|
{ this.renderFileSize() }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='mx_AudioPlayer_seek'>
|
||||||
|
<SeekBar
|
||||||
|
playback={this.props.playback}
|
||||||
|
tabIndex={-1} // prevent tabbing into the bar
|
||||||
|
playbackPhase={this.state.playbackPhase}
|
||||||
|
ref={this.seekRef}
|
||||||
|
/>
|
||||||
|
<PlaybackClock playback={this.props.playback} defaultDisplaySeconds={0} />
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ interface IState {
|
||||||
* Simply converts seconds into minutes and seconds. Note that hours will not be
|
* Simply converts seconds into minutes and seconds. Note that hours will not be
|
||||||
* displayed, making it possible to see "82:29".
|
* displayed, making it possible to see "82:29".
|
||||||
*/
|
*/
|
||||||
@replaceableComponent("views.voice_messages.Clock")
|
@replaceableComponent("views.audio_messages.Clock")
|
||||||
export default class Clock extends React.Component<IProps, IState> {
|
export default class Clock extends React.Component<IProps, IState> {
|
||||||
public constructor(props) {
|
public constructor(props) {
|
||||||
super(props);
|
super(props);
|
55
src/components/views/audio_messages/DurationClock.tsx
Normal file
55
src/components/views/audio_messages/DurationClock.tsx
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import Clock from "./Clock";
|
||||||
|
import { Playback } from "../../../voice/Playback";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
playback: Playback;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
durationSeconds: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A clock which shows a clip's maximum duration.
|
||||||
|
*/
|
||||||
|
@replaceableComponent("views.audio_messages.DurationClock")
|
||||||
|
export default class DurationClock extends React.PureComponent<IProps, IState> {
|
||||||
|
public constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
// we track the duration on state because we won't really know what the clip duration
|
||||||
|
// is until the first time update, and as a PureComponent we are trying to dedupe state
|
||||||
|
// updates as much as possible. This is just the easiest way to avoid a forceUpdate() or
|
||||||
|
// member property to track "did we get a duration".
|
||||||
|
durationSeconds: this.props.playback.clockInfo.durationSeconds,
|
||||||
|
};
|
||||||
|
this.props.playback.clockInfo.liveData.onUpdate(this.onTimeUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onTimeUpdate = (time: number[]) => {
|
||||||
|
this.setState({ durationSeconds: time[1] });
|
||||||
|
};
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return <Clock seconds={this.state.durationSeconds} />;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -12,16 +15,13 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Clock from "./Clock";
|
import { IRecordingUpdate, VoiceRecording } from "../../../voice/VoiceRecording";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import Clock from "./Clock";
|
||||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||||
import {
|
|
||||||
IRecordingUpdate,
|
|
||||||
VoiceRecording,
|
|
||||||
} from "../../../voice/VoiceRecording";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
recorder?: VoiceRecording;
|
recorder: VoiceRecording;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -31,7 +31,7 @@ interface IState {
|
||||||
/**
|
/**
|
||||||
* A clock for a live recording.
|
* A clock for a live recording.
|
||||||
*/
|
*/
|
||||||
@replaceableComponent("views.voice_messages.LiveRecordingClock")
|
@replaceableComponent("views.audio_messages.LiveRecordingClock")
|
||||||
export default class LiveRecordingClock extends React.PureComponent<IProps, IState> {
|
export default class LiveRecordingClock extends React.PureComponent<IProps, IState> {
|
||||||
private seconds = 0;
|
private seconds = 0;
|
||||||
private scheduledUpdate = new MarkedExecution(
|
private scheduledUpdate = new MarkedExecution(
|
|
@ -1,9 +1,12 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
Unless required by applicable law or agreed to in writing, software
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -12,16 +15,15 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import Waveform from "./Waveform";
|
import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../voice/VoiceRecording";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import { arrayFastResample } from "../../../utils/arrays";
|
||||||
|
import { percentageOf } from "../../../utils/numbers";
|
||||||
|
import Waveform from "./Waveform";
|
||||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||||
import {
|
|
||||||
IRecordingUpdate,
|
|
||||||
VoiceRecording,
|
|
||||||
} from "../../../voice/VoiceRecording";
|
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
recorder?: VoiceRecording;
|
recorder: VoiceRecording;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -31,7 +33,7 @@ interface IState {
|
||||||
/**
|
/**
|
||||||
* A waveform which shows the waveform of a live recording
|
* A waveform which shows the waveform of a live recording
|
||||||
*/
|
*/
|
||||||
@replaceableComponent("views.voice_messages.LiveRecordingWaveform")
|
@replaceableComponent("views.audio_messages.LiveRecordingWaveform")
|
||||||
export default class LiveRecordingWaveform extends React.PureComponent<IProps, IState> {
|
export default class LiveRecordingWaveform extends React.PureComponent<IProps, IState> {
|
||||||
public static defaultProps = {
|
public static defaultProps = {
|
||||||
progress: 1,
|
progress: 1,
|
||||||
|
@ -52,15 +54,18 @@ export default class LiveRecordingWaveform extends React.PureComponent<IProps, I
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.recorder.liveData.onUpdate((update: IRecordingUpdate) => {
|
this.props.recorder.liveData.onUpdate((update: IRecordingUpdate) => {
|
||||||
this.waveform = update.waveform;
|
const bars = arrayFastResample(Array.from(update.waveform), RECORDING_PLAYBACK_SAMPLES);
|
||||||
|
// The incoming data is between zero and one, but typically even screaming into a
|
||||||
|
// microphone won't send you over 0.6, so we artificially adjust the gain for the
|
||||||
|
// waveform. This results in a slightly more cinematic/animated waveform for the
|
||||||
|
// user.
|
||||||
|
this.waveform = bars.map(b => percentageOf(b, 0, 0.50));
|
||||||
this.scheduledUpdate.mark();
|
this.scheduledUpdate.mark();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateWaveform() {
|
private updateWaveform() {
|
||||||
this.setState({
|
this.setState({ waveform: this.waveform });
|
||||||
waveform: this.waveform,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
|
@ -21,7 +21,8 @@ import { _t } from "../../../languageHandler";
|
||||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
import { Playback, PlaybackState } from "../../../voice/Playback";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
interface IProps {
|
// omitted props are handled by render function
|
||||||
|
interface IProps extends Omit<React.ComponentProps<typeof AccessibleTooltipButton>, "title" | "onClick" | "disabled"> {
|
||||||
// Playback instance to manipulate. Cannot change during the component lifecycle.
|
// Playback instance to manipulate. Cannot change during the component lifecycle.
|
||||||
playback: Playback;
|
playback: Playback;
|
||||||
|
|
||||||
|
@ -33,19 +34,25 @@ interface IProps {
|
||||||
* Displays a play/pause button (activating the play/pause function of the recorder)
|
* Displays a play/pause button (activating the play/pause function of the recorder)
|
||||||
* to be displayed in reference to a recording.
|
* to be displayed in reference to a recording.
|
||||||
*/
|
*/
|
||||||
@replaceableComponent("views.voice_messages.PlayPauseButton")
|
@replaceableComponent("views.audio_messages.PlayPauseButton")
|
||||||
export default class PlayPauseButton extends React.PureComponent<IProps> {
|
export default class PlayPauseButton extends React.PureComponent<IProps> {
|
||||||
public constructor(props) {
|
public constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onClick = async () => {
|
private onClick = () => {
|
||||||
await this.props.playback.toggle();
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
this.toggleState();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public async toggleState() {
|
||||||
|
await this.props.playback.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
public render(): ReactNode {
|
public render(): ReactNode {
|
||||||
const isPlaying = this.props.playback.isPlaying;
|
const { playback, playbackPhase, ...restProps } = this.props;
|
||||||
const isDisabled = this.props.playbackPhase === PlaybackState.Decoding;
|
const isPlaying = playback.isPlaying;
|
||||||
|
const isDisabled = playbackPhase === PlaybackState.Decoding;
|
||||||
const classes = classNames('mx_PlayPauseButton', {
|
const classes = classNames('mx_PlayPauseButton', {
|
||||||
'mx_PlayPauseButton_play': !isPlaying,
|
'mx_PlayPauseButton_play': !isPlaying,
|
||||||
'mx_PlayPauseButton_pause': isPlaying,
|
'mx_PlayPauseButton_pause': isPlaying,
|
||||||
|
@ -56,6 +63,7 @@ export default class PlayPauseButton extends React.PureComponent<IProps> {
|
||||||
title={isPlaying ? _t("Pause") : _t("Play")}
|
title={isPlaying ? _t("Pause") : _t("Play")}
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
|
{...restProps}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,6 +22,11 @@ import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
playback: Playback;
|
playback: Playback;
|
||||||
|
|
||||||
|
// The default number of seconds to show when the playback has completed or
|
||||||
|
// has not started. Not used during playback, even when paused. Defaults to
|
||||||
|
// clip duration length.
|
||||||
|
defaultDisplaySeconds?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -33,7 +38,7 @@ interface IState {
|
||||||
/**
|
/**
|
||||||
* A clock for a playback of a recording.
|
* A clock for a playback of a recording.
|
||||||
*/
|
*/
|
||||||
@replaceableComponent("views.voice_messages.PlaybackClock")
|
@replaceableComponent("views.audio_messages.PlaybackClock")
|
||||||
export default class PlaybackClock extends React.PureComponent<IProps, IState> {
|
export default class PlaybackClock extends React.PureComponent<IProps, IState> {
|
||||||
public constructor(props) {
|
public constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -64,8 +69,12 @@ export default class PlaybackClock extends React.PureComponent<IProps, IState> {
|
||||||
public render() {
|
public render() {
|
||||||
let seconds = this.state.seconds;
|
let seconds = this.state.seconds;
|
||||||
if (this.state.playbackPhase === PlaybackState.Stopped) {
|
if (this.state.playbackPhase === PlaybackState.Stopped) {
|
||||||
|
if (Number.isFinite(this.props.defaultDisplaySeconds)) {
|
||||||
|
seconds = this.props.defaultDisplaySeconds;
|
||||||
|
} else {
|
||||||
seconds = this.state.durationSeconds;
|
seconds = this.state.durationSeconds;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return <Clock seconds={seconds} />;
|
return <Clock seconds={seconds} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -33,7 +33,7 @@ interface IState {
|
||||||
/**
|
/**
|
||||||
* A waveform which shows the waveform of a previously recorded recording
|
* A waveform which shows the waveform of a previously recorded recording
|
||||||
*/
|
*/
|
||||||
@replaceableComponent("views.voice_messages.PlaybackWaveform")
|
@replaceableComponent("views.audio_messages.PlaybackWaveform")
|
||||||
export default class PlaybackWaveform extends React.PureComponent<IProps, IState> {
|
export default class PlaybackWaveform extends React.PureComponent<IProps, IState> {
|
||||||
public constructor(props) {
|
public constructor(props) {
|
||||||
super(props);
|
super(props);
|
|
@ -20,6 +20,7 @@ import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||||
import PlaybackWaveform from "./PlaybackWaveform";
|
import PlaybackWaveform from "./PlaybackWaveform";
|
||||||
import PlayPauseButton from "./PlayPauseButton";
|
import PlayPauseButton from "./PlayPauseButton";
|
||||||
import PlaybackClock from "./PlaybackClock";
|
import PlaybackClock from "./PlaybackClock";
|
||||||
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
// Playback instance to render. Cannot change during component lifecycle: create
|
// Playback instance to render. Cannot change during component lifecycle: create
|
||||||
|
@ -31,6 +32,7 @@ interface IState {
|
||||||
playbackPhase: PlaybackState;
|
playbackPhase: PlaybackState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("views.audio_messages.RecordingPlayback")
|
||||||
export default class RecordingPlayback extends React.PureComponent<IProps, IState> {
|
export default class RecordingPlayback extends React.PureComponent<IProps, IState> {
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -53,7 +55,7 @@ export default class RecordingPlayback extends React.PureComponent<IProps, IStat
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): ReactNode {
|
public render(): ReactNode {
|
||||||
return <div className='mx_VoiceMessagePrimaryContainer'>
|
return <div className='mx_MediaBody mx_VoiceMessagePrimaryContainer'>
|
||||||
<PlayPauseButton playback={this.props.playback} playbackPhase={this.state.playbackPhase} />
|
<PlayPauseButton playback={this.props.playback} playbackPhase={this.state.playbackPhase} />
|
||||||
<PlaybackClock playback={this.props.playback} />
|
<PlaybackClock playback={this.props.playback} />
|
||||||
<PlaybackWaveform playback={this.props.playback} />
|
<PlaybackWaveform playback={this.props.playback} />
|
112
src/components/views/audio_messages/SeekBar.tsx
Normal file
112
src/components/views/audio_messages/SeekBar.tsx
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
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 { Playback, PlaybackState } from "../../../voice/Playback";
|
||||||
|
import React, { ChangeEvent, CSSProperties, ReactNode } from "react";
|
||||||
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||||
|
import { percentageOf } from "../../../utils/numbers";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
// Playback instance to render. Cannot change during component lifecycle: create
|
||||||
|
// an all-new component instead.
|
||||||
|
playback: Playback;
|
||||||
|
|
||||||
|
// Tab index for the underlying component. Useful if the seek bar is in a managed state.
|
||||||
|
// Defaults to zero.
|
||||||
|
tabIndex?: number;
|
||||||
|
|
||||||
|
playbackPhase: PlaybackState;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
percentage: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ISeekCSS extends CSSProperties {
|
||||||
|
'--fillTo': number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ARROW_SKIP_SECONDS = 5; // arbitrary
|
||||||
|
|
||||||
|
@replaceableComponent("views.audio_messages.SeekBar")
|
||||||
|
export default class SeekBar extends React.PureComponent<IProps, IState> {
|
||||||
|
// We use an animation frame request to avoid overly spamming prop updates, even if we aren't
|
||||||
|
// really using anything demanding on the CSS front.
|
||||||
|
|
||||||
|
private animationFrameFn = new MarkedExecution(
|
||||||
|
() => this.doUpdate(),
|
||||||
|
() => requestAnimationFrame(() => this.animationFrameFn.trigger()));
|
||||||
|
|
||||||
|
public static defaultProps = {
|
||||||
|
tabIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
percentage: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// We don't need to de-register: the class handles this for us internally
|
||||||
|
this.props.playback.clockInfo.liveData.onUpdate(() => this.animationFrameFn.mark());
|
||||||
|
}
|
||||||
|
|
||||||
|
private doUpdate() {
|
||||||
|
this.setState({
|
||||||
|
percentage: percentageOf(
|
||||||
|
this.props.playback.clockInfo.timeSeconds,
|
||||||
|
0,
|
||||||
|
this.props.playback.clockInfo.durationSeconds),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public left() {
|
||||||
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
this.props.playback.skipTo(this.props.playback.clockInfo.timeSeconds - ARROW_SKIP_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public right() {
|
||||||
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
this.props.playback.skipTo(this.props.playback.clockInfo.timeSeconds + ARROW_SKIP_SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onChange = (ev: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
// Thankfully, onChange is only called when the user changes the value, not when we
|
||||||
|
// change the value on the component. We can use this as a reliable "skip to X" function.
|
||||||
|
//
|
||||||
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
this.props.playback.skipTo(Number(ev.target.value) * this.props.playback.clockInfo.durationSeconds);
|
||||||
|
};
|
||||||
|
|
||||||
|
public render(): ReactNode {
|
||||||
|
// We use a range input to avoid having to re-invent accessibility handling on
|
||||||
|
// a custom set of divs.
|
||||||
|
return <input
|
||||||
|
type="range"
|
||||||
|
className='mx_SeekBar'
|
||||||
|
tabIndex={this.props.tabIndex}
|
||||||
|
onChange={this.onChange}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
value={this.state.percentage}
|
||||||
|
step={0.001}
|
||||||
|
style={{ '--fillTo': this.state.percentage } as ISeekCSS}
|
||||||
|
disabled={this.props.playbackPhase === PlaybackState.Decoding}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,8 +17,13 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import { CSSProperties } from "react";
|
||||||
|
|
||||||
export interface IProps {
|
interface WaveformCSSProperties extends CSSProperties {
|
||||||
|
'--barHeight': number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
relHeights: number[]; // relative heights (0-1)
|
relHeights: number[]; // relative heights (0-1)
|
||||||
progress: number; // percent complete, 0-1, default 100%
|
progress: number; // percent complete, 0-1, default 100%
|
||||||
}
|
}
|
||||||
|
@ -34,14 +39,7 @@ interface IState {
|
||||||
* For CSS purposes, a mx_Waveform_bar_100pct class is added when the bar should be
|
* For CSS purposes, a mx_Waveform_bar_100pct class is added when the bar should be
|
||||||
* "filled", as a demonstration of the progress property.
|
* "filled", as a demonstration of the progress property.
|
||||||
*/
|
*/
|
||||||
|
@replaceableComponent("views.audio_messages.Waveform")
|
||||||
import { CSSProperties } from "react";
|
|
||||||
|
|
||||||
export interface WaveformCSSProperties extends CSSProperties {
|
|
||||||
'--barHeight': number;
|
|
||||||
}
|
|
||||||
|
|
||||||
@replaceableComponent("views.voice_messages.Waveform")
|
|
||||||
export default class Waveform extends React.PureComponent<IProps, IState> {
|
export default class Waveform extends React.PureComponent<IProps, IState> {
|
||||||
public static defaultProps = {
|
public static defaultProps = {
|
||||||
progress: 1,
|
progress: 1,
|
|
@ -18,7 +18,6 @@ import React, { ChangeEvent, createRef, FormEvent, MouseEvent } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
@ -26,6 +25,8 @@ import Spinner from "../elements/Spinner";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { LocalisedPolicy, Policies } from '../../../Terms';
|
import { LocalisedPolicy, Policies } from '../../../Terms';
|
||||||
|
import Field from '../elements/Field';
|
||||||
|
import CaptchaForm from "./CaptchaForm";
|
||||||
|
|
||||||
/* This file contains a collection of components which are used by the
|
/* This file contains a collection of components which are used by the
|
||||||
* InteractiveAuth to prompt the user to enter the information needed
|
* InteractiveAuth to prompt the user to enter the information needed
|
||||||
|
@ -164,8 +165,7 @@ export class PasswordAuthEntry extends React.Component<IAuthEntryProps, IPasswor
|
||||||
|
|
||||||
let submitButtonOrSpinner;
|
let submitButtonOrSpinner;
|
||||||
if (this.props.busy) {
|
if (this.props.busy) {
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
submitButtonOrSpinner = <Spinner />;
|
||||||
submitButtonOrSpinner = <Loader />;
|
|
||||||
} else {
|
} else {
|
||||||
submitButtonOrSpinner = (
|
submitButtonOrSpinner = (
|
||||||
<input type="submit"
|
<input type="submit"
|
||||||
|
@ -185,8 +185,6 @@ export class PasswordAuthEntry extends React.Component<IAuthEntryProps, IPasswor
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Field = sdk.getComponent('elements.Field');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{ _t("Confirm your identity by entering your account password below.") }</p>
|
<p>{ _t("Confirm your identity by entering your account password below.") }</p>
|
||||||
|
@ -236,13 +234,11 @@ export class RecaptchaAuthEntry extends React.Component<IRecaptchaAuthEntryProps
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.props.busy) {
|
if (this.props.busy) {
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
return <Spinner />;
|
||||||
return <Loader />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let errorText = this.props.errorText;
|
let errorText = this.props.errorText;
|
||||||
|
|
||||||
const CaptchaForm = sdk.getComponent("views.auth.CaptchaForm");
|
|
||||||
let sitePublicKey;
|
let sitePublicKey;
|
||||||
if (!this.props.stageParams || !this.props.stageParams.public_key) {
|
if (!this.props.stageParams || !this.props.stageParams.public_key) {
|
||||||
errorText = _t(
|
errorText = _t(
|
||||||
|
@ -390,8 +386,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.props.busy) {
|
if (this.props.busy) {
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
return <Spinner />;
|
||||||
return <Loader />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkboxes = [];
|
const checkboxes = [];
|
||||||
|
@ -590,8 +585,7 @@ export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsi
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.requestingToken) {
|
if (this.state.requestingToken) {
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
return <Spinner />;
|
||||||
return <Loader />;
|
|
||||||
} else {
|
} else {
|
||||||
const enableSubmit = Boolean(this.state.token);
|
const enableSubmit = Boolean(this.state.token);
|
||||||
const submitClasses = classNames({
|
const submitClasses = classNames({
|
||||||
|
|
|
@ -52,8 +52,8 @@ interface IProps {
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
fieldValid: Partial<Record<LoginField, boolean>>;
|
fieldValid: Partial<Record<LoginField, boolean>>;
|
||||||
loginType: LoginField.Email | LoginField.MatrixId | LoginField.Phone,
|
loginType: LoginField.Email | LoginField.MatrixId | LoginField.Phone;
|
||||||
password: "",
|
password: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
enum LoginField {
|
enum LoginField {
|
||||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import * as Email from '../../../email';
|
import * as Email from '../../../email';
|
||||||
import { looksValid as phoneNumberLooksValid } from '../../../phonenumber';
|
import { looksValid as phoneNumberLooksValid } from '../../../phonenumber';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
@ -31,6 +30,7 @@ import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||||
import Field from '../elements/Field';
|
import Field from '../elements/Field';
|
||||||
import RegistrationEmailPromptDialog from '../dialogs/RegistrationEmailPromptDialog';
|
import RegistrationEmailPromptDialog from '../dialogs/RegistrationEmailPromptDialog';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import CountryDropdown from "./CountryDropdown";
|
||||||
|
|
||||||
enum RegistrationField {
|
enum RegistrationField {
|
||||||
Email = "field_email",
|
Email = "field_email",
|
||||||
|
@ -471,7 +471,6 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
|
||||||
if (!this.showPhoneNumber()) {
|
if (!this.showPhoneNumber()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown');
|
|
||||||
const phoneLabel = this.authStepIsRequired('m.login.msisdn') ?
|
const phoneLabel = this.authStepIsRequired('m.login.msisdn') ?
|
||||||
_t("Phone") :
|
_t("Phone") :
|
||||||
_t("Phone (optional)");
|
_t("Phone (optional)");
|
||||||
|
|
|
@ -30,13 +30,14 @@ import { _t } from "../../../languageHandler";
|
||||||
import TextWithTooltip from "../elements/TextWithTooltip";
|
import TextWithTooltip from "../elements/TextWithTooltip";
|
||||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import { IOOBData } from "../../../stores/ThreepidInviteStore";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
avatarSize: number;
|
avatarSize: number;
|
||||||
displayBadge?: boolean;
|
displayBadge?: boolean;
|
||||||
forceCount?: boolean;
|
forceCount?: boolean;
|
||||||
oobData?: object;
|
oobData?: IOOBData;
|
||||||
viewAvatarOnClick?: boolean;
|
viewAvatarOnClick?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,14 +24,14 @@ import Modal from '../../../Modal';
|
||||||
import * as Avatar from '../../../Avatar';
|
import * as Avatar from '../../../Avatar';
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { mediaFromMxc } from "../../../customisations/Media";
|
import { mediaFromMxc } from "../../../customisations/Media";
|
||||||
|
import { IOOBData } from '../../../stores/ThreepidInviteStore';
|
||||||
|
|
||||||
interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> {
|
interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> {
|
||||||
// Room may be left unset here, but if it is,
|
// Room may be left unset here, but if it is,
|
||||||
// oobData.avatarUrl should be set (else there
|
// oobData.avatarUrl should be set (else there
|
||||||
// would be nowhere to get the avatar from)
|
// would be nowhere to get the avatar from)
|
||||||
room?: Room;
|
room?: Room;
|
||||||
// TODO: type when js-sdk has types
|
oobData?: IOOBData;
|
||||||
oobData?: any;
|
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
resizeMethod?: ResizeMethod;
|
resizeMethod?: ResizeMethod;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
Copyright 2015, 2016, 2018, 2019, 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -16,12 +16,11 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||||
import { EventStatus } from 'matrix-js-sdk/src/models/event';
|
import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||||
|
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import * as sdk from '../../../index';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import Resend from '../../../Resend';
|
import Resend from '../../../Resend';
|
||||||
|
@ -29,53 +28,65 @@ import SettingsStore from '../../../settings/SettingsStore';
|
||||||
import { isUrlPermitted } from '../../../HtmlUtils';
|
import { isUrlPermitted } from '../../../HtmlUtils';
|
||||||
import { isContentActionable } from '../../../utils/EventUtils';
|
import { isContentActionable } from '../../../utils/EventUtils';
|
||||||
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from './IconizedContextMenu';
|
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from './IconizedContextMenu';
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
|
||||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard";
|
import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard";
|
||||||
import ForwardDialog from "../dialogs/ForwardDialog";
|
import ForwardDialog from "../dialogs/ForwardDialog";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
|
import ReportEventDialog from '../dialogs/ReportEventDialog';
|
||||||
|
import ViewSource from '../../structures/ViewSource';
|
||||||
|
import ConfirmRedactDialog from '../dialogs/ConfirmRedactDialog';
|
||||||
|
import ErrorDialog from '../dialogs/ErrorDialog';
|
||||||
|
import ShareDialog from '../dialogs/ShareDialog';
|
||||||
|
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||||
|
|
||||||
export function canCancel(eventStatus) {
|
export function canCancel(eventStatus: EventStatus): boolean {
|
||||||
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
|
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.context_menus.MessageContextMenu")
|
interface IEventTileOps {
|
||||||
export default class MessageContextMenu extends React.Component {
|
isWidgetHidden(): boolean;
|
||||||
static propTypes = {
|
unhideWidget(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
/* the MatrixEvent associated with the context menu */
|
/* the MatrixEvent associated with the context menu */
|
||||||
mxEvent: PropTypes.object.isRequired,
|
mxEvent: MatrixEvent;
|
||||||
|
|
||||||
/* an optional EventTileOps implementation that can be used to unhide preview widgets */
|
/* an optional EventTileOps implementation that can be used to unhide preview widgets */
|
||||||
eventTileOps: PropTypes.object,
|
eventTileOps?: IEventTileOps;
|
||||||
|
permalinkCreator?: RoomPermalinkCreator;
|
||||||
/* an optional function to be called when the user clicks collapse thread, if not provided hide button */
|
/* an optional function to be called when the user clicks collapse thread, if not provided hide button */
|
||||||
collapseReplyThread: PropTypes.func,
|
collapseReplyThread?(): void;
|
||||||
|
|
||||||
/* callback called when the menu is dismissed */
|
/* callback called when the menu is dismissed */
|
||||||
onFinished: PropTypes.func,
|
onFinished(): void;
|
||||||
|
|
||||||
/* if the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) */
|
/* if the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) */
|
||||||
onCloseDialog: PropTypes.func,
|
onCloseDialog?(): void;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
canRedact: boolean;
|
||||||
|
canPin: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@replaceableComponent("views.context_menus.MessageContextMenu")
|
||||||
|
export default class MessageContextMenu extends React.Component<IProps, IState> {
|
||||||
state = {
|
state = {
|
||||||
canRedact: false,
|
canRedact: false,
|
||||||
canPin: false,
|
canPin: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions);
|
MatrixClientPeg.get().on('RoomMember.powerLevel', this.checkPermissions);
|
||||||
this._checkPermissions();
|
this.checkPermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (cli) {
|
if (cli) {
|
||||||
cli.removeListener('RoomMember.powerLevel', this._checkPermissions);
|
cli.removeListener('RoomMember.powerLevel', this.checkPermissions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkPermissions = () => {
|
private checkPermissions = (): void => {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const room = cli.getRoom(this.props.mxEvent.getRoomId());
|
const room = cli.getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
|
||||||
|
@ -93,7 +104,7 @@ export default class MessageContextMenu extends React.Component {
|
||||||
this.setState({ canRedact, canPin });
|
this.setState({ canRedact, canPin });
|
||||||
};
|
};
|
||||||
|
|
||||||
_isPinned() {
|
private isPinned(): boolean {
|
||||||
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||||
const pinnedEvent = room.currentState.getStateEvents(EventType.RoomPinnedEvents, '');
|
const pinnedEvent = room.currentState.getStateEvents(EventType.RoomPinnedEvents, '');
|
||||||
if (!pinnedEvent) return false;
|
if (!pinnedEvent) return false;
|
||||||
|
@ -101,38 +112,35 @@ export default class MessageContextMenu extends React.Component {
|
||||||
return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId());
|
return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
onResendReactionsClick = () => {
|
private onResendReactionsClick = (): void => {
|
||||||
for (const reaction of this._getUnsentReactions()) {
|
for (const reaction of this.getUnsentReactions()) {
|
||||||
Resend.resend(reaction);
|
Resend.resend(reaction);
|
||||||
}
|
}
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
onReportEventClick = () => {
|
private onReportEventClick = (): void => {
|
||||||
const ReportEventDialog = sdk.getComponent("dialogs.ReportEventDialog");
|
|
||||||
Modal.createTrackedDialog('Report Event', '', ReportEventDialog, {
|
Modal.createTrackedDialog('Report Event', '', ReportEventDialog, {
|
||||||
mxEvent: this.props.mxEvent,
|
mxEvent: this.props.mxEvent,
|
||||||
}, 'mx_Dialog_reportEvent');
|
}, 'mx_Dialog_reportEvent');
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
onViewSourceClick = () => {
|
private onViewSourceClick = (): void => {
|
||||||
const ViewSource = sdk.getComponent('structures.ViewSource');
|
|
||||||
Modal.createTrackedDialog('View Event Source', '', ViewSource, {
|
Modal.createTrackedDialog('View Event Source', '', ViewSource, {
|
||||||
mxEvent: this.props.mxEvent,
|
mxEvent: this.props.mxEvent,
|
||||||
}, 'mx_Dialog_viewsource');
|
}, 'mx_Dialog_viewsource');
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
onRedactClick = () => {
|
private onRedactClick = (): void => {
|
||||||
const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
|
|
||||||
Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, {
|
Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, {
|
||||||
onFinished: async (proceed, reason) => {
|
onFinished: async (proceed: boolean, reason?: string) => {
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
try {
|
try {
|
||||||
if (this.props.onCloseDialog) this.props.onCloseDialog();
|
this.props.onCloseDialog?.();
|
||||||
await cli.redactEvent(
|
await cli.redactEvent(
|
||||||
this.props.mxEvent.getRoomId(),
|
this.props.mxEvent.getRoomId(),
|
||||||
this.props.mxEvent.getId(),
|
this.props.mxEvent.getId(),
|
||||||
|
@ -145,7 +153,6 @@ export default class MessageContextMenu extends React.Component {
|
||||||
// (e.g. no errcode or statusCode) as in that case the redactions end up in the
|
// (e.g. no errcode or statusCode) as in that case the redactions end up in the
|
||||||
// detached queue and we show the room status bar to allow retry
|
// detached queue and we show the room status bar to allow retry
|
||||||
if (typeof code !== "undefined") {
|
if (typeof code !== "undefined") {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
// display error message stating you couldn't delete this.
|
// display error message stating you couldn't delete this.
|
||||||
Modal.createTrackedDialog('You cannot delete this message', '', ErrorDialog, {
|
Modal.createTrackedDialog('You cannot delete this message', '', ErrorDialog, {
|
||||||
title: _t('Error'),
|
title: _t('Error'),
|
||||||
|
@ -158,7 +165,7 @@ export default class MessageContextMenu extends React.Component {
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
onForwardClick = () => {
|
private onForwardClick = (): void => {
|
||||||
Modal.createTrackedDialog('Forward Message', '', ForwardDialog, {
|
Modal.createTrackedDialog('Forward Message', '', ForwardDialog, {
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
event: this.props.mxEvent,
|
event: this.props.mxEvent,
|
||||||
|
@ -167,12 +174,12 @@ export default class MessageContextMenu extends React.Component {
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
onPinClick = () => {
|
private onPinClick = (): void => {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const room = cli.getRoom(this.props.mxEvent.getRoomId());
|
const room = cli.getRoom(this.props.mxEvent.getRoomId());
|
||||||
const eventId = this.props.mxEvent.getId();
|
const eventId = this.props.mxEvent.getId();
|
||||||
|
|
||||||
const pinnedIds = room?.currentState?.getStateEvents(EventType.RoomPinnedEvents, "")?.pinned || [];
|
const pinnedIds = room?.currentState?.getStateEvents(EventType.RoomPinnedEvents, "")?.getContent().pinned || [];
|
||||||
if (pinnedIds.includes(eventId)) {
|
if (pinnedIds.includes(eventId)) {
|
||||||
pinnedIds.splice(pinnedIds.indexOf(eventId), 1);
|
pinnedIds.splice(pinnedIds.indexOf(eventId), 1);
|
||||||
} else {
|
} else {
|
||||||
|
@ -188,18 +195,16 @@ export default class MessageContextMenu extends React.Component {
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
closeMenu = () => {
|
private closeMenu = (): void => {
|
||||||
if (this.props.onFinished) this.props.onFinished();
|
this.props.onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
onUnhidePreviewClick = () => {
|
private onUnhidePreviewClick = (): void => {
|
||||||
if (this.props.eventTileOps) {
|
this.props.eventTileOps?.unhideWidget();
|
||||||
this.props.eventTileOps.unhideWidget();
|
|
||||||
}
|
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
onQuoteClick = () => {
|
private onQuoteClick = (): void => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: Action.ComposerInsert,
|
action: Action.ComposerInsert,
|
||||||
event: this.props.mxEvent,
|
event: this.props.mxEvent,
|
||||||
|
@ -207,9 +212,8 @@ export default class MessageContextMenu extends React.Component {
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
onPermalinkClick = (e) => {
|
private onPermalinkClick = (e: React.MouseEvent): void => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
|
|
||||||
Modal.createTrackedDialog('share room message dialog', '', ShareDialog, {
|
Modal.createTrackedDialog('share room message dialog', '', ShareDialog, {
|
||||||
target: this.props.mxEvent,
|
target: this.props.mxEvent,
|
||||||
permalinkCreator: this.props.permalinkCreator,
|
permalinkCreator: this.props.permalinkCreator,
|
||||||
|
@ -217,30 +221,27 @@ export default class MessageContextMenu extends React.Component {
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
onCollapseReplyThreadClick = () => {
|
private onCollapseReplyThreadClick = (): void => {
|
||||||
this.props.collapseReplyThread();
|
this.props.collapseReplyThread();
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
_getReactions(filter) {
|
private getReactions(filter: (e: MatrixEvent) => boolean): MatrixEvent[] {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const room = cli.getRoom(this.props.mxEvent.getRoomId());
|
const room = cli.getRoom(this.props.mxEvent.getRoomId());
|
||||||
const eventId = this.props.mxEvent.getId();
|
const eventId = this.props.mxEvent.getId();
|
||||||
return room.getPendingEvents().filter(e => {
|
return room.getPendingEvents().filter(e => {
|
||||||
const relation = e.getRelation();
|
const relation = e.getRelation();
|
||||||
return relation &&
|
return relation?.rel_type === RelationType.Annotation && relation.event_id === eventId && filter(e);
|
||||||
relation.rel_type === "m.annotation" &&
|
|
||||||
relation.event_id === eventId &&
|
|
||||||
filter(e);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_getPendingReactions() {
|
private getPendingReactions(): MatrixEvent[] {
|
||||||
return this._getReactions(e => canCancel(e.status));
|
return this.getReactions(e => canCancel(e.status));
|
||||||
}
|
}
|
||||||
|
|
||||||
_getUnsentReactions() {
|
private getUnsentReactions(): MatrixEvent[] {
|
||||||
return this._getReactions(e => e.status === EventStatus.NOT_SENT);
|
return this.getReactions(e => e.status === EventStatus.NOT_SENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -248,16 +249,17 @@ export default class MessageContextMenu extends React.Component {
|
||||||
const me = cli.getUserId();
|
const me = cli.getUserId();
|
||||||
const mxEvent = this.props.mxEvent;
|
const mxEvent = this.props.mxEvent;
|
||||||
const eventStatus = mxEvent.status;
|
const eventStatus = mxEvent.status;
|
||||||
const unsentReactionsCount = this._getUnsentReactions().length;
|
const unsentReactionsCount = this.getUnsentReactions().length;
|
||||||
let resendReactionsButton;
|
|
||||||
let redactButton;
|
let resendReactionsButton: JSX.Element;
|
||||||
let forwardButton;
|
let redactButton: JSX.Element;
|
||||||
let pinButton;
|
let forwardButton: JSX.Element;
|
||||||
let unhidePreviewButton;
|
let pinButton: JSX.Element;
|
||||||
let externalURLButton;
|
let unhidePreviewButton: JSX.Element;
|
||||||
let quoteButton;
|
let externalURLButton: JSX.Element;
|
||||||
let collapseReplyThread;
|
let quoteButton: JSX.Element;
|
||||||
let redactItemList;
|
let collapseReplyThread: JSX.Element;
|
||||||
|
let redactItemList: JSX.Element;
|
||||||
|
|
||||||
// status is SENT before remote-echo, null after
|
// status is SENT before remote-echo, null after
|
||||||
const isSent = !eventStatus || eventStatus === EventStatus.SENT;
|
const isSent = !eventStatus || eventStatus === EventStatus.SENT;
|
||||||
|
@ -296,7 +298,7 @@ export default class MessageContextMenu extends React.Component {
|
||||||
pinButton = (
|
pinButton = (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
iconClassName="mx_MessageContextMenu_iconPin"
|
iconClassName="mx_MessageContextMenu_iconPin"
|
||||||
label={ this._isPinned() ? _t('Unpin') : _t('Pin') }
|
label={ this.isPinned() ? _t('Unpin') : _t('Pin') }
|
||||||
onClick={this.onPinClick}
|
onClick={this.onPinClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -327,16 +329,20 @@ export default class MessageContextMenu extends React.Component {
|
||||||
if (this.props.permalinkCreator) {
|
if (this.props.permalinkCreator) {
|
||||||
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
|
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
|
||||||
}
|
}
|
||||||
// XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID)
|
|
||||||
const permalinkButton = (
|
const permalinkButton = (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
iconClassName="mx_MessageContextMenu_iconPermalink"
|
iconClassName="mx_MessageContextMenu_iconPermalink"
|
||||||
onClick={this.onPermalinkClick}
|
onClick={this.onPermalinkClick}
|
||||||
label= {_t('Share')}
|
label= {_t('Share')}
|
||||||
element="a"
|
element="a"
|
||||||
href={permalink}
|
{
|
||||||
target="_blank"
|
// XXX: Typescript signature for AccessibleButton doesn't work properly for non-inputs like `a`
|
||||||
rel="noreferrer noopener"
|
...{
|
||||||
|
href: permalink,
|
||||||
|
target: "_blank",
|
||||||
|
rel: "noreferrer noopener",
|
||||||
|
}
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -351,8 +357,8 @@ export default class MessageContextMenu extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bridges can provide a 'external_url' to link back to the source.
|
// Bridges can provide a 'external_url' to link back to the source.
|
||||||
if (typeof (mxEvent.event.content.external_url) === "string" &&
|
if (typeof (mxEvent.getContent().external_url) === "string" &&
|
||||||
isUrlPermitted(mxEvent.event.content.external_url)
|
isUrlPermitted(mxEvent.getContent().external_url)
|
||||||
) {
|
) {
|
||||||
externalURLButton = (
|
externalURLButton = (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
|
@ -360,9 +366,14 @@ export default class MessageContextMenu extends React.Component {
|
||||||
onClick={this.closeMenu}
|
onClick={this.closeMenu}
|
||||||
label={ _t('Source URL') }
|
label={ _t('Source URL') }
|
||||||
element="a"
|
element="a"
|
||||||
target="_blank"
|
{
|
||||||
rel="noreferrer noopener"
|
// XXX: Typescript signature for AccessibleButton doesn't work properly for non-inputs like `a`
|
||||||
href={mxEvent.event.content.external_url}
|
...{
|
||||||
|
target: "_blank",
|
||||||
|
rel: "noreferrer noopener",
|
||||||
|
href: mxEvent.getContent().external_url,
|
||||||
|
}
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -377,7 +388,7 @@ export default class MessageContextMenu extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let reportEventButton;
|
let reportEventButton: JSX.Element;
|
||||||
if (mxEvent.getSender() !== me) {
|
if (mxEvent.getSender() !== me) {
|
||||||
reportEventButton = (
|
reportEventButton = (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue