From 54cd385e6d5bf07c0e47dee2e448e9d9c4f14245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 27 Aug 2021 13:31:43 +0200 Subject: [PATCH 01/42] Improve AUX panel behaviour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/structures/_RoomView.scss | 1 - src/components/structures/RoomView.tsx | 34 ------------------- src/components/views/rooms/AuxPanel.tsx | 32 ++--------------- src/components/views/voip/CallViewForRoom.tsx | 7 +--- 4 files changed, 3 insertions(+), 71 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index 86c2efeb4a..fd9c4a14fc 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -89,7 +89,6 @@ limitations under the License. margin: 0px auto; overflow: auto; - flex: 0 0 auto; } .mx_RoomView_auxPanel_fullHeight { diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 474b99262d..ef5600eed2 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -78,7 +78,6 @@ import { objectHasDiff } from "../../utils/objects"; import SpaceRoomView from "./SpaceRoomView"; import { IOpts } from "../../createRoom"; import { replaceableComponent } from "../../utils/replaceableComponent"; -import UIStore from "../../stores/UIStore"; import EditorStateTransfer from "../../utils/EditorStateTransfer"; import { throttle } from "lodash"; import ErrorDialog from '../views/dialogs/ErrorDialog'; @@ -156,7 +155,6 @@ export interface IState { // used by componentDidUpdate to avoid unnecessary checks atEndOfLiveTimelineInit: boolean; showTopUnreadMessagesBar: boolean; - auxPanelMaxHeight?: number; statusBarVisible: boolean; // We load this later by asking the js-sdk to suggest a version for us. // This object is the result of Room#getRecommendedVersion() @@ -563,10 +561,6 @@ export default class RoomView extends React.Component { }); window.addEventListener('beforeunload', this.onPageUnload); - if (this.props.resizeNotifier) { - this.props.resizeNotifier.on("middlePanelResized", this.onResize); - } - this.onResize(); } shouldComponentUpdate(nextProps, nextState) { @@ -654,9 +648,6 @@ export default class RoomView extends React.Component { } window.removeEventListener('beforeunload', this.onPageUnload); - if (this.props.resizeNotifier) { - this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize); - } // Remove RoomStore listener if (this.roomStoreToken) { @@ -1617,28 +1608,6 @@ export default class RoomView extends React.Component { }; } - private onResize = () => { - // It seems flexbox doesn't give us a way to constrain the auxPanel height to have - // a minimum of the height of the video element, whilst also capping it from pushing out the page - // so we have to do it via JS instead. In this implementation we cap the height by putting - // a maxHeight on the underlying remote video tag. - - // header + footer + status + give us at least 120px of scrollback at all times. - let auxPanelMaxHeight = UIStore.instance.windowHeight - - (54 + // height of RoomHeader - 36 + // height of the status area - 51 + // minimum height of the message composer - 120); // amount of desired scrollback - - // XXX: this is a bit of a hack and might possibly cause the video to push out the page anyway - // but it's better than the video going missing entirely - if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50; - - if (this.state.auxPanelMaxHeight !== auxPanelMaxHeight) { - this.setState({ auxPanelMaxHeight }); - } - }; - private onStatusBarVisible = () => { if (this.unmounted || this.state.statusBarVisible) return; this.setState({ statusBarVisible: true }); @@ -1926,11 +1895,8 @@ export default class RoomView extends React.Component { const auxPanel = ( { aux } diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 4a62d6711e..7d3877025c 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -35,16 +35,6 @@ interface IProps { room: Room; userId: string; showApps: boolean; // Render apps - - // maxHeight attribute for the aux panel and the video - // therein - maxHeight: number; - - // a callback which is called when the content of the aux panel changes - // content in a way that is likely to make it change size. - onResize: () => void; - fullHeight: boolean; - resizeNotifier: ResizeNotifier; } @@ -92,13 +82,6 @@ export default class AuxPanel extends React.Component { return objectHasDiff(this.props, nextProps) || objectHasDiff(this.state, nextState); } - componentDidUpdate(prevProps, prevState) { - // most changes are likely to cause a resize - if (this.props.onResize) { - this.props.onResize(); - } - } - private rateLimitedUpdate = throttle(() => { this.setState({ counters: this.computeCounters() }); }, 500, { leading: true, trailing: true }); @@ -138,7 +121,6 @@ export default class AuxPanel extends React.Component { const callView = ( ); @@ -148,7 +130,6 @@ export default class AuxPanel extends React.Component { appsDrawer = ; @@ -204,21 +185,12 @@ export default class AuxPanel extends React.Component { } } - const classes = classNames({ - "mx_RoomView_auxPanel": true, - "mx_RoomView_auxPanel_fullHeight": this.props.fullHeight, - }); - const style: React.CSSProperties = {}; - if (!this.props.fullHeight) { - style.maxHeight = this.props.maxHeight; - } - return ( - + { stateViews } + { this.props.children } { appsDrawer } { callView } - { this.props.children } ); } diff --git a/src/components/views/voip/CallViewForRoom.tsx b/src/components/views/voip/CallViewForRoom.tsx index a5aa3e7734..b0a6f17095 100644 --- a/src/components/views/voip/CallViewForRoom.tsx +++ b/src/components/views/voip/CallViewForRoom.tsx @@ -27,9 +27,6 @@ interface IProps { // What room we should display the call for roomId: string; - // maxHeight style attribute for the video panel - maxVideoHeight?: number; - resizeNotifier: ResizeNotifier; } @@ -99,14 +96,12 @@ export default class CallViewForRoom extends React.Component { public render() { if (!this.state.call) return null; - // We subtract 8 as it the margin-bottom of the mx_CallViewForRoom_ResizeWrapper - const maxHeight = this.props.maxVideoHeight - 8; return (
Date: Fri, 27 Aug 2021 14:07:36 +0200 Subject: [PATCH 02/42] Remove unused import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/AuxPanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx index 7d3877025c..7afa29624a 100644 --- a/src/components/views/rooms/AuxPanel.tsx +++ b/src/components/views/rooms/AuxPanel.tsx @@ -15,7 +15,6 @@ limitations under the License. */ import React from 'react'; -import classNames from 'classnames'; import { lexicographicCompare } from 'matrix-js-sdk/src/utils'; import { Room } from 'matrix-js-sdk/src/models/room'; From 2fc36628308bbc4a3949faf93f837c0272d4843c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 17 Sep 2021 09:38:59 +0100 Subject: [PATCH 03/42] Fix space admin check false positive on multiple admins --- src/components/views/dialogs/LeaveSpaceDialog.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/LeaveSpaceDialog.tsx b/src/components/views/dialogs/LeaveSpaceDialog.tsx index 841fa14407..d26c0a8b6a 100644 --- a/src/components/views/dialogs/LeaveSpaceDialog.tsx +++ b/src/components/views/dialogs/LeaveSpaceDialog.tsx @@ -131,8 +131,13 @@ interface IProps { } const isOnlyAdmin = (room: Room): boolean => { - return !room.getJoinedMembers().some(member => { - return member.userId !== room.client.credentials.userId && member.powerLevelNorm === 100; + const userId = room.client.getUserId(); + if (room.getMember(userId).powerLevelNorm !== 100) { + return false; // user is not an admin + } + return room.getJoinedMembers().every(member => { + // return true if every other member has a lower power level (we are highest) + return member.userId === userId || member.powerLevelNorm < 100; }); }; From 308f6b1d868b7383d95a2e4c94d0d004d1b26bfc Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 21 Sep 2021 09:39:41 +0100 Subject: [PATCH 04/42] Upgrade matrix-js-sdk to 13.0.0-rc.1 --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 3e3d9383c4..0498ee948e 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "13.0.0-rc.1", "matrix-widget-api": "^0.1.0-beta.16", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 546e762224..f5931c8c6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5806,9 +5806,10 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "12.5.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/f84905b00398072b592addfb1dae64c8f3a07fa2" +matrix-js-sdk@13.0.0-rc.1: + version "13.0.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-13.0.0-rc.1.tgz#12deab353862852acae8342108d30ce080d364da" + integrity sha512-dfqJwXmG1+Ky2geaNADWYb7mwB2IfLFTE+T4q16gCoh2HM0W5yTMvi+kiJs0QspWFXICTps7eBSSq0827QNU8A== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From afbd52e28b9d9cd36775d44b61a383f83c311d5c Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 21 Sep 2021 09:41:49 +0100 Subject: [PATCH 05/42] Prepare changelog for v3.31.0-rc.1 --- CHANGELOG.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a445a4041..d2da9dad17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,52 @@ +Changes in [3.31.0-rc.1](https://github.com/vector-im/element-desktop/releases/tag/v3.31.0-rc.1) (2021-09-21) +============================================================================================================= + +## ✨ Features + * Say Joining space instead of Joining room where we know its a space ([\#6818](https://github.com/matrix-org/matrix-react-sdk/pull/6818)). Fixes vector-im/element-web#19064 and vector-im/element-web#19064. + * Add warning that some spaces may not be relinked to the newly upgraded room ([\#6805](https://github.com/matrix-org/matrix-react-sdk/pull/6805)). Fixes vector-im/element-web#18858 and vector-im/element-web#18858. + * Delabs Spaces, iterate some copy and move communities/space toggle to preferences ([\#6594](https://github.com/matrix-org/matrix-react-sdk/pull/6594)). Fixes vector-im/element-web#18088, vector-im/element-web#18524 vector-im/element-web#18088 and vector-im/element-web#18088. + * Show "Message" in the user info panel instead of "Start chat" ([\#6319](https://github.com/matrix-org/matrix-react-sdk/pull/6319)). Fixes vector-im/element-web#17877 and vector-im/element-web#17877. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix space keyboard shortcuts conflicting with native zoom shortcuts ([\#6804](https://github.com/matrix-org/matrix-react-sdk/pull/6804)). + * Replace plain text emoji at the end of a line ([\#6784](https://github.com/matrix-org/matrix-react-sdk/pull/6784)). Fixes vector-im/element-web#18833 and vector-im/element-web#18833. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Simplify Space Panel layout and fix some edge cases ([\#6800](https://github.com/matrix-org/matrix-react-sdk/pull/6800)). Fixes vector-im/element-web#18694 and vector-im/element-web#18694. + * Show unsent message warning on Space Panel buttons ([\#6778](https://github.com/matrix-org/matrix-react-sdk/pull/6778)). Fixes vector-im/element-web#18891 and vector-im/element-web#18891. + * Hide mute/unmute button in UserInfo for Spaces as it makes no sense ([\#6790](https://github.com/matrix-org/matrix-react-sdk/pull/6790)). Fixes vector-im/element-web#19007 and vector-im/element-web#19007. + * Fix automatic field population in space create menu not validating ([\#6792](https://github.com/matrix-org/matrix-react-sdk/pull/6792)). Fixes vector-im/element-web#19005 and vector-im/element-web#19005. + * Optimize input label transition on focus ([\#6783](https://github.com/matrix-org/matrix-react-sdk/pull/6783)). Fixes vector-im/element-web#12876 and vector-im/element-web#12876. Contributed by [MadLittleMods](https://github.com/MadLittleMods). + * Adapt and re-use the RolesRoomSettingsTab for Spaces ([\#6779](https://github.com/matrix-org/matrix-react-sdk/pull/6779)). Fixes vector-im/element-web#18908 vector-im/element-web#18909 and vector-im/element-web#18908. + * Deduplicate join rule management between rooms and spaces ([\#6724](https://github.com/matrix-org/matrix-react-sdk/pull/6724)). Fixes vector-im/element-web#18798 and vector-im/element-web#18798. + * Add config option to turn on in-room event sending timing metrics ([\#6766](https://github.com/matrix-org/matrix-react-sdk/pull/6766)). + * Improve the upgrade for restricted user experience ([\#6764](https://github.com/matrix-org/matrix-react-sdk/pull/6764)). Fixes vector-im/element-web#18677 and vector-im/element-web#18677. + * Improve tooltips on space quick actions and explore button ([\#6760](https://github.com/matrix-org/matrix-react-sdk/pull/6760)). Fixes vector-im/element-web#18528 and vector-im/element-web#18528. + * Make space members and user info behave more expectedly ([\#6765](https://github.com/matrix-org/matrix-react-sdk/pull/6765)). Fixes vector-im/element-web#17018 and vector-im/element-web#17018. + * hide no-op m.room.encryption events and better word param changes ([\#6747](https://github.com/matrix-org/matrix-react-sdk/pull/6747)). Fixes vector-im/element-web#18597 and vector-im/element-web#18597. + * Respect m.space.parent relations if they hold valid permissions ([\#6746](https://github.com/matrix-org/matrix-react-sdk/pull/6746)). Fixes vector-im/element-web#10935 and vector-im/element-web#10935. + * Space panel accessibility improvements ([\#6744](https://github.com/matrix-org/matrix-react-sdk/pull/6744)). Fixes vector-im/element-web#18892 and vector-im/element-web#18892. + +## 🐛 Bug Fixes + * Revert Firefox composer deletion hacks ([\#6844](https://github.com/matrix-org/matrix-react-sdk/pull/6844)). Fixes vector-im/element-web#19103 and vector-im/element-web#19103. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix accessing field on oobData which may be undefined ([\#6830](https://github.com/matrix-org/matrix-react-sdk/pull/6830)). Fixes vector-im/element-web#19085 and vector-im/element-web#19085. + * Fix pill deletion on Firefox 78 ([\#6832](https://github.com/matrix-org/matrix-react-sdk/pull/6832)). Fixes vector-im/element-web#19077 and vector-im/element-web#19077. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix reactions aria-label not being a string and thus being read as [Object object] ([\#6828](https://github.com/matrix-org/matrix-react-sdk/pull/6828)). + * Fix missing null guard in space hierarchy pagination ([\#6821](https://github.com/matrix-org/matrix-react-sdk/pull/6821)). Fixes matrix-org/element-web-rageshakes#6299 and matrix-org/element-web-rageshakes#6299. + * Fix checks to show prompt to start new chats ([\#6812](https://github.com/matrix-org/matrix-react-sdk/pull/6812)). + * Fix room list scroll jumps ([\#6777](https://github.com/matrix-org/matrix-react-sdk/pull/6777)). Fixes vector-im/element-web#17460 vector-im/element-web#18440 and vector-im/element-web#17460. Contributed by [robintown](https://github.com/robintown). + * Fix various message bubble alignment issues ([\#6785](https://github.com/matrix-org/matrix-react-sdk/pull/6785)). Fixes vector-im/element-web#18293, vector-im/element-web#18294 vector-im/element-web#18305 and vector-im/element-web#18293. Contributed by [robintown](https://github.com/robintown). + * Make message bubble font size consistent ([\#6795](https://github.com/matrix-org/matrix-react-sdk/pull/6795)). Contributed by [robintown](https://github.com/robintown). + * Fix edge cases around joining new room which does not belong to active space ([\#6797](https://github.com/matrix-org/matrix-react-sdk/pull/6797)). Fixes vector-im/element-web#19025 and vector-im/element-web#19025. + * Fix edge case space issues around creation and initial view ([\#6798](https://github.com/matrix-org/matrix-react-sdk/pull/6798)). Fixes vector-im/element-web#19023 and vector-im/element-web#19023. + * Stop spinner on space preview if the join fails ([\#6803](https://github.com/matrix-org/matrix-react-sdk/pull/6803)). Fixes vector-im/element-web#19034 and vector-im/element-web#19034. + * Fix emoji picker and stickerpicker not appearing correctly when opened ([\#6793](https://github.com/matrix-org/matrix-react-sdk/pull/6793)). Fixes vector-im/element-web#19012 and vector-im/element-web#19012. Contributed by [Palid](https://github.com/Palid). + * Fix autocomplete not having y-scroll ([\#6794](https://github.com/matrix-org/matrix-react-sdk/pull/6794)). Fixes vector-im/element-web#18997 and vector-im/element-web#18997. Contributed by [Palid](https://github.com/Palid). + * Fix broken edge case with public space creation with no alias ([\#6791](https://github.com/matrix-org/matrix-react-sdk/pull/6791)). Fixes vector-im/element-web#19003 and vector-im/element-web#19003. + * Redirect from /#/welcome to /#/home if already logged in ([\#6786](https://github.com/matrix-org/matrix-react-sdk/pull/6786)). Fixes vector-im/element-web#18990 and vector-im/element-web#18990. Contributed by [aaronraimist](https://github.com/aaronraimist). + * Fix build issues from two conflicting PRs landing without merge conflict ([\#6780](https://github.com/matrix-org/matrix-react-sdk/pull/6780)). + * Render guest settings only in public rooms/spaces ([\#6693](https://github.com/matrix-org/matrix-react-sdk/pull/6693)). Fixes vector-im/element-web#18776 and vector-im/element-web#18776. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix message bubble corners being wrong in the presence of hidden events ([\#6776](https://github.com/matrix-org/matrix-react-sdk/pull/6776)). Fixes vector-im/element-web#18124 and vector-im/element-web#18124. Contributed by [robintown](https://github.com/robintown). + * Debounce read marker update on scroll ([\#6771](https://github.com/matrix-org/matrix-react-sdk/pull/6771)). Fixes vector-im/element-web#18961 and vector-im/element-web#18961. + * Use cursor:pointer on space panel buttons ([\#6770](https://github.com/matrix-org/matrix-react-sdk/pull/6770)). Fixes vector-im/element-web#18951 and vector-im/element-web#18951. + * Fix regressed tab view buttons in space update toast ([\#6761](https://github.com/matrix-org/matrix-react-sdk/pull/6761)). Fixes vector-im/element-web#18781 and vector-im/element-web#18781. + Changes in [3.30.0](https://github.com/vector-im/element-desktop/releases/tag/v3.30.0) (2021-09-14) =================================================================================================== From 531622f3b5946b88255b3ee4a72e0d16826e718b Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 21 Sep 2021 09:41:50 +0100 Subject: [PATCH 06/42] v3.31.0-rc.1 --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 0498ee948e..edf4c88c61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.30.0", + "version": "3.31.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -25,7 +25,7 @@ "bin": { "reskindex": "scripts/reskindex.js" }, - "main": "./src/index.js", + "main": "./lib/index.js", "matrix_src_main": "./src/index.js", "matrix_lib_main": "./lib/index.js", "matrix_lib_typings": "./lib/index.d.ts", @@ -210,5 +210,6 @@ "coverageReporters": [ "text" ] - } + }, + "typings": "./lib/index.d.ts" } From 9c69869f63f3fe933d482a6bdad2650a4ad61c78 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 22 Sep 2021 11:55:25 +0100 Subject: [PATCH 07/42] Fix spacing for message composer buttons --- res/css/views/rooms/_MessageComposer.scss | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index 9ba966c083..c20dd43daf 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -185,16 +185,26 @@ limitations under the License. } } +.mx_ContextualMenu { + .mx_MessageComposer_button { + padding-left: calc(var(--size) + 6px); + } +} + .mx_MessageComposer_button { --size: 26px; position: relative; - margin-right: 6px; cursor: pointer; height: var(--size); line-height: var(--size); width: auto; - padding-left: calc(var(--size) + 5px); + padding-left: var(--size); border-radius: 100%; + margin-right: 6px; + + &:last-child { + margin-right: auto; + } &::before { content: ''; From 672cdf5330d0207767f94e49bfefad00fec9ac72 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 22 Sep 2021 14:35:24 +0100 Subject: [PATCH 08/42] Prepare changelog for v3.31.0-rc.2 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2da9dad17..71ddb1c5fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Changes in [3.31.0-rc.2](https://github.com/vector-im/element-desktop/releases/tag/v3.31.0-rc.2) (2021-09-22) +============================================================================================================= + +## 🐛 Bug Fixes + * Fix spacing for message composer buttons ([\#6854](https://github.com/matrix-org/matrix-react-sdk/pull/6854)). + Changes in [3.31.0-rc.1](https://github.com/vector-im/element-desktop/releases/tag/v3.31.0-rc.1) (2021-09-21) ============================================================================================================= From a22e2ef874767da9250f97913642c66f87f61006 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 22 Sep 2021 14:35:24 +0100 Subject: [PATCH 09/42] v3.31.0-rc.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index edf4c88c61..9593f74aae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.31.0-rc.1", + "version": "3.31.0-rc.2", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From a9f7ab785cae4604914b67df70ed2be5e7a3afb7 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 23 Sep 2021 10:59:33 +0100 Subject: [PATCH 10/42] Change CIDER state persistence key to cater for threads Extending the CIDER state persistence to threads and make sure that SendMessageComposer can restore drafts for specific threads This also prevents the thread's replyToEvent to leaking in the room composer --- .../views/rooms/SendMessageComposer.tsx | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 05c41f74bf..4138b00bdf 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -164,6 +164,20 @@ export default class SendMessageComposer extends React.Component { window.addEventListener("beforeunload", this.saveStoredEditorState); } + public componentDidUpdate(prevProps: IProps): void { + const replyToEventChanged = this.props.replyToEvent !== prevProps.replyToEvent; + if (replyToEventChanged) { + this.model.reset([]); + } + + if (this.props.replyInThread && this.props.replyToEvent && (!prevProps.replyToEvent || replyToEventChanged)) { + const partCreator = new CommandPartCreator(this.props.room, this.context); + const parts = this.restoreStoredEditorState(partCreator) || []; + this.model.reset(parts); + this.editorRef.current?.focus(); + } + } + private onKeyDown = (event: KeyboardEvent): void => { // ignore any keypress while doing IME compositions if (this.editorRef.current?.isComposing(event)) { @@ -484,7 +498,12 @@ export default class SendMessageComposer extends React.Component { } private get editorStateKey() { - return `mx_cider_state_${this.props.room.roomId}`; + let key = `mx_cider_state_${this.props.room.roomId}`; + const thread = this.props.replyToEvent?.getThread(); + if (thread) { + key += `_${thread.id}`; + } + return key; } private clearStoredEditorState(): void { @@ -492,6 +511,10 @@ export default class SendMessageComposer extends React.Component { } private restoreStoredEditorState(partCreator: PartCreator): Part[] { + if (this.props.replyInThread && !this.props.replyToEvent) { + return null; + } + const json = localStorage.getItem(this.editorStateKey); if (json) { try { From 836e427dc8fd275c418acac6e142f4726e8c5454 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 24 Sep 2021 11:13:57 +0100 Subject: [PATCH 11/42] Scope editor reset when reply changed for thread view --- src/components/views/rooms/SendMessageComposer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 4138b00bdf..cc27ccf153 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -165,7 +165,7 @@ export default class SendMessageComposer extends React.Component { } public componentDidUpdate(prevProps: IProps): void { - const replyToEventChanged = this.props.replyToEvent !== prevProps.replyToEvent; + const replyToEventChanged = this.props.replyInThread && (this.props.replyToEvent !== prevProps.replyToEvent); if (replyToEventChanged) { this.model.reset([]); } From 9f26cca2562e22fe97b1f3ef6b53514e3ec6c04f Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 24 Sep 2021 14:35:00 +0100 Subject: [PATCH 12/42] Fix the User View Extreme confusion between users and members, and presumably we were passing one as the other and they were similar enough that it worked. UserView still makes fake member objects but this at least fixes the user view in the right panel. Fixes https://github.com/vector-im/element-web/issues/19158 --- src/components/structures/RightPanel.tsx | 9 ++++----- src/components/structures/UserView.tsx | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index f626bb67d9..5d9d2a0b6a 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -18,7 +18,6 @@ limitations under the License. import React from 'react'; import { Room } from "matrix-js-sdk/src/models/room"; import { RoomState } from "matrix-js-sdk/src/models/room-state"; -import { User } from "matrix-js-sdk/src/models/user"; import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; @@ -59,7 +58,7 @@ import { SetRightPanelPhasePayload } from '../../dispatcher/payloads/SetRightPan interface IProps { room?: Room; // if showing panels for a given room, this is set groupId?: string; // if showing panels for a given group, this is set - user?: User; // used if we know the user ahead of opening the panel + member?: RoomMember; // used if we know the room member ahead of opening the panel resizeNotifier: ResizeNotifier; permalinkCreator?: RoomPermalinkCreator; e2eStatus?: E2EStatus; @@ -100,10 +99,10 @@ export default class RightPanel extends React.Component { // Helper function to split out the logic for getPhaseFromProps() and the constructor // as both are called at the same time in the constructor. - private getUserForPanel() { + private getUserForPanel(): RoomMember { if (this.state && this.state.member) return this.state.member; const lastParams = RightPanelStore.getSharedInstance().roomPanelPhaseParams; - return this.props.user || lastParams['member']; + return this.props.member || lastParams['member']; } // gets the current phase from the props and also maybe the store @@ -225,7 +224,7 @@ export default class RightPanel extends React.Component { // XXX: There are three different ways of 'closing' this panel depending on what state // things are in... this knows far more than it should do about the state of the rest // of the app and is generally a bit silly. - if (this.props.user) { + if (this.props.member) { // If we have a user prop then we're displaying a user from the 'user' page type // in LoggedInView, so need to change the page type to close the panel (we switch // to the home page which is not obviously the correct thing to do, but I'm not sure diff --git a/src/components/structures/UserView.tsx b/src/components/structures/UserView.tsx index 0b686995fd..32168e8449 100644 --- a/src/components/structures/UserView.tsx +++ b/src/components/structures/UserView.tsx @@ -86,8 +86,8 @@ export default class UserView extends React.Component { public render(): JSX.Element { if (this.state.loading) { return ; - } else if (this.state.member?.user) { - const panel = ; + } else if (this.state.member) { + const panel = ; return ( ); From bffebb9304099822e06838917ed2894a6638ebc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 24 Sep 2021 20:37:59 +0200 Subject: [PATCH 13/42] Convert index to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/usercontent/{index.js => index.ts} | 34 ++++++++++++-------------- 1 file changed, 16 insertions(+), 18 deletions(-) rename src/usercontent/{index.js => index.ts} (61%) diff --git a/src/usercontent/index.js b/src/usercontent/index.ts similarity index 61% rename from src/usercontent/index.js rename to src/usercontent/index.ts index c03126ec80..f143f16720 100644 --- a/src/usercontent/index.js +++ b/src/usercontent/index.ts @@ -1,5 +1,5 @@ let hasCalled = false; -function remoteRender(event) { +function remoteRender(event: MessageEvent): void { const data = event.data; // If we're handling secondary calls, start from scratch @@ -11,10 +11,11 @@ function remoteRender(event) { const img = document.createElement("span"); // we'll mask it as an image img.id = "img"; - const a = document.createElement("a"); + const a: HTMLAnchorElement = document.createElement("a"); a.id = "a"; a.rel = "noreferrer noopener"; a.download = data.download; + // @ts-ignore a.style = data.style; a.style.fontFamily = "Arial, Helvetica, Sans-Serif"; a.href = window.URL.createObjectURL(data.blob); @@ -23,24 +24,21 @@ function remoteRender(event) { // Apply image style after so we can steal the anchor's colour. // Style copied from a rendered version of mx_MFileBody_download_icon - img.style = (data.imgStyle || "" + - "width: 12px; height: 12px;" + - "-webkit-mask-size: 12px;" + - "mask-size: 12px;" + - "-webkit-mask-position: center;" + - "mask-position: center;" + - "-webkit-mask-repeat: no-repeat;" + - "mask-repeat: no-repeat;" + - "display: inline-block;") + "" + - - // Always add these styles - `-webkit-mask-image: url('${data.imgSrc}');` + - `mask-image: url('${data.imgSrc}');` + - `background-color: ${a.style.color};`; + // @ts-ignore + img.style = data.imgStyle ?? ""; + img.style.width = "12px"; + img.style.height = "12px"; + img.style.webkitMaskSize = "12px"; + img.style.webkitMaskPosition = "center"; + img.style.webkitMaskRepeat = "no-repeat"; + img.style.display = "inline-block"; + img.style.webkitMaskImage = `url('${data.imgSrc}')`; + img.style.backgroundColor = `${a.style.color}`; const body = document.body; // Don't display scrollbars if the link takes more than one line to display. - body.style = "margin: 0px; overflow: hidden"; + body.style .margin = "0px"; + body.style.overflow = "hidden"; body.appendChild(a); if (event.data.auto) { @@ -48,7 +46,7 @@ function remoteRender(event) { } } -window.onmessage = function(e) { +window.onmessage = function(e: MessageEvent): void { if (e.origin === window.location.origin) { if (e.data.blob) remoteRender(e); } From 27f74b3ebd0a72e192e2d9bc7c07de00813b47c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 25 Sep 2021 07:53:38 +0200 Subject: [PATCH 14/42] Remove unnecessary type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/usercontent/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/usercontent/index.ts b/src/usercontent/index.ts index f143f16720..82e8ee0d81 100644 --- a/src/usercontent/index.ts +++ b/src/usercontent/index.ts @@ -11,7 +11,7 @@ function remoteRender(event: MessageEvent): void { const img = document.createElement("span"); // we'll mask it as an image img.id = "img"; - const a: HTMLAnchorElement = document.createElement("a"); + const a = document.createElement("a"); a.id = "a"; a.rel = "noreferrer noopener"; a.download = data.download; From 1a2476609c0bea8505063eae00a08b5c37930c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 25 Sep 2021 07:55:40 +0200 Subject: [PATCH 15/42] Fix code to be equivalent to the previous one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/usercontent/index.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/usercontent/index.ts b/src/usercontent/index.ts index 82e8ee0d81..eaa8c86ec6 100644 --- a/src/usercontent/index.ts +++ b/src/usercontent/index.ts @@ -24,16 +24,19 @@ function remoteRender(event: MessageEvent): void { // Apply image style after so we can steal the anchor's colour. // Style copied from a rendered version of mx_MFileBody_download_icon - // @ts-ignore - img.style = data.imgStyle ?? ""; - img.style.width = "12px"; - img.style.height = "12px"; - img.style.webkitMaskSize = "12px"; - img.style.webkitMaskPosition = "center"; - img.style.webkitMaskRepeat = "no-repeat"; - img.style.display = "inline-block"; - img.style.webkitMaskImage = `url('${data.imgSrc}')`; - img.style.backgroundColor = `${a.style.color}`; + if (data.imgStyle) { + // @ts-ignore + img.style = data.imgStyle; + } else { + img.style.width = "12px"; + img.style.height = "12px"; + img.style.webkitMaskSize = "12px"; + img.style.webkitMaskPosition = "center"; + img.style.webkitMaskRepeat = "no-repeat"; + img.style.display = "inline-block"; + img.style.webkitMaskImage = `url('${data.imgSrc}')`; + img.style.backgroundColor = `${a.style.color}`; + } const body = document.body; // Don't display scrollbars if the link takes more than one line to display. From 3450e91e32cc830a07226f7a2cfe12a0cb8a22b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 25 Sep 2021 09:58:01 +0200 Subject: [PATCH 16/42] Improve typing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/usercontent/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/usercontent/index.ts b/src/usercontent/index.ts index eaa8c86ec6..df551e88e6 100644 --- a/src/usercontent/index.ts +++ b/src/usercontent/index.ts @@ -8,10 +8,10 @@ function remoteRender(event: MessageEvent): void { } hasCalled = true; - const img = document.createElement("span"); // we'll mask it as an image + const img: HTMLSpanElement = document.createElement("span"); // we'll mask it as an image img.id = "img"; - const a = document.createElement("a"); + const a: HTMLAnchorElement = document.createElement("a"); a.id = "a"; a.rel = "noreferrer noopener"; a.download = data.download; From bee85a0bcd16bae07a53272484901f95dc6dd5f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 24 Sep 2021 19:55:52 +0200 Subject: [PATCH 17/42] Convert EditHistoryMessage to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- ...storyMessage.js => EditHistoryMessage.tsx} | 85 ++++++++++--------- .../views/messages/RedactedBody.tsx | 7 +- 2 files changed, 53 insertions(+), 39 deletions(-) rename src/components/views/messages/{EditHistoryMessage.js => EditHistoryMessage.tsx} (73%) diff --git a/src/components/views/messages/EditHistoryMessage.js b/src/components/views/messages/EditHistoryMessage.tsx similarity index 73% rename from src/components/views/messages/EditHistoryMessage.js rename to src/components/views/messages/EditHistoryMessage.tsx index 2c6a567f6b..1abed87b76 100644 --- a/src/components/views/messages/EditHistoryMessage.js +++ b/src/components/views/messages/EditHistoryMessage.tsx @@ -15,107 +15,112 @@ limitations under the License. */ import React, { createRef } from 'react'; -import PropTypes from 'prop-types'; import * as HtmlUtils from '../../../HtmlUtils'; import { editBodyDiffToHtml } from '../../../utils/MessageDiffUtils'; import { formatTime } from '../../../DateUtils'; -import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; +import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { pillifyLinks, unmountPills } from '../../../utils/pillify'; import { _t } from '../../../languageHandler'; -import * as sdk from '../../../index'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import Modal from '../../../Modal'; import classNames from 'classnames'; import RedactedBody from "./RedactedBody"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import AccessibleButton from "../elements/AccessibleButton"; +import ConfirmAndWaitRedactDialog from "../dialogs/ConfirmAndWaitRedactDialog"; +import ViewSource from "../../structures/ViewSource"; function getReplacedContent(event) { const originalContent = event.getOriginalContent(); return originalContent["m.new_content"] || originalContent; } -@replaceableComponent("views.messages.EditHistoryMessage") -export default class EditHistoryMessage extends React.PureComponent { - static propTypes = { - // the message event being edited - mxEvent: PropTypes.instanceOf(MatrixEvent).isRequired, - previousEdit: PropTypes.instanceOf(MatrixEvent), - isBaseEvent: PropTypes.bool, - }; +interface IProps { + // the message event being edited + mxEvent: MatrixEvent; + previousEdit?: MatrixEvent; + isBaseEvent?: boolean; + isTwelveHour?: boolean; +} - constructor(props) { +interface IState { + canRedact: boolean; + sendStatus: EventStatus; +} + +@replaceableComponent("views.messages.EditHistoryMessage") +export default class EditHistoryMessage extends React.PureComponent { + private content = createRef(); + private pills: Element[] = []; + + constructor(props: IProps) { super(props); + const cli = MatrixClientPeg.get(); const { userId } = cli.credentials; const event = this.props.mxEvent; const room = cli.getRoom(event.getRoomId()); if (event.localRedactionEvent()) { - event.localRedactionEvent().on("status", this._onAssociatedStatusChanged); + event.localRedactionEvent().on("status", this.onAssociatedStatusChanged); } const canRedact = room.currentState.maySendRedactionForEvent(event, userId); this.state = { canRedact, sendStatus: event.getAssociatedStatus() }; - - this._content = createRef(); - this._pills = []; } - _onAssociatedStatusChanged = () => { + private onAssociatedStatusChanged = (): void => { this.setState({ sendStatus: this.props.mxEvent.getAssociatedStatus() }); }; - _onRedactClick = async () => { + private onRedactClick = async (): Promise => { const event = this.props.mxEvent; const cli = MatrixClientPeg.get(); - const ConfirmAndWaitRedactDialog = sdk.getComponent("dialogs.ConfirmAndWaitRedactDialog"); Modal.createTrackedDialog('Confirm Redact Dialog', 'Edit history', ConfirmAndWaitRedactDialog, { redact: () => cli.redactEvent(event.getRoomId(), event.getId()), }, 'mx_Dialog_confirmredact'); }; - _onViewSourceClick = () => { - const ViewSource = sdk.getComponent('structures.ViewSource'); + private onViewSourceClick = (): void => { Modal.createTrackedDialog('View Event Source', 'Edit history', ViewSource, { mxEvent: this.props.mxEvent, }, 'mx_Dialog_viewsource'); }; - pillifyLinks() { + private pillifyLinks(): void { // not present for redacted events - if (this._content.current) { - pillifyLinks(this._content.current.children, this.props.mxEvent, this._pills); + if (this.content.current) { + pillifyLinks(this.content.current.children, this.props.mxEvent, this.pills); } } - componentDidMount() { + public componentDidMount(): void { this.pillifyLinks(); } - componentWillUnmount() { - unmountPills(this._pills); + public componentWillUnmount(): void { + unmountPills(this.pills); const event = this.props.mxEvent; if (event.localRedactionEvent()) { - event.localRedactionEvent().off("status", this._onAssociatedStatusChanged); + event.localRedactionEvent().off("status", this.onAssociatedStatusChanged); } } - componentDidUpdate() { + public componentDidUpdate(): void { this.pillifyLinks(); } - _renderActionBar() { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + private renderActionBar(): JSX.Element { // hide the button when already redacted let redactButton; if (!this.props.mxEvent.isRedacted() && !this.props.isBaseEvent && this.state.canRedact) { redactButton = ( - + { _t("Remove") } ); } const viewSourceButton = ( - + { _t("View Source") } ); @@ -128,7 +133,7 @@ export default class EditHistoryMessage extends React.PureComponent { ); } - render() { + public render(): JSX.Element { const { mxEvent } = this.props; const content = getReplacedContent(mxEvent); let contentContainer; @@ -139,18 +144,22 @@ export default class EditHistoryMessage extends React.PureComponent { if (this.props.previousEdit) { contentElements = editBodyDiffToHtml(getReplacedContent(this.props.previousEdit), content); } else { - contentElements = HtmlUtils.bodyToHtml(content, null, { stripReplyFallback: true }); + contentElements = HtmlUtils.bodyToHtml( + content, + null, + { stripReplyFallback: true, returnString: false }, + ); } if (mxEvent.getContent().msgtype === "m.emote") { const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); contentContainer = ( -
*  +
{ name }  { contentElements }
); } else { - contentContainer =
{ contentElements }
; + contentContainer =
{ contentElements }
; } } @@ -167,7 +176,7 @@ export default class EditHistoryMessage extends React.PureComponent {
{ timestamp } { contentContainer } - { this._renderActionBar() } + { this.renderActionBar() }
diff --git a/src/components/views/messages/RedactedBody.tsx b/src/components/views/messages/RedactedBody.tsx index c2e137c97b..7202e3a2e5 100644 --- a/src/components/views/messages/RedactedBody.tsx +++ b/src/components/views/messages/RedactedBody.tsx @@ -21,8 +21,13 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { formatFullDate } from "../../../DateUtils"; import SettingsStore from "../../../settings/SettingsStore"; import { IBodyProps } from "./IBodyProps"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -const RedactedBody = React.forwardRef(({ mxEvent }, ref) => { +interface IProps { + mxEvent: MatrixEvent; +} + +const RedactedBody = React.forwardRef(({ mxEvent }, ref) => { const cli: MatrixClient = useContext(MatrixClientContext); let text = _t("Message deleted"); From ff1c1fbc7852b7b86aeb3bd899a974f638c6e14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 24 Sep 2021 19:58:28 +0200 Subject: [PATCH 18/42] Convert MjolnirBody to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../{MjolnirBody.js => MjolnirBody.tsx} | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) rename src/components/views/messages/{MjolnirBody.js => MjolnirBody.tsx} (73%) diff --git a/src/components/views/messages/MjolnirBody.js b/src/components/views/messages/MjolnirBody.tsx similarity index 73% rename from src/components/views/messages/MjolnirBody.js rename to src/components/views/messages/MjolnirBody.tsx index 23f255b569..c21a9a353e 100644 --- a/src/components/views/messages/MjolnirBody.js +++ b/src/components/views/messages/MjolnirBody.tsx @@ -15,22 +15,22 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + +interface IProps { + mxEvent: MatrixEvent; + onMessageAllowed: () => void; +} @replaceableComponent("views.messages.MjolnirBody") -export default class MjolnirBody extends React.Component { - static propTypes = { - mxEvent: PropTypes.object.isRequired, - onMessageAllowed: PropTypes.func.isRequired, - }; - - constructor() { - super(); +export default class MjolnirBody extends React.Component { + constructor(props: IProps) { + super(props); } - _onAllowClick = (e) => { + private onAllowClick = (e: React.MouseEvent): void => { e.preventDefault(); e.stopPropagation(); @@ -39,11 +39,11 @@ export default class MjolnirBody extends React.Component { this.props.onMessageAllowed(); }; - render() { + public render(): JSX.Element { return (
{ _t( "You have ignored this user, so their message is hidden. Show anyways.", - {}, { a: (sub) => { sub } }, + {}, { a: (sub) => { sub } }, ) }
); } From 421fa1ab69b2d9f9cb512f5395d6fbd34301312b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 24 Sep 2021 20:06:00 +0200 Subject: [PATCH 19/42] Convert RoomAvatarEvent to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- ...RoomAvatarEvent.js => RoomAvatarEvent.tsx} | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) rename src/components/views/messages/{RoomAvatarEvent.js => RoomAvatarEvent.tsx} (88%) diff --git a/src/components/views/messages/RoomAvatarEvent.js b/src/components/views/messages/RoomAvatarEvent.tsx similarity index 88% rename from src/components/views/messages/RoomAvatarEvent.js rename to src/components/views/messages/RoomAvatarEvent.tsx index 9832332311..c29460b002 100644 --- a/src/components/views/messages/RoomAvatarEvent.js +++ b/src/components/views/messages/RoomAvatarEvent.tsx @@ -17,23 +17,24 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; -import * as sdk from '../../../index'; import Modal from '../../../Modal'; import AccessibleButton from '../elements/AccessibleButton'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import RoomAvatar from "../avatars/RoomAvatar"; +import ImageView from "../elements/ImageView"; + +interface IProps { + /* the MatrixEvent to show */ + mxEvent: MatrixEvent; +} @replaceableComponent("views.messages.RoomAvatarEvent") -export default class RoomAvatarEvent extends React.Component { - static propTypes = { - /* the MatrixEvent to show */ - mxEvent: PropTypes.object.isRequired, - }; - - onAvatarClick = () => { +export default class RoomAvatarEvent extends React.Component { + private onAvatarClick = (): void => { const cli = MatrixClientPeg.get(); const ev = this.props.mxEvent; const httpUrl = mediaFromMxc(ev.getContent().url).srcHttp; @@ -44,7 +45,6 @@ export default class RoomAvatarEvent extends React.Component { roomName: room ? room.name : '', }); - const ImageView = sdk.getComponent("elements.ImageView"); const params = { src: httpUrl, name: text, @@ -52,10 +52,9 @@ export default class RoomAvatarEvent extends React.Component { Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true); }; - render() { + public render(): JSX.Element { const ev = this.props.mxEvent; const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); - const RoomAvatar = sdk.getComponent("avatars.RoomAvatar"); if (!ev.getContent().url || ev.getContent().url.trim().length === 0) { return ( From c287d15fa0b314cbe52d3ab552ad144c64e75fdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 24 Sep 2021 20:08:15 +0200 Subject: [PATCH 20/42] Convert RoomCreate to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../{RoomCreate.js => RoomCreate.tsx} | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) rename src/components/views/messages/{RoomCreate.js => RoomCreate.tsx} (85%) diff --git a/src/components/views/messages/RoomCreate.js b/src/components/views/messages/RoomCreate.tsx similarity index 85% rename from src/components/views/messages/RoomCreate.js rename to src/components/views/messages/RoomCreate.tsx index a0bc8daa64..c846ba5632 100644 --- a/src/components/views/messages/RoomCreate.js +++ b/src/components/views/messages/RoomCreate.tsx @@ -16,7 +16,6 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import dis from '../../../dispatcher/dispatcher'; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; @@ -24,15 +23,16 @@ import { _t } from '../../../languageHandler'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import EventTileBubble from "./EventTileBubble"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + +interface IProps { + /* the MatrixEvent to show */ + mxEvent: MatrixEvent; +} @replaceableComponent("views.messages.RoomCreate") -export default class RoomCreate extends React.Component { - static propTypes = { - /* the MatrixEvent to show */ - mxEvent: PropTypes.object.isRequired, - }; - - _onLinkClicked = e => { +export default class RoomCreate extends React.Component { + private onLinkClicked = (e: React.MouseEvent): void => { e.preventDefault(); const predecessor = this.props.mxEvent.getContent()['predecessor']; @@ -45,7 +45,7 @@ export default class RoomCreate extends React.Component { }); }; - render() { + public render(): JSX.Element { const predecessor = this.props.mxEvent.getContent()['predecessor']; if (predecessor === undefined) { return
; // We should never have been instantiated in this case @@ -55,7 +55,7 @@ export default class RoomCreate extends React.Component { permalinkCreator.load(); const predecessorPermalink = permalinkCreator.forEvent(predecessor['event_id']); const link = ( - + { _t("Click here to see older messages.") } ); From fb5c18caa0e9bb0c700e1bf95da17b054e2da079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 24 Sep 2021 20:04:03 +0200 Subject: [PATCH 21/42] Convert MKeyVerificationConclusion to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- ...sion.js => MKeyVerificationConclusion.tsx} | 47 ++++++++++--------- src/components/views/rooms/EventTile.tsx | 4 +- 2 files changed, 26 insertions(+), 25 deletions(-) rename src/components/views/messages/{MKeyVerificationConclusion.js => MKeyVerificationConclusion.tsx} (74%) diff --git a/src/components/views/messages/MKeyVerificationConclusion.js b/src/components/views/messages/MKeyVerificationConclusion.tsx similarity index 74% rename from src/components/views/messages/MKeyVerificationConclusion.js rename to src/components/views/messages/MKeyVerificationConclusion.tsx index a5f12df47d..3e2ae9fa9b 100644 --- a/src/components/views/messages/MKeyVerificationConclusion.js +++ b/src/components/views/messages/MKeyVerificationConclusion.tsx @@ -16,44 +16,50 @@ limitations under the License. import React from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; -import { getNameForEventRoom, userLabelForEventRoom } - from '../../../utils/KeyVerificationStateObserver'; +import { getNameForEventRoom, userLabelForEventRoom } from '../../../utils/KeyVerificationStateObserver'; import EventTileBubble from "./EventTileBubble"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; +import { EventType } from "matrix-js-sdk/src/@types/event"; + +interface IProps { + /* the MatrixEvent to show */ + mxEvent: MatrixEvent; +} @replaceableComponent("views.messages.MKeyVerificationConclusion") -export default class MKeyVerificationConclusion extends React.Component { - constructor(props) { +export default class MKeyVerificationConclusion extends React.Component { + constructor(props: IProps) { super(props); } - componentDidMount() { + public componentDidMount(): void { const request = this.props.mxEvent.verificationRequest; if (request) { - request.on("change", this._onRequestChanged); + request.on("change", this.onRequestChanged); } - MatrixClientPeg.get().on("userTrustStatusChanged", this._onTrustChanged); + MatrixClientPeg.get().on("userTrustStatusChanged", this.onTrustChanged); } - componentWillUnmount() { + public componentWillUnmount(): void { const request = this.props.mxEvent.verificationRequest; if (request) { - request.off("change", this._onRequestChanged); + request.off("change", this.onRequestChanged); } const cli = MatrixClientPeg.get(); if (cli) { - cli.removeListener("userTrustStatusChanged", this._onTrustChanged); + cli.removeListener("userTrustStatusChanged", this.onTrustChanged); } } - _onRequestChanged = () => { + private onRequestChanged = () => { this.forceUpdate(); }; - _onTrustChanged = (userId, status) => { + private onTrustChanged = (userId: string): void => { const { mxEvent } = this.props; const request = mxEvent.verificationRequest; if (!request || request.otherUserId !== userId) { @@ -62,17 +68,17 @@ export default class MKeyVerificationConclusion extends React.Component { this.forceUpdate(); }; - _shouldRender(mxEvent, request) { + public static shouldRender(mxEvent: MatrixEvent, request: VerificationRequest): boolean { // normally should not happen if (!request) { return false; } // .cancel event that was sent after the verification finished, ignore - if (mxEvent.getType() === "m.key.verification.cancel" && !request.cancelled) { + if (mxEvent.getType() === EventType.KeyVerificationCancel && !request.cancelled) { return false; } // .done event that was sent after the verification cancelled, ignore - if (mxEvent.getType() === "m.key.verification.done" && !request.done) { + if (mxEvent.getType() === EventType.KeyVerificationDone && !request.done) { return false; } @@ -89,11 +95,11 @@ export default class MKeyVerificationConclusion extends React.Component { return true; } - render() { + public render(): JSX.Element { const { mxEvent } = this.props; const request = mxEvent.verificationRequest; - if (!this._shouldRender(mxEvent, request)) { + if (!MKeyVerificationConclusion.shouldRender(mxEvent, request)) { return null; } @@ -129,8 +135,3 @@ export default class MKeyVerificationConclusion extends React.Component { return null; } } - -MKeyVerificationConclusion.propTypes = { - /* the MatrixEvent to show */ - mxEvent: PropTypes.object.isRequired, -}; diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index e1f0eb5368..d1ac06b199 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -58,6 +58,7 @@ import ReactionsRow from '../messages/ReactionsRow'; import { getEventDisplayInfo } from '../../../utils/EventUtils'; import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; import SettingsStore from "../../../settings/SettingsStore"; +import MKeyVerificationConclusion from "../messages/MKeyVerificationConclusion"; const eventTileTypes = { [EventType.RoomMessage]: 'messages.MessageEvent', @@ -144,8 +145,7 @@ export function getHandlerTile(ev) { // XXX: This is extremely a hack. Possibly these components should have an interface for // declining to render? if (type === "m.key.verification.cancel" || type === "m.key.verification.done") { - const MKeyVerificationConclusion = sdk.getComponent("messages.MKeyVerificationConclusion"); - if (!MKeyVerificationConclusion.prototype._shouldRender.call(null, ev, ev.request)) { + if (!MKeyVerificationConclusion.shouldRender(ev, ev.request)) { return; } } From 755bfb4562ef1faba9c2bd415bb2d906ca72a446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 26 Sep 2021 15:02:07 +0200 Subject: [PATCH 22/42] Convert DirectoryUtils to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/utils/{DirectoryUtils.js => DirectoryUtils.ts} | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) rename src/utils/{DirectoryUtils.js => DirectoryUtils.ts} (81%) diff --git a/src/utils/DirectoryUtils.js b/src/utils/DirectoryUtils.ts similarity index 81% rename from src/utils/DirectoryUtils.js rename to src/utils/DirectoryUtils.ts index 577a6441f8..255ae0e3fd 100644 --- a/src/utils/DirectoryUtils.js +++ b/src/utils/DirectoryUtils.ts @@ -14,9 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { IInstance } from "matrix-js-sdk/src/client"; +import { Protocols } from "../components/views/directory/NetworkDropdown"; + // Find a protocol 'instance' with a given instance_id // in the supplied protocols dict -export function instanceForInstanceId(protocols, instanceId) { +export function instanceForInstanceId(protocols: Protocols, instanceId: string): IInstance { if (!instanceId) return null; for (const proto of Object.keys(protocols)) { if (!protocols[proto].instances && protocols[proto].instances instanceof Array) continue; @@ -28,7 +31,7 @@ export function instanceForInstanceId(protocols, instanceId) { // given an instance_id, return the name of the protocol for // that instance ID in the supplied protocols dict -export function protocolNameForInstanceId(protocols, instanceId) { +export function protocolNameForInstanceId(protocols: Protocols, instanceId: string): string { if (!instanceId) return null; for (const proto of Object.keys(protocols)) { if (!protocols[proto].instances && protocols[proto].instances instanceof Array) continue; From 0c6bf950f6d84bd5bea5a6a22a3ff09228fce1e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 26 Sep 2021 15:03:50 +0200 Subject: [PATCH 23/42] Convert HostingLink to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/utils/{HostingLink.js => HostingLink.ts} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/utils/{HostingLink.js => HostingLink.ts} (91%) diff --git a/src/utils/HostingLink.js b/src/utils/HostingLink.ts similarity index 91% rename from src/utils/HostingLink.js rename to src/utils/HostingLink.ts index 134e045ca2..f8c0f12c3f 100644 --- a/src/utils/HostingLink.js +++ b/src/utils/HostingLink.ts @@ -17,7 +17,7 @@ limitations under the License. import SdkConfig from '../SdkConfig'; import { MatrixClientPeg } from '../MatrixClientPeg'; -export function getHostingLink(campaign) { +export function getHostingLink(campaign: string): string { const hostingLink = SdkConfig.get().hosting_signup_link; if (!hostingLink) return null; if (!campaign) return hostingLink; @@ -27,7 +27,7 @@ export function getHostingLink(campaign) { try { const hostingUrl = new URL(hostingLink); hostingUrl.searchParams.set("utm_campaign", campaign); - return hostingUrl.format(); + return hostingUrl.toString(); } catch (e) { return hostingLink; } From aa10cf45a5876880474ba82a07096732d5f1d45b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 26 Sep 2021 15:05:51 +0200 Subject: [PATCH 24/42] Convert KeyVerificationStateObserver to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- ...cationStateObserver.js => KeyVerificationStateObserver.ts} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/utils/{KeyVerificationStateObserver.js => KeyVerificationStateObserver.ts} (86%) diff --git a/src/utils/KeyVerificationStateObserver.js b/src/utils/KeyVerificationStateObserver.ts similarity index 86% rename from src/utils/KeyVerificationStateObserver.js rename to src/utils/KeyVerificationStateObserver.ts index 023cdf3a75..e4fae3f3c8 100644 --- a/src/utils/KeyVerificationStateObserver.js +++ b/src/utils/KeyVerificationStateObserver.ts @@ -17,14 +17,14 @@ limitations under the License. import { MatrixClientPeg } from '../MatrixClientPeg'; import { _t } from '../languageHandler'; -export function getNameForEventRoom(userId, roomId) { +export function getNameForEventRoom(userId: string, roomId: string): string { const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); const member = room && room.getMember(userId); return member ? member.name : userId; } -export function userLabelForEventRoom(userId, roomId) { +export function userLabelForEventRoom(userId: string, roomId: string): string { const name = getNameForEventRoom(userId, roomId); if (name !== userId) { return _t("%(name)s (%(userId)s)", { name, userId }); From ee344efb3d725b7c4b0c1a4acdd0c2e707b9ee38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 26 Sep 2021 15:20:42 +0200 Subject: [PATCH 25/42] Convert MegolmExportEncryption to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- ...ncryption.js => MegolmExportEncryption.ts} | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) rename src/utils/{MegolmExportEncryption.js => MegolmExportEncryption.ts} (90%) diff --git a/src/utils/MegolmExportEncryption.js b/src/utils/MegolmExportEncryption.ts similarity index 90% rename from src/utils/MegolmExportEncryption.js rename to src/utils/MegolmExportEncryption.ts index 8e7ee2005d..4fab271015 100644 --- a/src/utils/MegolmExportEncryption.js +++ b/src/utils/MegolmExportEncryption.ts @@ -26,17 +26,17 @@ const subtleCrypto = window.crypto.subtle || window.crypto.webkitSubtle; * Make an Error object which has a friendlyText property which is already * translated and suitable for showing to the user. * - * @param {string} msg message for the exception + * @param {string} message message for the exception * @param {string} friendlyText * @returns {Error} */ -function friendlyError(msg, friendlyText) { - const e = new Error(msg); - e.friendlyText = friendlyText; - return e; +function friendlyError( + message: string, friendlyText: string, +): { message: string, friendlyText: string } { + return { message, friendlyText }; } -function cryptoFailMsg() { +function cryptoFailMsg(): string { return _t('Your browser does not support the required cryptography extensions'); } @@ -49,7 +49,7 @@ function cryptoFailMsg() { * * */ -export async function decryptMegolmKeyFile(data, password) { +export async function decryptMegolmKeyFile(data: ArrayBuffer, password: string): Promise { const body = unpackMegolmKeyFile(data); const brand = SdkConfig.get().brand; @@ -124,7 +124,11 @@ export async function decryptMegolmKeyFile(data, password) { * key-derivation function. * @return {Promise} promise for encrypted output */ -export async function encryptMegolmKeyFile(data, password, options) { +export async function encryptMegolmKeyFile( + data: string, + password: string, + options?: { kdf_rounds?: number }, // eslint-disable-line camelcase +): Promise { options = options || {}; const kdfRounds = options.kdf_rounds || 500000; @@ -196,7 +200,7 @@ export async function encryptMegolmKeyFile(data, password, options) { * @param {String} password password * @return {Promise<[CryptoKey, CryptoKey]>} promise for [aes key, hmac key] */ -async function deriveKeys(salt, iterations, password) { +async function deriveKeys(salt: Uint8Array, iterations: number, password: string): Promise<[CryptoKey, CryptoKey]> { const start = new Date(); let key; @@ -229,7 +233,7 @@ async function deriveKeys(salt, iterations, password) { } const now = new Date(); - logger.log("E2e import/export: deriveKeys took " + (now - start) + "ms"); + logger.log("E2e import/export: deriveKeys took " + (now.getTime() - start.getTime()) + "ms"); const aesKey = keybits.slice(0, 32); const hmacKey = keybits.slice(32); @@ -271,7 +275,7 @@ const TRAILER_LINE = '-----END MEGOLM SESSION DATA-----'; * @param {ArrayBuffer} data input file * @return {Uint8Array} unbase64ed content */ -function unpackMegolmKeyFile(data) { +function unpackMegolmKeyFile(data: ArrayBuffer): Uint8Array { // parse the file as a great big String. This should be safe, because there // should be no non-ASCII characters, and it means that we can do string // comparisons to find the header and footer, and feed it into window.atob. @@ -279,6 +283,7 @@ function unpackMegolmKeyFile(data) { // look for the start line let lineStart = 0; + // eslint-disable-next-line no-constant-condition while (1) { const lineEnd = fileStr.indexOf('\n', lineStart); if (lineEnd < 0) { @@ -297,6 +302,7 @@ function unpackMegolmKeyFile(data) { const dataStart = lineStart; // look for the end line + // eslint-disable-next-line no-constant-condition while (1) { const lineEnd = fileStr.indexOf('\n', lineStart); const line = fileStr.slice(lineStart, lineEnd < 0 ? undefined : lineEnd).trim(); @@ -324,7 +330,7 @@ function unpackMegolmKeyFile(data) { * @param {Uint8Array} data raw data * @return {ArrayBuffer} formatted file */ -function packMegolmKeyFile(data) { +function packMegolmKeyFile(data: Uint8Array): ArrayBuffer { // we split into lines before base64ing, because encodeBase64 doesn't deal // terribly well with large arrays. const LINE_LENGTH = (72 * 4 / 3); @@ -347,7 +353,7 @@ function packMegolmKeyFile(data) { * @param {Uint8Array} uint8Array The data to encode. * @return {string} The base64. */ -function encodeBase64(uint8Array) { +function encodeBase64(uint8Array: Uint8Array): string { // Misinterpt the Uint8Array as Latin-1. // window.btoa expects a unicode string with codepoints in the range 0-255. const latin1String = String.fromCharCode.apply(null, uint8Array); @@ -360,7 +366,7 @@ function encodeBase64(uint8Array) { * @param {string} base64 The base64 to decode. * @return {Uint8Array} The decoded data. */ -function decodeBase64(base64) { +function decodeBase64(base64: string): Uint8Array { // window.atob returns a unicode string with codepoints in the range 0-255. const latin1String = window.atob(base64); // Encode the string as a Uint8Array From b08e05ebe7c8217e5b06d01298ca18958281b5c9 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sun, 26 Sep 2021 10:24:46 -0400 Subject: [PATCH 26/42] Fix pills being cut off in message bubble layout Signed-off-by: Robin Townsend --- res/css/views/elements/_RichText.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/elements/_RichText.scss b/res/css/views/elements/_RichText.scss index b9d845ea7a..1043fd08d1 100644 --- a/res/css/views/elements/_RichText.scss +++ b/res/css/views/elements/_RichText.scss @@ -18,7 +18,7 @@ a.mx_Pill { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; - max-width: calc(100% - 1ch); + max-width: 100%; } .mx_Pill { From 2584b5bc6bfecd80bc548135fd971beb480f1482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 26 Sep 2021 16:04:28 +0200 Subject: [PATCH 27/42] Convert rageshake to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/@types/global.d.ts | 10 +++ src/rageshake/{rageshake.js => rageshake.ts} | 79 +++++++++++--------- 2 files changed, 52 insertions(+), 37 deletions(-) rename src/rageshake/{rageshake.js => rageshake.ts} (91%) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 8ad93fa960..06bb822f36 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -49,6 +49,7 @@ import PerformanceMonitor from "../performance"; import UIStore from "../stores/UIStore"; import { SetupEncryptionStore } from "../stores/SetupEncryptionStore"; import { RoomScrollStateStore } from "../stores/RoomScrollStateStore"; +import { ConsoleLogger, IndexedDBLogStore } from "../rageshake/rageshake"; /* eslint-disable @typescript-eslint/naming-convention */ @@ -223,6 +224,15 @@ declare global { ) => string; isReady: () => boolean; }; + + // eslint-disable-next-line no-var, camelcase + var mx_rage_logger: ConsoleLogger; + // eslint-disable-next-line no-var, camelcase + var mx_rage_initPromise: Promise; + // eslint-disable-next-line no-var, camelcase + var mx_rage_initStoragePromise: Promise; + // eslint-disable-next-line no-var, camelcase + var mx_rage_store: IndexedDBLogStore; } /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/src/rageshake/rageshake.js b/src/rageshake/rageshake.ts similarity index 91% rename from src/rageshake/rageshake.js rename to src/rageshake/rageshake.ts index 0dc36e3a9f..acf77c31c0 100644 --- a/src/rageshake/rageshake.js +++ b/src/rageshake/rageshake.ts @@ -46,12 +46,10 @@ const FLUSH_RATE_MS = 30 * 1000; const MAX_LOG_SIZE = 1024 * 1024 * 5; // 5 MB // A class which monkey-patches the global console and stores log lines. -class ConsoleLogger { - constructor() { - this.logs = ""; - } +export class ConsoleLogger { + private logs = ""; - monkeyPatch(consoleObj) { + public monkeyPatch(consoleObj: Console): void { // Monkey-patch console logging const consoleFunctionsToLevels = { log: "I", @@ -69,14 +67,14 @@ class ConsoleLogger { }); } - log(level, ...args) { + private log(level: string, ...args: (Error | DOMException | object | string)[]): void { // We don't know what locale the user may be running so use ISO strings const ts = new Date().toISOString(); // Convert objects and errors to helpful things args = args.map((arg) => { if (arg instanceof DOMException) { - return arg.message + ` (${arg.name} | ${arg.code}) ` + (arg.stack ? `\n${arg.stack}` : ''); + return arg.message + ` (${arg.name} | ${arg.code})`; } else if (arg instanceof Error) { return arg.message + (arg.stack ? `\n${arg.stack}` : ''); } else if (typeof (arg) === 'object') { @@ -118,7 +116,7 @@ class ConsoleLogger { * @param {boolean} keepLogs True to not delete logs after flushing. * @return {string} \n delimited log lines to flush. */ - flush(keepLogs) { + public flush(keepLogs?: boolean): string { // The ConsoleLogger doesn't care how these end up on disk, it just // flushes them to the caller. if (keepLogs) { @@ -131,27 +129,28 @@ class ConsoleLogger { } // A class which stores log lines in an IndexedDB instance. -class IndexedDBLogStore { - constructor(indexedDB, logger) { - this.indexedDB = indexedDB; - this.logger = logger; - this.id = "instance-" + Math.random() + Date.now(); - this.index = 0; - this.db = null; +export class IndexedDBLogStore { + private id: string; + private index = 0; + private db = null; + private flushPromise = null; + private flushAgainPromise = null; - // these promises are cleared as soon as fulfilled - this.flushPromise = null; - // set if flush() is called whilst one is ongoing - this.flushAgainPromise = null; + constructor( + private indexedDB: IDBFactory, + private logger: ConsoleLogger, + ) { + this.id = "instance-" + Math.random() + Date.now(); } /** * @return {Promise} Resolves when the store is ready. */ - connect() { + public connect(): Promise { const req = this.indexedDB.open("logs"); return new Promise((resolve, reject) => { - req.onsuccess = (event) => { + req.onsuccess = (event: Event) => { + // @ts-ignore this.db = event.target.result; // Periodically flush logs to local storage / indexeddb setInterval(this.flush.bind(this), FLUSH_RATE_MS); @@ -160,6 +159,7 @@ class IndexedDBLogStore { req.onerror = (event) => { const err = ( + // @ts-ignore "Failed to open log database: " + event.target.error.name ); console.error(err); @@ -168,6 +168,7 @@ class IndexedDBLogStore { // First time: Setup the object store req.onupgradeneeded = (event) => { + // @ts-ignore const db = event.target.result; const logObjStore = db.createObjectStore("logs", { keyPath: ["id", "index"], @@ -178,7 +179,7 @@ class IndexedDBLogStore { logObjStore.createIndex("id", "id", { unique: false }); logObjStore.add( - this._generateLogEntry( + this.generateLogEntry( new Date() + " ::: Log database was created.", ), ); @@ -186,7 +187,7 @@ class IndexedDBLogStore { const lastModifiedStore = db.createObjectStore("logslastmod", { keyPath: "id", }); - lastModifiedStore.add(this._generateLastModifiedTime()); + lastModifiedStore.add(this.generateLastModifiedTime()); }; }); } @@ -210,7 +211,7 @@ class IndexedDBLogStore { * * @return {Promise} Resolved when the logs have been flushed. */ - flush() { + public flush(): Promise { // check if a flush() operation is ongoing if (this.flushPromise) { if (this.flushAgainPromise) { @@ -227,7 +228,7 @@ class IndexedDBLogStore { } // there is no flush promise or there was but it has finished, so do // a brand new one, destroying the chain which may have been built up. - this.flushPromise = new Promise((resolve, reject) => { + this.flushPromise = new Promise((resolve, reject) => { if (!this.db) { // not connected yet or user rejected access for us to r/w to the db. reject(new Error("No connected database")); @@ -251,9 +252,9 @@ class IndexedDBLogStore { new Error("Failed to write logs: " + event.target.errorCode), ); }; - objStore.add(this._generateLogEntry(lines)); + objStore.add(this.generateLogEntry(lines)); const lastModStore = txn.objectStore("logslastmod"); - lastModStore.put(this._generateLastModifiedTime()); + lastModStore.put(this.generateLastModifiedTime()); }).then(() => { this.flushPromise = null; }); @@ -270,12 +271,12 @@ class IndexedDBLogStore { * log ID). The objects have said log ID in an "id" field and "lines" which * is a big string with all the new-line delimited logs. */ - async consume() { + public async consume(): Promise<{lines: string, id: string}[]> { const db = this.db; // Returns: a string representing the concatenated logs for this ID. // Stops adding log fragments when the size exceeds maxSize - function fetchLogs(id, maxSize) { + function fetchLogs(id: string, maxSize: number): Promise { const objectStore = db.transaction("logs", "readonly").objectStore("logs"); return new Promise((resolve, reject) => { @@ -301,7 +302,7 @@ class IndexedDBLogStore { } // Returns: A sorted array of log IDs. (newest first) - function fetchLogIds() { + function fetchLogIds(): Promise { // To gather all the log IDs, query for all records in logslastmod. const o = db.transaction("logslastmod", "readonly").objectStore( "logslastmod", @@ -319,8 +320,8 @@ class IndexedDBLogStore { }); } - function deleteLogs(id) { - return new Promise((resolve, reject) => { + function deleteLogs(id: number): Promise { + return new Promise((resolve, reject) => { const txn = db.transaction( ["logs", "logslastmod"], "readwrite", ); @@ -389,7 +390,7 @@ class IndexedDBLogStore { return logs; } - _generateLogEntry(lines) { + private generateLogEntry(lines: string): {id: string, lines: string, index: number} { return { id: this.id, lines: lines, @@ -397,7 +398,7 @@ class IndexedDBLogStore { }; } - _generateLastModifiedTime() { + private generateLastModifiedTime(): {id: string, ts: number} { return { id: this.id, ts: Date.now(), @@ -415,15 +416,19 @@ class IndexedDBLogStore { * @return {Promise} Resolves to an array of whatever you returned from * resultMapper. */ -function selectQuery(store, keyRange, resultMapper) { +function selectQuery( + store: IDBIndex, keyRange: IDBKeyRange, resultMapper: (cursor: IDBCursorWithValue) => T, +): Promise { const query = store.openCursor(keyRange); return new Promise((resolve, reject) => { const results = []; query.onerror = (event) => { + // @ts-ignore reject(new Error("Query failed: " + event.target.errorCode)); }; // collect results query.onsuccess = (event) => { + // @ts-ignore const cursor = event.target.result; if (!cursor) { resolve(results); @@ -442,7 +447,7 @@ function selectQuery(store, keyRange, resultMapper) { * be set up immediately for the logs. * @return {Promise} Resolves when set up. */ -export function init(setUpPersistence = true) { +export function init(setUpPersistence = true): Promise { if (global.mx_rage_initPromise) { return global.mx_rage_initPromise; } @@ -462,7 +467,7 @@ export function init(setUpPersistence = true) { * then this no-ops. * @return {Promise} Resolves when complete. */ -export function tryInitStorage() { +export function tryInitStorage(): Promise { if (global.mx_rage_initStoragePromise) { return global.mx_rage_initStoragePromise; } From af9429c8a067c248515d9d8d703e31460f6d1b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 26 Sep 2021 19:57:02 +0200 Subject: [PATCH 28/42] Convert ActiveWidgetStore to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/@types/global.d.ts | 2 + src/Lifecycle.ts | 4 +- src/components/views/elements/AppTile.tsx | 8 +- .../views/elements/PersistentApp.tsx | 18 +-- src/stores/ActiveWidgetStore.js | 110 ----------------- src/stores/ActiveWidgetStore.ts | 112 ++++++++++++++++++ src/stores/WidgetStore.ts | 8 +- src/stores/widgets/StopGapWidget.ts | 8 +- 8 files changed, 137 insertions(+), 133 deletions(-) delete mode 100644 src/stores/ActiveWidgetStore.js create mode 100644 src/stores/ActiveWidgetStore.ts diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 8ad93fa960..220a34c688 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -49,6 +49,7 @@ import PerformanceMonitor from "../performance"; import UIStore from "../stores/UIStore"; import { SetupEncryptionStore } from "../stores/SetupEncryptionStore"; import { RoomScrollStateStore } from "../stores/RoomScrollStateStore"; +import ActiveWidgetStore from "../stores/ActiveWidgetStore"; /* eslint-disable @typescript-eslint/naming-convention */ @@ -92,6 +93,7 @@ declare global { mxUIStore: UIStore; mxSetupEncryptionStore?: SetupEncryptionStore; mxRoomScrollStateStore?: RoomScrollStateStore; + mxActiveWidgetStore?: ActiveWidgetStore; mxOnRecaptchaLoaded?: () => void; electron?: Electron; } diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index 57c922aeb7..3685f7b938 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -786,7 +786,7 @@ async function startMatrixClient(startSyncing = true): Promise { UserActivity.sharedInstance().start(); DMRoomMap.makeShared().start(); IntegrationManagers.sharedInstance().startWatching(); - ActiveWidgetStore.start(); + ActiveWidgetStore.instance.start(); CallHandler.sharedInstance().start(); // Start Mjolnir even though we haven't checked the feature flag yet. Starting @@ -892,7 +892,7 @@ export function stopMatrixClient(unsetClient = true): void { UserActivity.sharedInstance().stop(); TypingStore.sharedInstance().reset(); Presence.stop(); - ActiveWidgetStore.stop(); + ActiveWidgetStore.instance.stop(); IntegrationManagers.sharedInstance().stopWatching(); Mjolnir.sharedInstance().stop(); DeviceListener.sharedInstance().stop(); diff --git a/src/components/views/elements/AppTile.tsx b/src/components/views/elements/AppTile.tsx index ea9ef71626..3f4d75df27 100644 --- a/src/components/views/elements/AppTile.tsx +++ b/src/components/views/elements/AppTile.tsx @@ -163,7 +163,7 @@ export default class AppTile extends React.Component { if (this.state.hasPermissionToLoad && !hasPermissionToLoad) { // Force the widget to be non-persistent (able to be deleted/forgotten) - ActiveWidgetStore.destroyPersistentWidget(this.props.app.id); + ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id); PersistedElement.destroyElement(this.persistKey); if (this.sgWidget) this.sgWidget.stop(); } @@ -198,8 +198,8 @@ export default class AppTile extends React.Component { if (this.dispatcherRef) dis.unregister(this.dispatcherRef); // if it's not remaining on screen, get rid of the PersistedElement container - if (!ActiveWidgetStore.getWidgetPersistence(this.props.app.id)) { - ActiveWidgetStore.destroyPersistentWidget(this.props.app.id); + if (!ActiveWidgetStore.instance.getWidgetPersistence(this.props.app.id)) { + ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id); PersistedElement.destroyElement(this.persistKey); } @@ -282,7 +282,7 @@ export default class AppTile extends React.Component { // Delete the widget from the persisted store for good measure. PersistedElement.destroyElement(this.persistKey); - ActiveWidgetStore.destroyPersistentWidget(this.props.app.id); + ActiveWidgetStore.instance.destroyPersistentWidget(this.props.app.id); if (this.sgWidget) this.sgWidget.stop({ forceDestroy: true }); } diff --git a/src/components/views/elements/PersistentApp.tsx b/src/components/views/elements/PersistentApp.tsx index 1f911659e2..8d0751cc1d 100644 --- a/src/components/views/elements/PersistentApp.tsx +++ b/src/components/views/elements/PersistentApp.tsx @@ -17,7 +17,7 @@ limitations under the License. import React from 'react'; import RoomViewStore from '../../../stores/RoomViewStore'; -import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; +import ActiveWidgetStore, { ActiveWidgetStoreEvent } from '../../../stores/ActiveWidgetStore'; import WidgetUtils from '../../../utils/WidgetUtils'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { replaceableComponent } from "../../../utils/replaceableComponent"; @@ -39,13 +39,13 @@ export default class PersistentApp extends React.Component<{}, IState> { this.state = { roomId: RoomViewStore.getRoomId(), - persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(), + persistentWidgetId: ActiveWidgetStore.instance.getPersistentWidgetId(), }; } public componentDidMount(): void { this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); - ActiveWidgetStore.on('update', this.onActiveWidgetStoreUpdate); + ActiveWidgetStore.instance.on(ActiveWidgetStoreEvent.Update, this.onActiveWidgetStoreUpdate); MatrixClientPeg.get().on("Room.myMembership", this.onMyMembership); } @@ -53,7 +53,7 @@ export default class PersistentApp extends React.Component<{}, IState> { if (this.roomStoreToken) { this.roomStoreToken.remove(); } - ActiveWidgetStore.removeListener('update', this.onActiveWidgetStoreUpdate); + ActiveWidgetStore.instance.removeListener(ActiveWidgetStoreEvent.Update, this.onActiveWidgetStoreUpdate); if (MatrixClientPeg.get()) { MatrixClientPeg.get().removeListener("Room.myMembership", this.onMyMembership); } @@ -68,23 +68,23 @@ export default class PersistentApp extends React.Component<{}, IState> { private onActiveWidgetStoreUpdate = (): void => { this.setState({ - persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(), + persistentWidgetId: ActiveWidgetStore.instance.getPersistentWidgetId(), }); }; private onMyMembership = async (room: Room, membership: string): Promise => { - const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(this.state.persistentWidgetId); + const persistentWidgetInRoomId = ActiveWidgetStore.instance.getRoomId(this.state.persistentWidgetId); if (membership !== "join") { // we're not in the room anymore - delete if (room .roomId === persistentWidgetInRoomId) { - ActiveWidgetStore.destroyPersistentWidget(this.state.persistentWidgetId); + ActiveWidgetStore.instance.destroyPersistentWidget(this.state.persistentWidgetId); } } }; public render(): JSX.Element { if (this.state.persistentWidgetId) { - const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(this.state.persistentWidgetId); + const persistentWidgetInRoomId = ActiveWidgetStore.instance.getRoomId(this.state.persistentWidgetId); const persistentWidgetInRoom = MatrixClientPeg.get().getRoom(persistentWidgetInRoomId); @@ -96,7 +96,7 @@ export default class PersistentApp extends React.Component<{}, IState> { if (this.state.roomId !== persistentWidgetInRoomId && myMembership === "join") { // get the widget data const appEvent = WidgetUtils.getRoomWidgets(persistentWidgetInRoom).find((ev) => { - return ev.getStateKey() === ActiveWidgetStore.getPersistentWidgetId(); + return ev.getStateKey() === ActiveWidgetStore.instance.getPersistentWidgetId(); }); const app = WidgetUtils.makeAppConfig( appEvent.getStateKey(), appEvent.getContent(), appEvent.getSender(), diff --git a/src/stores/ActiveWidgetStore.js b/src/stores/ActiveWidgetStore.js deleted file mode 100644 index b270d99693..0000000000 --- a/src/stores/ActiveWidgetStore.js +++ /dev/null @@ -1,110 +0,0 @@ -/* -Copyright 2018 New Vector Ltd - -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 EventEmitter from 'events'; - -import { MatrixClientPeg } from '../MatrixClientPeg'; -import { WidgetMessagingStore } from "./widgets/WidgetMessagingStore"; - -/** - * Stores information about the widgets active in the app right now: - * * What widget is set to remain always-on-screen, if any - * Only one widget may be 'always on screen' at any one time. - * * Negotiated capabilities for active apps - */ -class ActiveWidgetStore extends EventEmitter { - constructor() { - super(); - this._persistentWidgetId = null; - - // What room ID each widget is associated with (if it's a room widget) - this._roomIdByWidgetId = {}; - - this.onRoomStateEvents = this.onRoomStateEvents.bind(this); - - this.dispatcherRef = null; - } - - start() { - MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents); - } - - stop() { - if (MatrixClientPeg.get()) { - MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents); - } - this._roomIdByWidgetId = {}; - } - - onRoomStateEvents(ev, state) { - // XXX: This listens for state events in order to remove the active widget. - // Everything else relies on views listening for events and calling setters - // on this class which is terrible. This store should just listen for events - // and keep itself up to date. - // TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111) - if (ev.getType() !== 'im.vector.modular.widgets') return; - - if (ev.getStateKey() === this._persistentWidgetId) { - this.destroyPersistentWidget(this._persistentWidgetId); - } - } - - destroyPersistentWidget(id) { - if (id !== this._persistentWidgetId) return; - const toDeleteId = this._persistentWidgetId; - - WidgetMessagingStore.instance.stopMessagingById(id); - - this.setWidgetPersistence(toDeleteId, false); - this.delRoomId(toDeleteId); - } - - setWidgetPersistence(widgetId, val) { - if (this._persistentWidgetId === widgetId && !val) { - this._persistentWidgetId = null; - } else if (this._persistentWidgetId !== widgetId && val) { - this._persistentWidgetId = widgetId; - } - this.emit('update'); - } - - getWidgetPersistence(widgetId) { - return this._persistentWidgetId === widgetId; - } - - getPersistentWidgetId() { - return this._persistentWidgetId; - } - - getRoomId(widgetId) { - return this._roomIdByWidgetId[widgetId]; - } - - setRoomId(widgetId, roomId) { - this._roomIdByWidgetId[widgetId] = roomId; - this.emit('update'); - } - - delRoomId(widgetId) { - delete this._roomIdByWidgetId[widgetId]; - this.emit('update'); - } -} - -if (global.singletonActiveWidgetStore === undefined) { - global.singletonActiveWidgetStore = new ActiveWidgetStore(); -} -export default global.singletonActiveWidgetStore; diff --git a/src/stores/ActiveWidgetStore.ts b/src/stores/ActiveWidgetStore.ts new file mode 100644 index 0000000000..ca50689188 --- /dev/null +++ b/src/stores/ActiveWidgetStore.ts @@ -0,0 +1,112 @@ +/* +Copyright 2018 New Vector Ltd + +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 EventEmitter from 'events'; +import { MatrixEvent } from "matrix-js-sdk"; + +import { MatrixClientPeg } from '../MatrixClientPeg'; +import { WidgetMessagingStore } from "./widgets/WidgetMessagingStore"; + +export enum ActiveWidgetStoreEvent { + Update = "update", +} + +/** + * Stores information about the widgets active in the app right now: + * * What widget is set to remain always-on-screen, if any + * Only one widget may be 'always on screen' at any one time. + * * Negotiated capabilities for active apps + */ +export default class ActiveWidgetStore extends EventEmitter { + private static internalInstance: ActiveWidgetStore; + private persistentWidgetId: string; + // What room ID each widget is associated with (if it's a room widget) + private roomIdByWidgetId = new Map(); + + public static get instance(): ActiveWidgetStore { + if (!ActiveWidgetStore.internalInstance) { + ActiveWidgetStore.internalInstance = new ActiveWidgetStore(); + } + return ActiveWidgetStore.internalInstance; + } + + public start(): void { + MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents); + } + + public stop(): void { + if (MatrixClientPeg.get()) { + MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents); + } + this.roomIdByWidgetId.clear(); + } + + private onRoomStateEvents = (ev: MatrixEvent): void => { + // XXX: This listens for state events in order to remove the active widget. + // Everything else relies on views listening for events and calling setters + // on this class which is terrible. This store should just listen for events + // and keep itself up to date. + // TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111) + if (ev.getType() !== 'im.vector.modular.widgets') return; + + if (ev.getStateKey() === this.persistentWidgetId) { + this.destroyPersistentWidget(this.persistentWidgetId); + } + }; + + public destroyPersistentWidget(id: string): void { + if (id !== this.persistentWidgetId) return; + const toDeleteId = this.persistentWidgetId; + + WidgetMessagingStore.instance.stopMessagingById(id); + + this.setWidgetPersistence(toDeleteId, false); + this.delRoomId(toDeleteId); + } + + public setWidgetPersistence(widgetId: string, val: boolean): void { + if (this.persistentWidgetId === widgetId && !val) { + this.persistentWidgetId = null; + } else if (this.persistentWidgetId !== widgetId && val) { + this.persistentWidgetId = widgetId; + } + this.emit(ActiveWidgetStoreEvent.Update); + } + + public getWidgetPersistence(widgetId: string): boolean { + return this.persistentWidgetId === widgetId; + } + + public getPersistentWidgetId(): string { + return this.persistentWidgetId; + } + + public getRoomId(widgetId: string): string { + return this.roomIdByWidgetId.get(widgetId); + } + + public setRoomId(widgetId: string, roomId: string): void { + this.roomIdByWidgetId.set(widgetId, roomId); + this.emit(ActiveWidgetStoreEvent.Update); + } + + public delRoomId(widgetId: string): void { + this.roomIdByWidgetId.delete(widgetId); + this.emit(ActiveWidgetStoreEvent.Update); + } +} + +window.mxActiveWidgetStore = ActiveWidgetStore.instance; diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index bd03e2065b..44c8327c04 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -142,14 +142,14 @@ export default class WidgetStore extends AsyncStoreWithClient { // If a persistent widget is active, check to see if it's just been removed. // If it has, it needs to destroyed otherwise unmounting the node won't kill it - const persistentWidgetId = ActiveWidgetStore.getPersistentWidgetId(); + const persistentWidgetId = ActiveWidgetStore.instance.getPersistentWidgetId(); if (persistentWidgetId) { if ( - ActiveWidgetStore.getRoomId(persistentWidgetId) === room.roomId && + ActiveWidgetStore.instance.getRoomId(persistentWidgetId) === room.roomId && !roomInfo.widgets.some(w => w.id === persistentWidgetId) ) { logger.log(`Persistent widget ${persistentWidgetId} removed from room ${room.roomId}: destroying.`); - ActiveWidgetStore.destroyPersistentWidget(persistentWidgetId); + ActiveWidgetStore.instance.destroyPersistentWidget(persistentWidgetId); } } @@ -195,7 +195,7 @@ export default class WidgetStore extends AsyncStoreWithClient { // A persistent conference widget indicates that we're participating const widgets = roomInfo.widgets.filter(w => WidgetType.JITSI.matches(w.type)); - return widgets.some(w => ActiveWidgetStore.getWidgetPersistence(w.id)); + return widgets.some(w => ActiveWidgetStore.instance.getWidgetPersistence(w.id)); } } diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 361b02bc3e..e00c4c6c0b 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -266,7 +266,7 @@ export class StopGapWidget extends EventEmitter { WidgetMessagingStore.instance.storeMessaging(this.mockWidget, this.messaging); if (!this.appTileProps.userWidget && this.appTileProps.room) { - ActiveWidgetStore.setRoomId(this.mockWidget.id, this.appTileProps.room.roomId); + ActiveWidgetStore.instance.setRoomId(this.mockWidget.id, this.appTileProps.room.roomId); } // Always attach a handler for ViewRoom, but permission check it internally @@ -319,7 +319,7 @@ export class StopGapWidget extends EventEmitter { if (WidgetType.JITSI.matches(this.mockWidget.type)) { CountlyAnalytics.instance.trackJoinCall(this.appTileProps.room.roomId, true, true); } - ActiveWidgetStore.setWidgetPersistence(this.mockWidget.id, ev.detail.data.value); + ActiveWidgetStore.instance.setWidgetPersistence(this.mockWidget.id, ev.detail.data.value); ev.preventDefault(); this.messaging.transport.reply(ev.detail, {}); // ack } @@ -406,13 +406,13 @@ export class StopGapWidget extends EventEmitter { } public stop(opts = { forceDestroy: false }) { - if (!opts?.forceDestroy && ActiveWidgetStore.getPersistentWidgetId() === this.mockWidget.id) { + if (!opts?.forceDestroy && ActiveWidgetStore.instance.getPersistentWidgetId() === this.mockWidget.id) { logger.log("Skipping destroy - persistent widget"); return; } if (!this.started) return; WidgetMessagingStore.instance.stopMessaging(this.mockWidget); - ActiveWidgetStore.delRoomId(this.mockWidget.id); + ActiveWidgetStore.instance.delRoomId(this.mockWidget.id); if (MatrixClientPeg.get()) { MatrixClientPeg.get().off('event', this.onEvent); From 6aa5d98741b7642a0c9eb1dbc1bd73e59a582017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 27 Sep 2021 10:12:45 +0200 Subject: [PATCH 29/42] Remove constructor Co-authored-by: Germain --- src/components/views/messages/MjolnirBody.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/views/messages/MjolnirBody.tsx b/src/components/views/messages/MjolnirBody.tsx index c21a9a353e..db806e3a15 100644 --- a/src/components/views/messages/MjolnirBody.tsx +++ b/src/components/views/messages/MjolnirBody.tsx @@ -26,9 +26,6 @@ interface IProps { @replaceableComponent("views.messages.MjolnirBody") export default class MjolnirBody extends React.Component { - constructor(props: IProps) { - super(props); - } private onAllowClick = (e: React.MouseEvent): void => { e.preventDefault(); From a1d5e7ba66c94e2679a6cd596c83f319a0d69167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 27 Sep 2021 10:12:55 +0200 Subject: [PATCH 30/42] Add return type Co-authored-by: Germain --- src/components/views/messages/MKeyVerificationConclusion.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/messages/MKeyVerificationConclusion.tsx b/src/components/views/messages/MKeyVerificationConclusion.tsx index 3e2ae9fa9b..66ae3766d6 100644 --- a/src/components/views/messages/MKeyVerificationConclusion.tsx +++ b/src/components/views/messages/MKeyVerificationConclusion.tsx @@ -55,7 +55,7 @@ export default class MKeyVerificationConclusion extends React.Component } } - private onRequestChanged = () => { + private onRequestChanged = (): void => { this.forceUpdate(); }; From 1ed4a9eb4f994bbfca17a0768df97b8f850abdf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 27 Sep 2021 10:19:53 +0200 Subject: [PATCH 31/42] Fix JSDoc type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/utils/MegolmExportEncryption.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/MegolmExportEncryption.ts b/src/utils/MegolmExportEncryption.ts index 4fab271015..47c395bfb7 100644 --- a/src/utils/MegolmExportEncryption.ts +++ b/src/utils/MegolmExportEncryption.ts @@ -28,7 +28,7 @@ const subtleCrypto = window.crypto.subtle || window.crypto.webkitSubtle; * * @param {string} message message for the exception * @param {string} friendlyText - * @returns {Error} + * @returns {{message: string, friendlyText: string}} */ function friendlyError( message: string, friendlyText: string, From dd92f8f1f9e013a379b0f280ec9050876de48a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 27 Sep 2021 10:21:46 +0200 Subject: [PATCH 32/42] Move imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/RedactedBody.tsx | 2 +- src/components/views/messages/RoomAvatarEvent.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/messages/RedactedBody.tsx b/src/components/views/messages/RedactedBody.tsx index 7202e3a2e5..66200036cd 100644 --- a/src/components/views/messages/RedactedBody.tsx +++ b/src/components/views/messages/RedactedBody.tsx @@ -16,12 +16,12 @@ limitations under the License. import React, { useContext } from "react"; import { MatrixClient } from "matrix-js-sdk/src/client"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { _t } from "../../../languageHandler"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { formatFullDate } from "../../../DateUtils"; import SettingsStore from "../../../settings/SettingsStore"; import { IBodyProps } from "./IBodyProps"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; interface IProps { mxEvent: MatrixEvent; diff --git a/src/components/views/messages/RoomAvatarEvent.tsx b/src/components/views/messages/RoomAvatarEvent.tsx index c29460b002..12a8c88913 100644 --- a/src/components/views/messages/RoomAvatarEvent.tsx +++ b/src/components/views/messages/RoomAvatarEvent.tsx @@ -17,13 +17,13 @@ limitations under the License. */ import React from 'react'; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { _t } from '../../../languageHandler'; import Modal from '../../../Modal'; import AccessibleButton from '../elements/AccessibleButton'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import RoomAvatar from "../avatars/RoomAvatar"; import ImageView from "../elements/ImageView"; From 2712dde581d1c2e0eeb1137677c745c56e5aa5a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 27 Sep 2021 10:28:07 +0200 Subject: [PATCH 33/42] Remove empty line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/MjolnirBody.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/messages/MjolnirBody.tsx b/src/components/views/messages/MjolnirBody.tsx index db806e3a15..7e922a5715 100644 --- a/src/components/views/messages/MjolnirBody.tsx +++ b/src/components/views/messages/MjolnirBody.tsx @@ -26,7 +26,6 @@ interface IProps { @replaceableComponent("views.messages.MjolnirBody") export default class MjolnirBody extends React.Component { - private onAllowClick = (e: React.MouseEvent): void => { e.preventDefault(); e.stopPropagation(); From 0cba943f9648d675573dd990333569073a40021e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 27 Sep 2021 10:35:33 +0200 Subject: [PATCH 34/42] Fix types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/messages/MKeyVerificationConclusion.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/views/messages/MKeyVerificationConclusion.tsx b/src/components/views/messages/MKeyVerificationConclusion.tsx index 66ae3766d6..1ce39e1157 100644 --- a/src/components/views/messages/MKeyVerificationConclusion.tsx +++ b/src/components/views/messages/MKeyVerificationConclusion.tsx @@ -109,15 +109,18 @@ export default class MKeyVerificationConclusion extends React.Component let title; if (request.done) { - title = _t("You verified %(name)s", { name: getNameForEventRoom(request.otherUserId, mxEvent) }); + title = _t( + "You verified %(name)s", + { name: getNameForEventRoom(request.otherUserId, mxEvent.getRoomId()) }, + ); } else if (request.cancelled) { const userId = request.cancellingUserId; if (userId === myUserId) { title = _t("You cancelled verifying %(name)s", - { name: getNameForEventRoom(request.otherUserId, mxEvent) }); + { name: getNameForEventRoom(request.otherUserId, mxEvent.getRoomId()) }); } else { title = _t("%(name)s cancelled verifying", - { name: getNameForEventRoom(userId, mxEvent) }); + { name: getNameForEventRoom(userId, mxEvent.getRoomId()) }); } } From 0cfa2a58c7867eec05d5611c342da3cfc8e0fc8e Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk <3636685+Palid@users.noreply.github.com> Date: Mon, 27 Sep 2021 12:20:37 +0200 Subject: [PATCH 35/42] Add ability to expand and collapse long quoted messages (#6701) In case where we had a very long message the experience of going between messages to see the full quote isn't very nice on desktop, therefore this commit adds a button with additional hotkey to normalize the experience a bit. Fixes https://github.com/vector-im/element-web/issues/18884 --- res/css/views/elements/_ReplyThread.scss | 11 +++ res/css/views/messages/_MessageActionBar.scss | 10 +++ res/img/element-icons/collapse-message.svg | 1 + res/img/element-icons/expand-message.svg | 1 + src/components/structures/ContextMenu.tsx | 8 +- .../context_menus/MessageContextMenu.tsx | 4 +- src/components/views/elements/ReplyThread.tsx | 78 ++++++++++++------- .../views/messages/MessageActionBar.tsx | 39 +++++++--- src/components/views/messages/TextualBody.tsx | 1 + src/components/views/rooms/EventTile.tsx | 44 ++++++----- src/components/views/rooms/ReplyTile.tsx | 18 +++-- src/i18n/strings/en_EN.json | 2 + 12 files changed, 153 insertions(+), 64 deletions(-) create mode 100644 res/img/element-icons/collapse-message.svg create mode 100644 res/img/element-icons/expand-message.svg diff --git a/res/css/views/elements/_ReplyThread.scss b/res/css/views/elements/_ReplyThread.scss index 032cb49359..e19be82e25 100644 --- a/res/css/views/elements/_ReplyThread.scss +++ b/res/css/views/elements/_ReplyThread.scss @@ -59,3 +59,14 @@ limitations under the License. border-left-color: $username-variant8-color; } } + +.mx_ReplyThread--expanded { + .mx_EventTile_body { + display: block; + overflow-y: scroll !important; + } + .mx_EventTile_collapsedCodeBlock { + // !important needed due to .mx_ReplyTile .mx_EventTile_content .mx_EventTile_pre_container > pre + display: block !important; + } +} diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index 6805036e3d..46fc11956f 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -117,6 +117,16 @@ limitations under the License. mask-image: url('$(res)/img/download.svg'); } +.mx_MessageActionBar_expandMessageButton::after { + mask-size: 12px; + mask-image: url('$(res)/img/element-icons/expand-message.svg'); +} + +.mx_MessageActionBar_collapseMessageButton::after { + mask-size: 12px; + mask-image: url('$(res)/img/element-icons/collapse-message.svg'); +} + .mx_MessageActionBar_downloadButton.mx_MessageActionBar_downloadSpinnerButton::after { background-color: transparent; // hide the download icon mask } diff --git a/res/img/element-icons/collapse-message.svg b/res/img/element-icons/collapse-message.svg new file mode 100644 index 0000000000..91b0713f43 --- /dev/null +++ b/res/img/element-icons/collapse-message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/element-icons/expand-message.svg b/res/img/element-icons/expand-message.svg new file mode 100644 index 0000000000..a1c5149718 --- /dev/null +++ b/res/img/element-icons/expand-message.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index d65f8e3a10..2173230627 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -45,7 +45,7 @@ function getOrCreateContainer(): HTMLDivElement { const ARIA_MENU_ITEM_ROLES = new Set(["menuitem", "menuitemcheckbox", "menuitemradio"]); -interface IPosition { +export interface IPosition { top?: number; bottom?: number; left?: number; @@ -430,7 +430,11 @@ export type AboveLeftOf = IPosition & { // Placement method for to position context menu right-aligned and flowing to the left of elementRect, // and either above or below: wherever there is more space (maybe this should be aboveOrBelowLeftOf?) -export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None, vPadding = 0): AboveLeftOf => { +export const aboveLeftOf = ( + elementRect: DOMRect, + chevronFace = ChevronFace.None, + vPadding = 0, +): AboveLeftOf => { const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace }; const buttonRight = elementRect.right + window.pageXOffset; diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 8f5d3baa17..c7fcf32260 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -38,6 +38,7 @@ import ConfirmRedactDialog from '../dialogs/ConfirmRedactDialog'; import ErrorDialog from '../dialogs/ErrorDialog'; import ShareDialog from '../dialogs/ShareDialog'; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; +import { IPosition, ChevronFace } from '../../structures/ContextMenu'; export function canCancel(eventStatus: EventStatus): boolean { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; @@ -52,7 +53,8 @@ export interface IOperableEventTile { getEventTileOps(): IEventTileOps; } -interface IProps { +interface IProps extends IPosition { + chevronFace: ChevronFace; /* the MatrixEvent associated with the context menu */ mxEvent: MatrixEvent; /* an optional EventTileOps implementation that can be used to unhide preview widgets */ diff --git a/src/components/views/elements/ReplyThread.tsx b/src/components/views/elements/ReplyThread.tsx index 59c827d5d8..bd81218623 100644 --- a/src/components/views/elements/ReplyThread.tsx +++ b/src/components/views/elements/ReplyThread.tsx @@ -16,6 +16,8 @@ limitations under the License. */ import React from 'react'; +import classNames from 'classnames'; + import { _t } from '../../../languageHandler'; import dis from '../../../dispatcher/dispatcher'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; @@ -35,6 +37,12 @@ import ReplyTile from "../rooms/ReplyTile"; import Pill from './Pill'; import { Room } from 'matrix-js-sdk/src/models/room'; +/** + * This number is based on the previous behavior - if we have message of height + * over 60px then we want to show button that will allow to expand it. + */ +const SHOW_EXPAND_QUOTE_PIXELS = 60; + interface IProps { // the latest event in this chain of replies parentEv?: MatrixEvent; @@ -45,6 +53,8 @@ interface IProps { layout?: Layout; // Whether to always show a timestamp alwaysShowTimestamps?: boolean; + isQuoteExpanded?: boolean; + setQuoteExpanded: (isExpanded: boolean) => void; } interface IState { @@ -66,6 +76,7 @@ export default class ReplyThread extends React.Component { static contextType = MatrixClientContext; private unmounted = false; private room: Room; + private blockquoteRef = React.createRef(); constructor(props, context) { super(props, context); @@ -80,7 +91,7 @@ export default class ReplyThread extends React.Component { this.room = this.context.getRoom(this.props.parentEv.getRoomId()); } - public static getParentEventId(ev: MatrixEvent): string { + public static getParentEventId(ev: MatrixEvent): string | undefined { if (!ev || ev.isRedacted()) return; // XXX: For newer relations (annotations, replacements, etc.), we now @@ -137,7 +148,7 @@ export default class ReplyThread extends React.Component { public static getNestedReplyText( ev: MatrixEvent, permalinkCreator: RoomPermalinkCreator, - ): { body: string, html: string } { + ): { body: string, html: string } | null { if (!ev) return null; let { body, formatted_body: html } = ev.getContent(); @@ -237,37 +248,38 @@ export default class ReplyThread extends React.Component { return replyMixin; } - public static makeThread( - parentEv: MatrixEvent, - onHeightChanged: () => void, - permalinkCreator: RoomPermalinkCreator, - ref: React.RefObject, - layout: Layout, - alwaysShowTimestamps: boolean, - ): JSX.Element { - if (!ReplyThread.getParentEventId(parentEv)) return null; - return ; + public static hasThreadReply(event: MatrixEvent) { + return Boolean(ReplyThread.getParentEventId(event)); } componentDidMount() { this.initialize(); + this.trySetExpandableQuotes(); } componentDidUpdate() { this.props.onHeightChanged(); + this.trySetExpandableQuotes(); } componentWillUnmount() { this.unmounted = true; } + private trySetExpandableQuotes() { + if (this.props.isQuoteExpanded === undefined && this.blockquoteRef.current) { + const el: HTMLElement | null = this.blockquoteRef.current.querySelector('.mx_EventTile_body'); + if (el) { + const code: HTMLElement | null = el.querySelector('code'); + const isCodeEllipsisShown = code ? code.offsetHeight >= SHOW_EXPAND_QUOTE_PIXELS : false; + const isElipsisShown = el.offsetHeight >= SHOW_EXPAND_QUOTE_PIXELS || isCodeEllipsisShown; + if (isElipsisShown) { + this.props.setQuoteExpanded(false); + } + } + } + } + private async initialize(): Promise { const { parentEv } = this.props; // at time of making this component we checked that props.parentEv has a parentEventId @@ -321,7 +333,7 @@ export default class ReplyThread extends React.Component { this.initialize(); }; - private onQuoteClick = async (): Promise => { + private onQuoteClick = async (event: React.MouseEvent): Promise => { const events = [this.state.loadedEv, ...this.state.events]; let loadedEv = null; @@ -373,14 +385,26 @@ export default class ReplyThread extends React.Component { header = ; } + const { isQuoteExpanded } = this.props; const evTiles = this.state.events.map((ev) => { - return
- -
; + const classname = classNames({ + 'mx_ReplyThread': true, + [this.getReplyThreadColorClass(ev)]: true, + // We don't want to add the class if it's undefined, it should only be expanded/collapsed when it's true/false + 'mx_ReplyThread--expanded': isQuoteExpanded === true, + // We don't want to add the class if it's undefined, it should only be expanded/collapsed when it's true/false + 'mx_ReplyThread--collapsed': isQuoteExpanded === false, + }); + return ( +
+ this.props.setQuoteExpanded(!this.props.isQuoteExpanded)} + /> +
+ ); }); return
diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index f76fa32ddc..e835584387 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -17,7 +17,8 @@ limitations under the License. */ import React, { useEffect } from 'react'; -import { MatrixEvent, EventStatus } from 'matrix-js-sdk/src/models/event'; +import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event'; +import type { Relations } from 'matrix-js-sdk/src/models/relations'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; @@ -35,13 +36,17 @@ import Resend from "../../../Resend"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import DownloadActionButton from "./DownloadActionButton"; +import MessageContextMenu from "../context_menus/MessageContextMenu"; +import classNames from 'classnames'; + import SettingsStore from '../../../settings/SettingsStore'; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import ReplyThread from '../elements/ReplyThread'; interface IOptionsButtonProps { mxEvent: MatrixEvent; - getTile: () => any; // TODO: FIXME, haven't figured out what the return type is here + // TODO: Types + getTile: () => any | null; getReplyThread: () => ReplyThread; permalinkCreator: RoomPermalinkCreator; onFocusChange: (menuDisplayed: boolean) => void; @@ -57,8 +62,6 @@ const OptionsButton: React.FC = let contextMenu; if (menuDisplayed) { - const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu'); - const tile = getTile && getTile(); const replyThread = getReplyThread && getReplyThread(); @@ -90,7 +93,7 @@ const OptionsButton: React.FC = interface IReactButtonProps { mxEvent: MatrixEvent; - reactions: any; // TODO: types + reactions: Relations; onFocusChange: (menuDisplayed: boolean) => void; } @@ -127,12 +130,14 @@ const ReactButton: React.FC = ({ mxEvent, reactions, onFocusC interface IMessageActionBarProps { mxEvent: MatrixEvent; - // The Relations model from the JS SDK for reactions to `mxEvent` - reactions?: any; // TODO: types + reactions?: Relations; + // TODO: Types + getTile: () => any | null; + getReplyThread: () => ReplyThread | undefined; permalinkCreator?: RoomPermalinkCreator; - getTile: () => any; // TODO: FIXME, haven't figured out what the return type is here - getReplyThread?: () => ReplyThread; - onFocusChange?: (menuDisplayed: boolean) => void; + onFocusChange: (menuDisplayed: boolean) => void; + isQuoteExpanded?: boolean; + toggleThreadExpanded: () => void; } @replaceableComponent("views.messages.MessageActionBar") @@ -324,6 +329,20 @@ export default class MessageActionBar extends React.PureComponent); + } + // The menu button should be last, so dump it there. toolbarOpts.push( { // If it's less than 30% we don't add the expansion button. // We also round the number as it sometimes can be 29.99... const percentageOfViewport = Math.round(pre.offsetHeight / UIStore.instance.windowHeight * 100); + // TODO: additionally show the button if it's an expanded quoted message if (percentageOfViewport < 30) return; const button = document.createElement("span"); diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index d1ac06b199..592827eaf5 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -322,7 +322,7 @@ interface IState { reactions: Relations; hover: boolean; - + isQuoteExpanded?: boolean; thread?: Thread; } @@ -330,7 +330,8 @@ interface IState { export default class EventTile extends React.Component { private suppressReadReceiptAnimation: boolean; private isListeningForReceipts: boolean; - private tile = React.createRef(); + // TODO: Types + private tile = React.createRef(); private replyThread = React.createRef(); public readonly ref = createRef(); @@ -888,8 +889,8 @@ export default class EventTile extends React.Component { actionBarFocused: focused, }); }; - - getTile = () => this.tile.current; + // TODO: Types + getTile: () => any | null = () => this.tile.current; getReplyThread = () => this.replyThread.current; @@ -914,6 +915,11 @@ export default class EventTile extends React.Component { }); }; + private setQuoteExpanded = (expanded: boolean) => { + this.setState({ + isQuoteExpanded: expanded, + }); + }; render() { const msgtype = this.props.mxEvent.getContent().msgtype; const eventType = this.props.mxEvent.getType() as EventType; @@ -923,6 +929,7 @@ export default class EventTile extends React.Component { isInfoMessage, isLeftAlignedBubbleMessage, } = getEventDisplayInfo(this.props.mxEvent); + const { isQuoteExpanded } = this.state; // This shouldn't happen: the caller should check we support this type // before trying to instantiate us @@ -935,6 +942,7 @@ export default class EventTile extends React.Component {
; } + const EventTileType = sdk.getComponent(tileHandler); const isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1); @@ -1054,6 +1062,8 @@ export default class EventTile extends React.Component { getTile={this.getTile} getReplyThread={this.getReplyThread} onFocusChange={this.onActionBarFocusChange} + isQuoteExpanded={isQuoteExpanded} + toggleThreadExpanded={() => this.setQuoteExpanded(!isQuoteExpanded)} /> : undefined; const showTimestamp = this.props.mxEvent.getTs() @@ -1192,20 +1202,18 @@ export default class EventTile extends React.Component { } default: { - let thread; - // When the "showHiddenEventsInTimeline" lab is enabled, - // avoid showing replies for hidden events (events without tiles) - if (haveTileForEvent(this.props.mxEvent)) { - thread = ReplyThread.makeThread( - this.props.mxEvent, - this.props.onHeightChanged, - this.props.permalinkCreator, - this.replyThread, - this.props.layout, - this.props.alwaysShowTimestamps || this.state.hover, - ); - } - + const thread = haveTileForEvent(this.props.mxEvent) && + ReplyThread.hasThreadReply(this.props.mxEvent) ? ( + ) : null; const isOwnEvent = this.props.mxEvent?.sender?.userId === MatrixClientPeg.get().getUserId(); // tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers diff --git a/src/components/views/rooms/ReplyTile.tsx b/src/components/views/rooms/ReplyTile.tsx index cf7d1ce945..01a9e2f18b 100644 --- a/src/components/views/rooms/ReplyTile.tsx +++ b/src/components/views/rooms/ReplyTile.tsx @@ -35,6 +35,7 @@ interface IProps { highlights?: string[]; highlightLink?: string; onHeightChanged?(): void; + toggleExpandedQuote?: () => void; } @replaceableComponent("views.rooms.ReplyTile") @@ -82,12 +83,17 @@ export default class ReplyTile extends React.PureComponent { // This allows the permalink to be opened in a new tab/window or copied as // matrix.to, but also for it to enable routing within Riot when clicked. e.preventDefault(); - dis.dispatch({ - action: 'view_room', - event_id: this.props.mxEvent.getId(), - highlighted: true, - room_id: this.props.mxEvent.getRoomId(), - }); + // Expand thread on shift key + if (this.props.toggleExpandedQuote && e.shiftKey) { + this.props.toggleExpandedQuote(); + } else { + dis.dispatch({ + action: 'view_room', + event_id: this.props.mxEvent.getId(), + highlighted: true, + room_id: this.props.mxEvent.getRoomId(), + }); + } } }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b5d90f6671..bc45caedb5 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1944,6 +1944,8 @@ "Edit": "Edit", "Reply": "Reply", "Thread": "Thread", + "Collapse quotes │ ⇧+click": "Collapse quotes │ ⇧+click", + "Expand quotes │ ⇧+click": "Expand quotes │ ⇧+click", "Message Actions": "Message Actions", "Download %(text)s": "Download %(text)s", "Error decrypting attachment": "Error decrypting attachment", From ccfc57657ab6d59921a088e24f32880959ccdcbe Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 27 Sep 2021 14:27:26 +0100 Subject: [PATCH 36/42] Upgrade matrix-js-sdk to 13.0.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9593f74aae..7b6aa14bf9 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "13.0.0-rc.1", + "matrix-js-sdk": "13.0.0", "matrix-widget-api": "^0.1.0-beta.16", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index f5931c8c6e..56b4f74f4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5806,10 +5806,10 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@13.0.0-rc.1: - version "13.0.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-13.0.0-rc.1.tgz#12deab353862852acae8342108d30ce080d364da" - integrity sha512-dfqJwXmG1+Ky2geaNADWYb7mwB2IfLFTE+T4q16gCoh2HM0W5yTMvi+kiJs0QspWFXICTps7eBSSq0827QNU8A== +matrix-js-sdk@13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-13.0.0.tgz#a57a4b73892e98fcd5eb0081e4745fac567211dd" + integrity sha512-VkZhQBd6jlRNjsqjLIoHV3fnjOSljPDuJklKsF4l6yffkra7llxQctyMsrCnoiosdAQWigbLZsKSb+HbSFmcnw== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From d5dddadc3b54782959204f3bf646963754b9015d Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 27 Sep 2021 14:31:25 +0100 Subject: [PATCH 37/42] Prepare changelog for v3.31.0 --- CHANGELOG.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71ddb1c5fe..c28d72a3eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,51 @@ +Changes in [3.31.0](https://github.com/vector-im/element-desktop/releases/tag/v3.31.0) (2021-09-27) +=================================================================================================== + +## ✨ Features + * Say Joining space instead of Joining room where we know its a space ([\#6818](https://github.com/matrix-org/matrix-react-sdk/pull/6818)). Fixes vector-im/element-web#19064 and vector-im/element-web#19064. + * Add warning that some spaces may not be relinked to the newly upgraded room ([\#6805](https://github.com/matrix-org/matrix-react-sdk/pull/6805)). Fixes vector-im/element-web#18858 and vector-im/element-web#18858. + * Delabs Spaces, iterate some copy and move communities/space toggle to preferences ([\#6594](https://github.com/matrix-org/matrix-react-sdk/pull/6594)). Fixes vector-im/element-web#18088, vector-im/element-web#18524 vector-im/element-web#18088 and vector-im/element-web#18088. + * Show "Message" in the user info panel instead of "Start chat" ([\#6319](https://github.com/matrix-org/matrix-react-sdk/pull/6319)). Fixes vector-im/element-web#17877 and vector-im/element-web#17877. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix space keyboard shortcuts conflicting with native zoom shortcuts ([\#6804](https://github.com/matrix-org/matrix-react-sdk/pull/6804)). + * Replace plain text emoji at the end of a line ([\#6784](https://github.com/matrix-org/matrix-react-sdk/pull/6784)). Fixes vector-im/element-web#18833 and vector-im/element-web#18833. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Simplify Space Panel layout and fix some edge cases ([\#6800](https://github.com/matrix-org/matrix-react-sdk/pull/6800)). Fixes vector-im/element-web#18694 and vector-im/element-web#18694. + * Show unsent message warning on Space Panel buttons ([\#6778](https://github.com/matrix-org/matrix-react-sdk/pull/6778)). Fixes vector-im/element-web#18891 and vector-im/element-web#18891. + * Hide mute/unmute button in UserInfo for Spaces as it makes no sense ([\#6790](https://github.com/matrix-org/matrix-react-sdk/pull/6790)). Fixes vector-im/element-web#19007 and vector-im/element-web#19007. + * Fix automatic field population in space create menu not validating ([\#6792](https://github.com/matrix-org/matrix-react-sdk/pull/6792)). Fixes vector-im/element-web#19005 and vector-im/element-web#19005. + * Optimize input label transition on focus ([\#6783](https://github.com/matrix-org/matrix-react-sdk/pull/6783)). Fixes vector-im/element-web#12876 and vector-im/element-web#12876. Contributed by [MadLittleMods](https://github.com/MadLittleMods). + * Adapt and re-use the RolesRoomSettingsTab for Spaces ([\#6779](https://github.com/matrix-org/matrix-react-sdk/pull/6779)). Fixes vector-im/element-web#18908 vector-im/element-web#18909 and vector-im/element-web#18908. + * Deduplicate join rule management between rooms and spaces ([\#6724](https://github.com/matrix-org/matrix-react-sdk/pull/6724)). Fixes vector-im/element-web#18798 and vector-im/element-web#18798. + * Add config option to turn on in-room event sending timing metrics ([\#6766](https://github.com/matrix-org/matrix-react-sdk/pull/6766)). + * Improve the upgrade for restricted user experience ([\#6764](https://github.com/matrix-org/matrix-react-sdk/pull/6764)). Fixes vector-im/element-web#18677 and vector-im/element-web#18677. + * Improve tooltips on space quick actions and explore button ([\#6760](https://github.com/matrix-org/matrix-react-sdk/pull/6760)). Fixes vector-im/element-web#18528 and vector-im/element-web#18528. + * Make space members and user info behave more expectedly ([\#6765](https://github.com/matrix-org/matrix-react-sdk/pull/6765)). Fixes vector-im/element-web#17018 and vector-im/element-web#17018. + * hide no-op m.room.encryption events and better word param changes ([\#6747](https://github.com/matrix-org/matrix-react-sdk/pull/6747)). Fixes vector-im/element-web#18597 and vector-im/element-web#18597. + * Respect m.space.parent relations if they hold valid permissions ([\#6746](https://github.com/matrix-org/matrix-react-sdk/pull/6746)). Fixes vector-im/element-web#10935 and vector-im/element-web#10935. + * Space panel accessibility improvements ([\#6744](https://github.com/matrix-org/matrix-react-sdk/pull/6744)). Fixes vector-im/element-web#18892 and vector-im/element-web#18892. + +## 🐛 Bug Fixes + * Fix spacing for message composer buttons ([\#6854](https://github.com/matrix-org/matrix-react-sdk/pull/6854)). + * Fix accessing field on oobData which may be undefined ([\#6830](https://github.com/matrix-org/matrix-react-sdk/pull/6830)). Fixes vector-im/element-web#19085 and vector-im/element-web#19085. + * Fix reactions aria-label not being a string and thus being read as [Object object] ([\#6828](https://github.com/matrix-org/matrix-react-sdk/pull/6828)). + * Fix missing null guard in space hierarchy pagination ([\#6821](https://github.com/matrix-org/matrix-react-sdk/pull/6821)). Fixes matrix-org/element-web-rageshakes#6299 and matrix-org/element-web-rageshakes#6299. + * Fix checks to show prompt to start new chats ([\#6812](https://github.com/matrix-org/matrix-react-sdk/pull/6812)). + * Fix room list scroll jumps ([\#6777](https://github.com/matrix-org/matrix-react-sdk/pull/6777)). Fixes vector-im/element-web#17460 vector-im/element-web#18440 and vector-im/element-web#17460. Contributed by [robintown](https://github.com/robintown). + * Fix various message bubble alignment issues ([\#6785](https://github.com/matrix-org/matrix-react-sdk/pull/6785)). Fixes vector-im/element-web#18293, vector-im/element-web#18294 vector-im/element-web#18305 and vector-im/element-web#18293. Contributed by [robintown](https://github.com/robintown). + * Make message bubble font size consistent ([\#6795](https://github.com/matrix-org/matrix-react-sdk/pull/6795)). Contributed by [robintown](https://github.com/robintown). + * Fix edge cases around joining new room which does not belong to active space ([\#6797](https://github.com/matrix-org/matrix-react-sdk/pull/6797)). Fixes vector-im/element-web#19025 and vector-im/element-web#19025. + * Fix edge case space issues around creation and initial view ([\#6798](https://github.com/matrix-org/matrix-react-sdk/pull/6798)). Fixes vector-im/element-web#19023 and vector-im/element-web#19023. + * Stop spinner on space preview if the join fails ([\#6803](https://github.com/matrix-org/matrix-react-sdk/pull/6803)). Fixes vector-im/element-web#19034 and vector-im/element-web#19034. + * Fix emoji picker and stickerpicker not appearing correctly when opened ([\#6793](https://github.com/matrix-org/matrix-react-sdk/pull/6793)). Fixes vector-im/element-web#19012 and vector-im/element-web#19012. Contributed by [Palid](https://github.com/Palid). + * Fix autocomplete not having y-scroll ([\#6794](https://github.com/matrix-org/matrix-react-sdk/pull/6794)). Fixes vector-im/element-web#18997 and vector-im/element-web#18997. Contributed by [Palid](https://github.com/Palid). + * Fix broken edge case with public space creation with no alias ([\#6791](https://github.com/matrix-org/matrix-react-sdk/pull/6791)). Fixes vector-im/element-web#19003 and vector-im/element-web#19003. + * Redirect from /#/welcome to /#/home if already logged in ([\#6786](https://github.com/matrix-org/matrix-react-sdk/pull/6786)). Fixes vector-im/element-web#18990 and vector-im/element-web#18990. Contributed by [aaronraimist](https://github.com/aaronraimist). + * Fix build issues from two conflicting PRs landing without merge conflict ([\#6780](https://github.com/matrix-org/matrix-react-sdk/pull/6780)). + * Render guest settings only in public rooms/spaces ([\#6693](https://github.com/matrix-org/matrix-react-sdk/pull/6693)). Fixes vector-im/element-web#18776 and vector-im/element-web#18776. Contributed by [SimonBrandner](https://github.com/SimonBrandner). + * Fix message bubble corners being wrong in the presence of hidden events ([\#6776](https://github.com/matrix-org/matrix-react-sdk/pull/6776)). Fixes vector-im/element-web#18124 and vector-im/element-web#18124. Contributed by [robintown](https://github.com/robintown). + * Debounce read marker update on scroll ([\#6771](https://github.com/matrix-org/matrix-react-sdk/pull/6771)). Fixes vector-im/element-web#18961 and vector-im/element-web#18961. + * Use cursor:pointer on space panel buttons ([\#6770](https://github.com/matrix-org/matrix-react-sdk/pull/6770)). Fixes vector-im/element-web#18951 and vector-im/element-web#18951. + * Fix regressed tab view buttons in space update toast ([\#6761](https://github.com/matrix-org/matrix-react-sdk/pull/6761)). Fixes vector-im/element-web#18781 and vector-im/element-web#18781. + Changes in [3.31.0-rc.2](https://github.com/vector-im/element-desktop/releases/tag/v3.31.0-rc.2) (2021-09-22) ============================================================================================================= From dad60804ba336c5d3ad8844c4b07733d46e0b30a Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 27 Sep 2021 14:31:26 +0100 Subject: [PATCH 38/42] v3.31.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7b6aa14bf9..c8a7d0f74e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.31.0-rc.2", + "version": "3.31.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 3e2e3fcd2014d6f7ac7f62b53c99f2e79bc1289f Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 27 Sep 2021 14:33:46 +0100 Subject: [PATCH 39/42] Resetting package fields for development --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c8a7d0f74e..9e740fa8a7 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "bin": { "reskindex": "scripts/reskindex.js" }, - "main": "./lib/index.js", + "main": "./src/index.js", "matrix_src_main": "./src/index.js", "matrix_lib_main": "./lib/index.js", "matrix_lib_typings": "./lib/index.d.ts", @@ -210,6 +210,5 @@ "coverageReporters": [ "text" ] - }, - "typings": "./lib/index.d.ts" + } } From b635d017799e198fa0391e51af0b12e9d5843992 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 27 Sep 2021 14:33:56 +0100 Subject: [PATCH 40/42] Reset matrix-js-sdk back to develop branch --- package.json | 2 +- yarn.lock | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9e740fa8a7..89084acd68 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "13.0.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^0.1.0-beta.16", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 107caf1730..39c50464d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5806,10 +5806,9 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@13.0.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "13.0.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-13.0.0.tgz#a57a4b73892e98fcd5eb0081e4745fac567211dd" - integrity sha512-VkZhQBd6jlRNjsqjLIoHV3fnjOSljPDuJklKsF4l6yffkra7llxQctyMsrCnoiosdAQWigbLZsKSb+HbSFmcnw== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/2515d07c8fc3bf5e1afc8352e3e330cca30dde85" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From dabc13c98fbcad659a89a84dfc22e5bd4e1b01b1 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Mon, 27 Sep 2021 16:32:01 +0100 Subject: [PATCH 41/42] Extract logic to a function For better readability of the call to useAsyncMemo() Signed-off-by: Paulo Pinto --- .../views/rooms/LinkPreviewGroup.tsx | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/components/views/rooms/LinkPreviewGroup.tsx b/src/components/views/rooms/LinkPreviewGroup.tsx index c9842bdd33..f9d0c76e99 100644 --- a/src/components/views/rooms/LinkPreviewGroup.tsx +++ b/src/components/views/rooms/LinkPreviewGroup.tsx @@ -16,7 +16,7 @@ limitations under the License. import React, { useContext, useEffect } from "react"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { IPreviewUrlResponse } from "matrix-js-sdk/src/client"; +import { IPreviewUrlResponse, MatrixClient } from "matrix-js-sdk/src/client"; import { useStateToggle } from "../../../hooks/useStateToggle"; import LinkPreviewWidget from "./LinkPreviewWidget"; @@ -40,13 +40,7 @@ const LinkPreviewGroup: React.FC = ({ links, mxEvent, onCancelClick, onH const ts = mxEvent.getTs(); const previews = useAsyncMemo<[string, IPreviewUrlResponse][]>(async () => { - return Promise.all<[string, IPreviewUrlResponse] | void>(links.map(async link => { - try { - return [link, await cli.getUrlPreview(link, ts)]; - } catch (error) { - console.error("Failed to get URL preview: " + error); - } - })).then(a => a.filter(Boolean)) as Promise<[string, IPreviewUrlResponse][]>; + return fetchPreviews(cli, links, ts); }, [links, ts], []); useEffect(() => { @@ -89,4 +83,15 @@ const LinkPreviewGroup: React.FC = ({ links, mxEvent, onCancelClick, onH
; }; +const fetchPreviews = (cli: MatrixClient, links: string[], ts: number): + Promise<[string, IPreviewUrlResponse][]> => { + return Promise.all<[string, IPreviewUrlResponse] | void>(links.map(async link => { + try { + return [link, await cli.getUrlPreview(link, ts)]; + } catch (error) { + console.error("Failed to get URL preview: " + error); + } + })).then(a => a.filter(Boolean)) as Promise<[string, IPreviewUrlResponse][]>; +}; + export default LinkPreviewGroup; From 432dd994bdf70add062235c3c6aa5e2cc158fe4c Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Mon, 27 Sep 2021 18:03:15 +0100 Subject: [PATCH 42/42] Filter out invalid previews The call to cli.getUrlPreview() might return an empty object ({}), which means there is in fact no preview for that URL. Signed-off-by: Paulo Pinto --- src/components/views/rooms/LinkPreviewGroup.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/LinkPreviewGroup.tsx b/src/components/views/rooms/LinkPreviewGroup.tsx index f9d0c76e99..eed13aff0f 100644 --- a/src/components/views/rooms/LinkPreviewGroup.tsx +++ b/src/components/views/rooms/LinkPreviewGroup.tsx @@ -87,7 +87,10 @@ const fetchPreviews = (cli: MatrixClient, links: string[], ts: number): Promise<[string, IPreviewUrlResponse][]> => { return Promise.all<[string, IPreviewUrlResponse] | void>(links.map(async link => { try { - return [link, await cli.getUrlPreview(link, ts)]; + const preview = await cli.getUrlPreview(link, ts); + if (preview && Object.keys(preview).length > 0) { + return [link, preview]; + } } catch (error) { console.error("Failed to get URL preview: " + error); }