From a308a5418323f9529986791a59e27e37ce2eebae Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 31 Mar 2021 12:28:24 +0100 Subject: [PATCH 001/198] Clicking jump to bottom resets room hash --- res/css/views/rooms/_JumpToBottomButton.scss | 1 + src/components/structures/RoomView.tsx | 1 + src/components/views/rooms/JumpToBottomButton.js | 2 ++ 3 files changed, 4 insertions(+) diff --git a/res/css/views/rooms/_JumpToBottomButton.scss b/res/css/views/rooms/_JumpToBottomButton.scss index 6cb3b6bce9..a8dc2ce11c 100644 --- a/res/css/views/rooms/_JumpToBottomButton.scss +++ b/res/css/views/rooms/_JumpToBottomButton.scss @@ -52,6 +52,7 @@ limitations under the License. .mx_JumpToBottomButton_scrollDown { position: relative; + display: block; height: 38px; border-radius: 19px; box-sizing: border-box; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index a180afba29..e08461b511 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2037,6 +2037,7 @@ export default class RoomView extends React.Component { highlight={this.state.room.getUnreadNotificationCount('highlight') > 0} numUnreadMessages={this.state.numUnreadMessages} onScrollToBottomClick={this.jumpToLiveTimeline} + roomId={this.state.roomId} />); } diff --git a/src/components/views/rooms/JumpToBottomButton.js b/src/components/views/rooms/JumpToBottomButton.js index b6cefc1231..2c62877dc3 100644 --- a/src/components/views/rooms/JumpToBottomButton.js +++ b/src/components/views/rooms/JumpToBottomButton.js @@ -29,6 +29,8 @@ export default (props) => { } return (
From c5eb17eabd2fbb9245cd401397d6b385c58d5128 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 6 Apr 2021 17:26:32 +0100 Subject: [PATCH 002/198] reset highlighted event on room timeline scroll --- src/components/structures/MessagePanel.js | 4 ++++ src/components/structures/RoomView.tsx | 12 ++++++++++++ src/components/structures/ScrollPanel.js | 13 +++++++++++++ src/components/structures/TimelinePanel.js | 8 +++++++- 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 41a3015721..371ee5dce7 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -120,6 +120,9 @@ export default class MessagePanel extends React.Component { // callback which is called when the panel is scrolled. onScroll: PropTypes.func, + // callback which is called when the user interacts with the room timeline + onUserScroll: PropTypes.func, + // callback which is called when more content is needed. onFillRequest: PropTypes.func, @@ -869,6 +872,7 @@ export default class MessagePanel extends React.Component { ref={this._scrollPanel} className={className} onScroll={this.props.onScroll} + onUserScroll={this.props.onUserScroll} onResize={this.onResize} onFillRequest={this.props.onFillRequest} onUnfillRequest={this.props.onUnfillRequest} diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index e08461b511..8d815c79a1 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -637,6 +637,17 @@ export default class RoomView extends React.Component { SettingsStore.unwatchSetting(this.layoutWatcherRef); } + private onUserScroll = () => { + if (this.state.initialEventId && this.state.isInitialEventHighlighted) { + dis.dispatch({ + action: 'view_room', + room_id: this.state.room.roomId, + event_id: this.state.initialEventId, + highlighted: false, + }); + } + } + private onLayoutChange = () => { this.setState({ layout: SettingsStore.getValue("layout"), @@ -2011,6 +2022,7 @@ export default class RoomView extends React.Component { eventId={this.state.initialEventId} eventPixelOffset={this.state.initialEventPixelOffset} onScroll={this.onMessageListScroll} + onUserScroll={this.onUserScroll} onReadMarkerUpdated={this.updateTopUnreadMessagesBar} showUrlPreview = {this.state.showUrlPreview} className={messagePanelClassNames} diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index 3a9b2b8a77..5cb9437b81 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -133,6 +133,10 @@ export default class ScrollPanel extends React.Component { */ onScroll: PropTypes.func, + /* onUserScroll: callback which is called when the user interacts with the room timeline + */ + onUserScroll: PropTypes.func, + /* className: classnames to add to the top-level div */ className: PropTypes.string, @@ -535,31 +539,39 @@ export default class ScrollPanel extends React.Component { * @param {object} ev the keyboard event */ handleScrollKey = ev => { + let isScrolling = false; switch (ev.key) { case Key.PAGE_UP: if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { + isScrolling = true; this.scrollRelative(-1); } break; case Key.PAGE_DOWN: if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { + isScrolling = true; this.scrollRelative(1); } break; case Key.HOME: if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { + isScrolling = true; this.scrollToTop(); } break; case Key.END: if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { + isScrolling = true; this.scrollToBottom(); } break; } + if (isScrolling && this.props.onUserScroll) { + this.props.onUserScroll(ev); + } }; /* Scroll the panel to bring the DOM node with the scroll token @@ -896,6 +908,7 @@ export default class ScrollPanel extends React.Component { // list-style-type: none; is no longer a list return ( { this.props.fixedChildren }
diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 12f5d6e890..b1d1e16719 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -92,6 +92,9 @@ class TimelinePanel extends React.Component { // callback which is called when the panel is scrolled. onScroll: PropTypes.func, + // callback which is called when the user interacts with the room timeline + onUserScroll: PropTypes.func, + // callback which is called when the read-up-to mark is updated. onReadMarkerUpdated: PropTypes.func, @@ -255,7 +258,9 @@ class TimelinePanel extends React.Component { console.warn("Replacing timelineSet on a TimelinePanel - confusion may ensue"); } - if (newProps.eventId != this.props.eventId) { + const differentEventId = newProps.eventId != this.props.eventId; + const differentHighlightedEventId = newProps.highlightedEventId != this.props.highlightedEventId; + if (differentEventId || differentHighlightedEventId) { console.log("TimelinePanel switching to eventId " + newProps.eventId + " (was " + this.props.eventId + ")"); return this._initTimeline(newProps); @@ -1438,6 +1443,7 @@ class TimelinePanel extends React.Component { ourUserId={MatrixClientPeg.get().credentials.userId} stickyBottom={stickyBottom} onScroll={this.onMessageListScroll} + onUserScroll={this.props.onUserScroll} onFillRequest={this.onMessageListFillRequest} onUnfillRequest={this.onMessageListUnfillRequest} isTwelveHour={this.state.isTwelveHour} From ef1da6acddf04530100c467274d0fb1f4dae7907 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 9 Apr 2021 09:02:47 +0100 Subject: [PATCH 003/198] remove wrongly committed orig file --- src/components/structures/ScrollPanel.js.orig | 938 ------------------ 1 file changed, 938 deletions(-) delete mode 100644 src/components/structures/ScrollPanel.js.orig diff --git a/src/components/structures/ScrollPanel.js.orig b/src/components/structures/ScrollPanel.js.orig deleted file mode 100644 index 5909632aa6..0000000000 --- a/src/components/structures/ScrollPanel.js.orig +++ /dev/null @@ -1,938 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket 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 React, {createRef} from "react"; -import PropTypes from 'prop-types'; -import Timer from '../../utils/Timer'; -import AutoHideScrollbar from "./AutoHideScrollbar"; -import {replaceableComponent} from "../../utils/replaceableComponent"; -import {getKeyBindingsManager, RoomAction} from "../../KeyBindingsManager"; - -const DEBUG_SCROLL = false; - -// The amount of extra scroll distance to allow prior to unfilling. -// See _getExcessHeight. -const UNPAGINATION_PADDING = 6000; -// The number of milliseconds to debounce calls to onUnfillRequest, to prevent -// many scroll events causing many unfilling requests. -const UNFILL_REQUEST_DEBOUNCE_MS = 200; -// _updateHeight makes the height a ceiled multiple of this so we -// don't have to update the height too often. It also allows the user -// to scroll past the pagination spinner a bit so they don't feel blocked so -// much while the content loads. -const PAGE_SIZE = 400; - -let debuglog; -if (DEBUG_SCROLL) { - // using bind means that we get to keep useful line numbers in the console - debuglog = console.log.bind(console, "ScrollPanel debuglog:"); -} else { - debuglog = function() {}; -} - -/* This component implements an intelligent scrolling list. - * - * It wraps a list of
  • children; when items are added to the start or end - * of the list, the scroll position is updated so that the user still sees the - * same position in the list. - * - * It also provides a hook which allows parents to provide more list elements - * when we get close to the start or end of the list. - * - * Each child element should have a 'data-scroll-tokens'. This string of - * comma-separated tokens may contain a single token or many, where many indicates - * that the element contains elements that have scroll tokens themselves. The first - * token in 'data-scroll-tokens' is used to serialise the scroll state, and returned - * as the 'trackedScrollToken' attribute by getScrollState(). - * - * IMPORTANT: INDIVIDUAL TOKENS WITHIN 'data-scroll-tokens' MUST NOT CONTAIN COMMAS. - * - * Some notes about the implementation: - * - * The saved 'scrollState' can exist in one of two states: - * - * - stuckAtBottom: (the default, and restored by resetScrollState): the - * viewport is scrolled down as far as it can be. When the children are - * updated, the scroll position will be updated to ensure it is still at - * the bottom. - * - * - fixed, in which the viewport is conceptually tied at a specific scroll - * offset. We don't save the absolute scroll offset, because that would be - * affected by window width, zoom level, amount of scrollback, etc. Instead - * we save an identifier for the last fully-visible message, and the number - * of pixels the window was scrolled below it - which is hopefully near - * enough. - * - * The 'stickyBottom' property controls the behaviour when we reach the bottom - * of the window (either through a user-initiated scroll, or by calling - * scrollToBottom). If stickyBottom is enabled, the scrollState will enter - * 'stuckAtBottom' state - ensuring that new additions cause the window to - * scroll down further. If stickyBottom is disabled, we just save the scroll - * offset as normal. - */ - -@replaceableComponent("structures.ScrollPanel") -export default class ScrollPanel extends React.Component { - static propTypes = { - /* stickyBottom: if set to true, then once the user hits the bottom of - * the list, any new children added to the list will cause the list to - * scroll down to show the new element, rather than preserving the - * existing view. - */ - stickyBottom: PropTypes.bool, - - /* startAtBottom: if set to true, the view is assumed to start - * scrolled to the bottom. - * XXX: It's likely this is unnecessary and can be derived from - * stickyBottom, but I'm adding an extra parameter to ensure - * behaviour stays the same for other uses of ScrollPanel. - * If so, let's remove this parameter down the line. - */ - startAtBottom: PropTypes.bool, - - /* onFillRequest(backwards): a callback which is called on scroll when - * the user nears the start (backwards = true) or end (backwards = - * false) of the list. - * - * This should return a promise; no more calls will be made until the - * promise completes. - * - * The promise should resolve to true if there is more data to be - * retrieved in this direction (in which case onFillRequest may be - * called again immediately), or false if there is no more data in this - * directon (at this time) - which will stop the pagination cycle until - * the user scrolls again. - */ - onFillRequest: PropTypes.func, - - /* onUnfillRequest(backwards): a callback which is called on scroll when - * there are children elements that are far out of view and could be removed - * without causing pagination to occur. - * - * This function should accept a boolean, which is true to indicate the back/top - * of the panel and false otherwise, and a scroll token, which refers to the - * first element to remove if removing from the front/bottom, and last element - * to remove if removing from the back/top. - */ - onUnfillRequest: PropTypes.func, - - /* onScroll: a callback which is called whenever any scroll happens. - */ - onScroll: PropTypes.func, - - /* onUserScroll: callback which is called when the user interacts with the room timeline - */ - onUserScroll: PropTypes.func, - - /* className: classnames to add to the top-level div - */ - className: PropTypes.string, - - /* style: styles to add to the top-level div - */ - style: PropTypes.object, - - /* resizeNotifier: ResizeNotifier to know when middle column has changed size - */ - resizeNotifier: PropTypes.object, - - /* fixedChildren: allows for children to be passed which are rendered outside - * of the wrapper - */ - fixedChildren: PropTypes.node, - }; - - static defaultProps = { - stickyBottom: true, - startAtBottom: true, - onFillRequest: function(backwards) { return Promise.resolve(false); }, - onUnfillRequest: function(backwards, scrollToken) {}, - onScroll: function() {}, - }; - - constructor(props) { - super(props); - - this._pendingFillRequests = {b: null, f: null}; - - if (this.props.resizeNotifier) { - this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize); - } - - this.resetScrollState(); - - this._itemlist = createRef(); - } - - componentDidMount() { - this.checkScroll(); - } - - componentDidUpdate() { - // after adding event tiles, we may need to tweak the scroll (either to - // keep at the bottom of the timeline, or to maintain the view after - // adding events to the top). - // - // This will also re-check the fill state, in case the paginate was inadequate - this.checkScroll(); - this.updatePreventShrinking(); - } - - componentWillUnmount() { - // set a boolean to say we've been unmounted, which any pending - // promises can use to throw away their results. - // - // (We could use isMounted(), but facebook have deprecated that.) - this.unmounted = true; - - if (this.props.resizeNotifier) { - this.props.resizeNotifier.removeListener("middlePanelResizedNoisy", this.onResize); - } - } - - onScroll = ev => { - // skip scroll events caused by resizing - if (this.props.resizeNotifier && this.props.resizeNotifier.isResizing) return; - debuglog("onScroll", this._getScrollNode().scrollTop); - this._scrollTimeout.restart(); - this._saveScrollState(); - this.updatePreventShrinking(); - this.props.onScroll(ev); - this.checkFillState(); - }; - - onResize = () => { - debuglog("onResize"); - this.checkScroll(); - // update preventShrinkingState if present - if (this.preventShrinkingState) { - this.preventShrinking(); - } - }; - - // after an update to the contents of the panel, check that the scroll is - // where it ought to be, and set off pagination requests if necessary. - checkScroll = () => { - if (this.unmounted) { - return; - } - this._restoreSavedScrollState(); - this.checkFillState(); - }; - - // return true if the content is fully scrolled down right now; else false. - // - // note that this is independent of the 'stuckAtBottom' state - it is simply - // about whether the content is scrolled down right now, irrespective of - // whether it will stay that way when the children update. - isAtBottom = () => { - const sn = this._getScrollNode(); - // fractional values (both too big and too small) - // for scrollTop happen on certain browsers/platforms - // when scrolled all the way down. E.g. Chrome 72 on debian. - // so check difference <= 1; - return Math.abs(sn.scrollHeight - (sn.scrollTop + sn.clientHeight)) <= 1; - }; - - // returns the vertical height in the given direction that can be removed from - // the content box (which has a height of scrollHeight, see checkFillState) without - // pagination occuring. - // - // padding* = UNPAGINATION_PADDING - // - // ### Region determined as excess. - // - // .---------. - - - // |#########| | | - // |#########| - | scrollTop | - // | | | padding* | | - // | | | | | - // .-+---------+-. - - | | - // : | | : | | | - // : | | : | clientHeight | | - // : | | : | | | - // .-+---------+-. - - | - // | | | | | | - // | | | | | clientHeight | scrollHeight - // | | | | | | - // `-+---------+-' - | - // : | | : | | - // : | | : | clientHeight | - // : | | : | | - // `-+---------+-' - - | - // | | | padding* | - // | | | | - // |#########| - | - // |#########| | - // `---------' - - _getExcessHeight(backwards) { - const sn = this._getScrollNode(); - const contentHeight = this._getMessagesHeight(); - const listHeight = this._getListHeight(); - const clippedHeight = contentHeight - listHeight; - const unclippedScrollTop = sn.scrollTop + clippedHeight; - - if (backwards) { - return unclippedScrollTop - sn.clientHeight - UNPAGINATION_PADDING; - } else { - return contentHeight - (unclippedScrollTop + 2*sn.clientHeight) - UNPAGINATION_PADDING; - } - } - - // check the scroll state and send out backfill requests if necessary. - checkFillState = async (depth=0) => { - if (this.unmounted) { - return; - } - - const isFirstCall = depth === 0; - const sn = this._getScrollNode(); - - // if there is less than a screenful of messages above or below the - // viewport, try to get some more messages. - // - // scrollTop is the number of pixels between the top of the content and - // the top of the viewport. - // - // scrollHeight is the total height of the content. - // - // clientHeight is the height of the viewport (excluding borders, - // margins, and scrollbars). - // - // - // .---------. - - - // | | | scrollTop | - // .-+---------+-. - - | - // | | | | | | - // | | | | | clientHeight | scrollHeight - // | | | | | | - // `-+---------+-' - | - // | | | - // | | | - // `---------' - - // - - // as filling is async and recursive, - // don't allow more than 1 chain of calls concurrently - // do make a note when a new request comes in while already running one, - // so we can trigger a new chain of calls once done. - if (isFirstCall) { - if (this._isFilling) { - debuglog("_isFilling: not entering while request is ongoing, marking for a subsequent request"); - this._fillRequestWhileRunning = true; - return; - } - debuglog("_isFilling: setting"); - this._isFilling = true; - } - - const itemlist = this._itemlist.current; - const firstTile = itemlist && itemlist.firstElementChild; - const contentTop = firstTile && firstTile.offsetTop; - const fillPromises = []; - - // if scrollTop gets to 1 screen from the top of the first tile, - // try backward filling - if (!firstTile || (sn.scrollTop - contentTop) < sn.clientHeight) { - // need to back-fill - fillPromises.push(this._maybeFill(depth, true)); - } - // if scrollTop gets to 2 screens from the end (so 1 screen below viewport), - // try forward filling - if ((sn.scrollHeight - sn.scrollTop) < sn.clientHeight * 2) { - // need to forward-fill - fillPromises.push(this._maybeFill(depth, false)); - } - - if (fillPromises.length) { - try { - await Promise.all(fillPromises); - } catch (err) { - console.error(err); - } - } - if (isFirstCall) { - debuglog("_isFilling: clearing"); - this._isFilling = false; - } - - if (this._fillRequestWhileRunning) { - this._fillRequestWhileRunning = false; - this.checkFillState(); - } - }; - - // check if unfilling is possible and send an unfill request if necessary - _checkUnfillState(backwards) { - let excessHeight = this._getExcessHeight(backwards); - if (excessHeight <= 0) { - return; - } - - const origExcessHeight = excessHeight; - - const tiles = this._itemlist.current.children; - - // The scroll token of the first/last tile to be unpaginated - let markerScrollToken = null; - - // Subtract heights of tiles to simulate the tiles being unpaginated until the - // excess height is less than the height of the next tile to subtract. This - // prevents excessHeight becoming negative, which could lead to future - // pagination. - // - // If backwards is true, we unpaginate (remove) tiles from the back (top). - let tile; - for (let i = 0; i < tiles.length; i++) { - tile = tiles[backwards ? i : tiles.length - 1 - i]; - // Subtract height of tile as if it were unpaginated - excessHeight -= tile.clientHeight; - //If removing the tile would lead to future pagination, break before setting scroll token - if (tile.clientHeight > excessHeight) { - break; - } - // The tile may not have a scroll token, so guard it - if (tile.dataset.scrollTokens) { - markerScrollToken = tile.dataset.scrollTokens.split(',')[0]; - } - } - - if (markerScrollToken) { - // Use a debouncer to prevent multiple unfill calls in quick succession - // This is to make the unfilling process less aggressive - if (this._unfillDebouncer) { - clearTimeout(this._unfillDebouncer); - } - this._unfillDebouncer = setTimeout(() => { - this._unfillDebouncer = null; - debuglog("unfilling now", backwards, origExcessHeight); - this.props.onUnfillRequest(backwards, markerScrollToken); - }, UNFILL_REQUEST_DEBOUNCE_MS); - } - } - - // check if there is already a pending fill request. If not, set one off. - _maybeFill(depth, backwards) { - const dir = backwards ? 'b' : 'f'; - if (this._pendingFillRequests[dir]) { - debuglog("Already a "+dir+" fill in progress - not starting another"); - return; - } - - debuglog("starting "+dir+" fill"); - - // onFillRequest can end up calling us recursively (via onScroll - // events) so make sure we set this before firing off the call. - this._pendingFillRequests[dir] = true; - - // wait 1ms before paginating, because otherwise - // this will block the scroll event handler for +700ms - // if messages are already cached in memory, - // This would cause jumping to happen on Chrome/macOS. - return new Promise(resolve => setTimeout(resolve, 1)).then(() => { - return this.props.onFillRequest(backwards); - }).finally(() => { - this._pendingFillRequests[dir] = false; - }).then((hasMoreResults) => { - if (this.unmounted) { - return; - } - // Unpaginate once filling is complete - this._checkUnfillState(!backwards); - - debuglog(""+dir+" fill complete; hasMoreResults:"+hasMoreResults); - if (hasMoreResults) { - // further pagination requests have been disabled until now, so - // it's time to check the fill state again in case the pagination - // was insufficient. - return this.checkFillState(depth + 1); - } - }); - } - - /* get the current scroll state. This returns an object with the following - * properties: - * - * boolean stuckAtBottom: true if we are tracking the bottom of the - * scroll. false if we are tracking a particular child. - * - * string trackedScrollToken: undefined if stuckAtBottom is true; if it is - * false, the first token in data-scroll-tokens of the child which we are - * tracking. - * - * number bottomOffset: undefined if stuckAtBottom is true; if it is false, - * the number of pixels the bottom of the tracked child is above the - * bottom of the scroll panel. - */ - getScrollState = () => this.scrollState; - - /* reset the saved scroll state. - * - * This is useful if the list is being replaced, and you don't want to - * preserve scroll even if new children happen to have the same scroll - * tokens as old ones. - * - * This will cause the viewport to be scrolled down to the bottom on the - * next update of the child list. This is different to scrollToBottom(), - * which would save the current bottom-most child as the active one (so is - * no use if no children exist yet, or if you are about to replace the - * child list.) - */ - resetScrollState = () => { - this.scrollState = { - stuckAtBottom: this.props.startAtBottom, - }; - this._bottomGrowth = 0; - this._pages = 0; - this._scrollTimeout = new Timer(100); - this._heightUpdateInProgress = false; - }; - - /** - * jump to the top of the content. - */ - scrollToTop = () => { - this._getScrollNode().scrollTop = 0; - this._saveScrollState(); - }; - - /** - * jump to the bottom of the content. - */ - scrollToBottom = () => { - // the easiest way to make sure that the scroll state is correctly - // saved is to do the scroll, then save the updated state. (Calculating - // it ourselves is hard, and we can't rely on an onScroll callback - // happening, since there may be no user-visible change here). - const sn = this._getScrollNode(); - sn.scrollTop = sn.scrollHeight; - this._saveScrollState(); - }; - - /** - * Page up/down. - * - * @param {number} mult: -1 to page up, +1 to page down - */ - scrollRelative = mult => { - const scrollNode = this._getScrollNode(); - const delta = mult * scrollNode.clientHeight * 0.5; - scrollNode.scrollBy(0, delta); - this._saveScrollState(); - }; - - /** - * Scroll up/down in response to a scroll key - * @param {object} ev the keyboard event - */ - handleScrollKey = ev => { -<<<<<<< HEAD - let isScrolling = false; - switch (ev.key) { - case Key.PAGE_UP: - if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { - isScrolling = true; - this.scrollRelative(-1); - } - break; - - case Key.PAGE_DOWN: - if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { - isScrolling = true; - this.scrollRelative(1); - } - break; - - case Key.HOME: - if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { - isScrolling = true; - this.scrollToTop(); - } - break; - - case Key.END: - if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) { - isScrolling = true; - this.scrollToBottom(); - } -======= - const roomAction = getKeyBindingsManager().getRoomAction(ev); - switch (roomAction) { - case RoomAction.ScrollUp: - this.scrollRelative(-1); - break; - case RoomAction.RoomScrollDown: - this.scrollRelative(1); - break; - case RoomAction.JumpToFirstMessage: - this.scrollToTop(); - break; - case RoomAction.JumpToLatestMessage: - this.scrollToBottom(); ->>>>>>> develop - break; - } - if (isScrolling && this.props.onUserScroll) { - this.props.onUserScroll(ev); - } - }; - - /* Scroll the panel to bring the DOM node with the scroll token - * `scrollToken` into view. - * - * offsetBase gives the reference point for the pixelOffset. 0 means the - * top of the container, 1 means the bottom, and fractional values mean - * somewhere in the middle. If omitted, it defaults to 0. - * - * pixelOffset gives the number of pixels *above* the offsetBase that the - * node (specifically, the bottom of it) will be positioned. If omitted, it - * defaults to 0. - */ - scrollToToken = (scrollToken, pixelOffset, offsetBase) => { - pixelOffset = pixelOffset || 0; - offsetBase = offsetBase || 0; - - // set the trackedScrollToken so we can get the node through _getTrackedNode - this.scrollState = { - stuckAtBottom: false, - trackedScrollToken: scrollToken, - }; - const trackedNode = this._getTrackedNode(); - const scrollNode = this._getScrollNode(); - if (trackedNode) { - // set the scrollTop to the position we want. - // note though, that this might not succeed if the combination of offsetBase and pixelOffset - // would position the trackedNode towards the top of the viewport. - // This because when setting the scrollTop only 10 or so events might be loaded, - // not giving enough content below the trackedNode to scroll downwards - // enough so it ends up in the top of the viewport. - debuglog("scrollToken: setting scrollTop", {offsetBase, pixelOffset, offsetTop: trackedNode.offsetTop}); - scrollNode.scrollTop = (trackedNode.offsetTop - (scrollNode.clientHeight * offsetBase)) + pixelOffset; - this._saveScrollState(); - } - }; - - _saveScrollState() { - if (this.props.stickyBottom && this.isAtBottom()) { - this.scrollState = { stuckAtBottom: true }; - debuglog("saved stuckAtBottom state"); - return; - } - - const scrollNode = this._getScrollNode(); - const viewportBottom = scrollNode.scrollHeight - (scrollNode.scrollTop + scrollNode.clientHeight); - - const itemlist = this._itemlist.current; - const messages = itemlist.children; - let node = null; - - // TODO: do a binary search here, as items are sorted by offsetTop - // loop backwards, from bottom-most message (as that is the most common case) - for (let i = messages.length-1; i >= 0; --i) { - if (!messages[i].dataset.scrollTokens) { - continue; - } - node = messages[i]; - // break at the first message (coming from the bottom) - // that has it's offsetTop above the bottom of the viewport. - if (this._topFromBottom(node) > viewportBottom) { - // Use this node as the scrollToken - break; - } - } - - if (!node) { - debuglog("unable to save scroll state: found no children in the viewport"); - return; - } - const scrollToken = node.dataset.scrollTokens.split(',')[0]; - debuglog("saving anchored scroll state to message", node && node.innerText, scrollToken); - const bottomOffset = this._topFromBottom(node); - this.scrollState = { - stuckAtBottom: false, - trackedNode: node, - trackedScrollToken: scrollToken, - bottomOffset: bottomOffset, - pixelOffset: bottomOffset - viewportBottom, //needed for restoring the scroll position when coming back to the room - }; - } - - async _restoreSavedScrollState() { - const scrollState = this.scrollState; - - if (scrollState.stuckAtBottom) { - const sn = this._getScrollNode(); - if (sn.scrollTop !== sn.scrollHeight) { - sn.scrollTop = sn.scrollHeight; - } - } else if (scrollState.trackedScrollToken) { - const itemlist = this._itemlist.current; - const trackedNode = this._getTrackedNode(); - if (trackedNode) { - const newBottomOffset = this._topFromBottom(trackedNode); - const bottomDiff = newBottomOffset - scrollState.bottomOffset; - this._bottomGrowth += bottomDiff; - scrollState.bottomOffset = newBottomOffset; - const newHeight = `${this._getListHeight()}px`; - if (itemlist.style.height !== newHeight) { - itemlist.style.height = newHeight; - } - debuglog("balancing height because messages below viewport grew by", bottomDiff); - } - } - if (!this._heightUpdateInProgress) { - this._heightUpdateInProgress = true; - try { - await this._updateHeight(); - } finally { - this._heightUpdateInProgress = false; - } - } else { - debuglog("not updating height because request already in progress"); - } - } - - // need a better name that also indicates this will change scrollTop? Rebalance height? Reveal content? - async _updateHeight() { - // wait until user has stopped scrolling - if (this._scrollTimeout.isRunning()) { - debuglog("updateHeight waiting for scrolling to end ... "); - await this._scrollTimeout.finished(); - } else { - debuglog("updateHeight getting straight to business, no scrolling going on."); - } - - // We might have unmounted since the timer finished, so abort if so. - if (this.unmounted) { - return; - } - - const sn = this._getScrollNode(); - const itemlist = this._itemlist.current; - const contentHeight = this._getMessagesHeight(); - const minHeight = sn.clientHeight; - const height = Math.max(minHeight, contentHeight); - this._pages = Math.ceil(height / PAGE_SIZE); - this._bottomGrowth = 0; - const newHeight = `${this._getListHeight()}px`; - - const scrollState = this.scrollState; - if (scrollState.stuckAtBottom) { - if (itemlist.style.height !== newHeight) { - itemlist.style.height = newHeight; - } - if (sn.scrollTop !== sn.scrollHeight) { - sn.scrollTop = sn.scrollHeight; - } - debuglog("updateHeight to", newHeight); - } else if (scrollState.trackedScrollToken) { - const trackedNode = this._getTrackedNode(); - // if the timeline has been reloaded - // this can be called before scrollToBottom or whatever has been called - // so don't do anything if the node has disappeared from - // the currently filled piece of the timeline - if (trackedNode) { - const oldTop = trackedNode.offsetTop; - if (itemlist.style.height !== newHeight) { - itemlist.style.height = newHeight; - } - const newTop = trackedNode.offsetTop; - const topDiff = newTop - oldTop; - // important to scroll by a relative amount as - // reading scrollTop and then setting it might - // yield out of date values and cause a jump - // when setting it - sn.scrollBy(0, topDiff); - debuglog("updateHeight to", {newHeight, topDiff}); - } - } - } - - _getTrackedNode() { - const scrollState = this.scrollState; - const trackedNode = scrollState.trackedNode; - - if (!trackedNode || !trackedNode.parentElement) { - let node; - const messages = this._itemlist.current.children; - const scrollToken = scrollState.trackedScrollToken; - - for (let i = messages.length-1; i >= 0; --i) { - const m = messages[i]; - // 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens - // There might only be one scroll token - if (m.dataset.scrollTokens && - m.dataset.scrollTokens.split(',').indexOf(scrollToken) !== -1) { - node = m; - break; - } - } - if (node) { - debuglog("had to find tracked node again for " + scrollState.trackedScrollToken); - } - scrollState.trackedNode = node; - } - - if (!scrollState.trackedNode) { - debuglog("No node with ; '"+scrollState.trackedScrollToken+"'"); - return; - } - - return scrollState.trackedNode; - } - - _getListHeight() { - return this._bottomGrowth + (this._pages * PAGE_SIZE); - } - - _getMessagesHeight() { - const itemlist = this._itemlist.current; - const lastNode = itemlist.lastElementChild; - const lastNodeBottom = lastNode ? lastNode.offsetTop + lastNode.clientHeight : 0; - const firstNodeTop = itemlist.firstElementChild ? itemlist.firstElementChild.offsetTop : 0; - // 18 is itemlist padding - return lastNodeBottom - firstNodeTop + (18 * 2); - } - - _topFromBottom(node) { - // current capped height - distance from top = distance from bottom of container to top of tracked element - return this._itemlist.current.clientHeight - node.offsetTop; - } - - /* get the DOM node which has the scrollTop property we care about for our - * message panel. - */ - _getScrollNode() { - if (this.unmounted) { - // this shouldn't happen, but when it does, turn the NPE into - // something more meaningful. - throw new Error("ScrollPanel._getScrollNode called when unmounted"); - } - - if (!this._divScroll) { - // Likewise, we should have the ref by this point, but if not - // turn the NPE into something meaningful. - throw new Error("ScrollPanel._getScrollNode called before AutoHideScrollbar ref collected"); - } - - return this._divScroll; - } - - _collectScroll = divScroll => { - this._divScroll = divScroll; - }; - - /** - Mark the bottom offset of the last tile so we can balance it out when - anything below it changes, by calling updatePreventShrinking, to keep - the same minimum bottom offset, effectively preventing the timeline to shrink. - */ - preventShrinking = () => { - const messageList = this._itemlist.current; - const tiles = messageList && messageList.children; - if (!messageList) { - return; - } - let lastTileNode; - for (let i = tiles.length - 1; i >= 0; i--) { - const node = tiles[i]; - if (node.dataset.scrollTokens) { - lastTileNode = node; - break; - } - } - if (!lastTileNode) { - return; - } - this.clearPreventShrinking(); - const offsetFromBottom = messageList.clientHeight - (lastTileNode.offsetTop + lastTileNode.clientHeight); - this.preventShrinkingState = { - offsetFromBottom: offsetFromBottom, - offsetNode: lastTileNode, - }; - debuglog("prevent shrinking, last tile ", offsetFromBottom, "px from bottom"); - }; - - /** Clear shrinking prevention. Used internally, and when the timeline is reloaded. */ - clearPreventShrinking = () => { - const messageList = this._itemlist.current; - const balanceElement = messageList && messageList.parentElement; - if (balanceElement) balanceElement.style.paddingBottom = null; - this.preventShrinkingState = null; - debuglog("prevent shrinking cleared"); - }; - - /** - update the container padding to balance - the bottom offset of the last tile since - preventShrinking was called. - Clears the prevent-shrinking state ones the offset - from the bottom of the marked tile grows larger than - what it was when marking. - */ - updatePreventShrinking = () => { - if (this.preventShrinkingState) { - const sn = this._getScrollNode(); - const scrollState = this.scrollState; - const messageList = this._itemlist.current; - const {offsetNode, offsetFromBottom} = this.preventShrinkingState; - // element used to set paddingBottom to balance the typing notifs disappearing - const balanceElement = messageList.parentElement; - // if the offsetNode got unmounted, clear - let shouldClear = !offsetNode.parentElement; - // also if 200px from bottom - if (!shouldClear && !scrollState.stuckAtBottom) { - const spaceBelowViewport = sn.scrollHeight - (sn.scrollTop + sn.clientHeight); - shouldClear = spaceBelowViewport >= 200; - } - // try updating if not clearing - if (!shouldClear) { - const currentOffset = messageList.clientHeight - (offsetNode.offsetTop + offsetNode.clientHeight); - const offsetDiff = offsetFromBottom - currentOffset; - if (offsetDiff > 0) { - balanceElement.style.paddingBottom = `${offsetDiff}px`; - debuglog("update prevent shrinking ", offsetDiff, "px from bottom"); - } else if (offsetDiff < 0) { - shouldClear = true; - } - } - if (shouldClear) { - this.clearPreventShrinking(); - } - } - }; - - render() { - // TODO: the classnames on the div and ol could do with being updated to - // reflect the fact that we don't necessarily contain a list of messages. - // it's not obvious why we have a separate div and ol anyway. - - // give the
      an explicit role=list because Safari+VoiceOver seems to think an ordered-list with - // list-style-type: none; is no longer a list - return ( - { this.props.fixedChildren } -
      -
        - { this.props.children } -
      -
      -
      - ); - } -} From d450822bfd99824bc0aa8b61aec603c71ddd4dd0 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 9 Apr 2021 11:17:50 +0100 Subject: [PATCH 004/198] fix linting issues in ScrollPanel --- src/components/structures/ScrollPanel.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/structures/ScrollPanel.js b/src/components/structures/ScrollPanel.js index 80447fd556..3c305524b8 100644 --- a/src/components/structures/ScrollPanel.js +++ b/src/components/structures/ScrollPanel.js @@ -539,24 +539,24 @@ export default class ScrollPanel extends React.Component { * @param {object} ev the keyboard event */ handleScrollKey = ev => { - let isScrolling = false; + let isScrolling = false; const roomAction = getKeyBindingsManager().getRoomAction(ev); switch (roomAction) { case RoomAction.ScrollUp: this.scrollRelative(-1); - isScrolling = true; + isScrolling = true; break; case RoomAction.RoomScrollDown: this.scrollRelative(1); - isScrolling = true; + isScrolling = true; break; case RoomAction.JumpToFirstMessage: this.scrollToTop(); - isScrolling = true; + isScrolling = true; break; case RoomAction.JumpToLatestMessage: this.scrollToBottom(); - isScrolling = true; + isScrolling = true; break; } if (isScrolling && this.props.onUserScroll) { From d148b521f5ab71498ba5a63559871273c6334d49 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 9 Apr 2021 11:23:41 +0100 Subject: [PATCH 005/198] Revert JumpToBottom to button and use dispatcher to view room --- src/components/structures/RoomView.tsx | 6 ++++-- src/components/views/rooms/JumpToBottomButton.js | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 8d815c79a1..52608d1db1 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1509,8 +1509,10 @@ export default class RoomView extends React.Component { // jump down to the bottom of this room, where new events are arriving private jumpToLiveTimeline = () => { - this.messagePanel.jumpToLiveTimeline(); - dis.fire(Action.FocusComposer); + dis.dispatch({ + action: 'view_room', + room_id: this.state.room.roomId, + }); }; // jump up to wherever our read marker is diff --git a/src/components/views/rooms/JumpToBottomButton.js b/src/components/views/rooms/JumpToBottomButton.js index 2c62877dc3..b6cefc1231 100644 --- a/src/components/views/rooms/JumpToBottomButton.js +++ b/src/components/views/rooms/JumpToBottomButton.js @@ -29,8 +29,6 @@ export default (props) => { } return (
      From 61a260cd405eacac4c52777bd048657a9fda3702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 18 Apr 2021 15:40:56 +0200 Subject: [PATCH 006/198] Format mxid --- res/css/views/messages/_SenderProfile.scss | 12 ++++++ .../views/messages/SenderProfile.js | 43 ++++++++++++++----- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/res/css/views/messages/_SenderProfile.scss b/res/css/views/messages/_SenderProfile.scss index 655cb39489..e3a38bad6d 100644 --- a/res/css/views/messages/_SenderProfile.scss +++ b/res/css/views/messages/_SenderProfile.scss @@ -15,6 +15,18 @@ limitations under the License. */ .mx_SenderProfile_name { + display: flex; + align-items: center; + gap: 5px; +} + +.mx_SenderProfile_displayName { font-weight: 600; } +.mx_SenderProfile_mxid { + font-weight: 600; + font-family: monospace; + font-size: 1rem; + color: gray; +} diff --git a/src/components/views/messages/SenderProfile.js b/src/components/views/messages/SenderProfile.js index bd10526799..264d4ed3de 100644 --- a/src/components/views/messages/SenderProfile.js +++ b/src/components/views/messages/SenderProfile.js @@ -89,7 +89,21 @@ export default class SenderProfile extends React.Component { render() { const {mxEvent} = this.props; const colorClass = getUserNameColorClass(mxEvent.getSender()); - const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); + + let disambiguate; + let displayName; + let mxid; + + const sender = mxEvent.sender; + if (sender) { + disambiguate = sender.disambiguate; + displayName = sender.rawDisplayName; + mxid = sender.userId; + } else { + disambiguate = false; + displayName = mxEvent.getSender(); + mxid = mxEvent.getSender(); + } const {msgtype} = mxEvent.getContent(); if (msgtype === 'm.emote') { @@ -108,20 +122,29 @@ export default class SenderProfile extends React.Component { />; } - const nameElem = name || ''; - - // Name + flair - const nameFlair = - - { nameElem } + const displayNameElement = ( + + { displayName || '' } - { flair } - ; + ); + + let mxidElement; + if (disambiguate) { + mxidElement = ( + + { `[${mxid || ""}]` } + + ); + } return (
      - { nameFlair } +
      + { displayNameElement } + { mxidElement } + { flair } +
      ); From 39eb487f49718453ac152d97119f764527a67821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 20 Apr 2021 11:09:03 +0200 Subject: [PATCH 007/198] TS conversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../{SenderProfile.js => SenderProfile.tsx} | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) rename src/components/views/messages/{SenderProfile.js => SenderProfile.tsx} (82%) diff --git a/src/components/views/messages/SenderProfile.js b/src/components/views/messages/SenderProfile.tsx similarity index 82% rename from src/components/views/messages/SenderProfile.js rename to src/components/views/messages/SenderProfile.tsx index 264d4ed3de..fa635eaedb 100644 --- a/src/components/views/messages/SenderProfile.js +++ b/src/components/views/messages/SenderProfile.tsx @@ -15,26 +15,37 @@ */ import React from 'react'; -import PropTypes from 'prop-types'; import Flair from '../elements/Flair.js'; import FlairStore from '../../../stores/FlairStore'; import {getUserNameColorClass} from '../../../utils/FormattingUtils'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import MatrixEvent from "matrix-js-sdk/src/models/event"; + +interface IProps { + mxEvent: MatrixEvent; + onClick(): void; + enableFlair: boolean; +} + +interface IState { + userGroups; + relatedGroups; +} @replaceableComponent("views.messages.SenderProfile") -export default class SenderProfile extends React.Component { - static propTypes = { - mxEvent: PropTypes.object.isRequired, // event whose sender we're showing - onClick: PropTypes.func, - }; - +export default class SenderProfile extends React.Component { static contextType = MatrixClientContext; + unmounted: boolean; - state = { - userGroups: null, - relatedGroups: [], - }; + constructor(props: IProps) { + super(props) + + this.state = { + userGroups: null, + relatedGroups: [], + }; + } componentDidMount() { this.unmounted = false; @@ -89,28 +100,17 @@ export default class SenderProfile extends React.Component { render() { const {mxEvent} = this.props; const colorClass = getUserNameColorClass(mxEvent.getSender()); - - let disambiguate; - let displayName; - let mxid; - - const sender = mxEvent.sender; - if (sender) { - disambiguate = sender.disambiguate; - displayName = sender.rawDisplayName; - mxid = sender.userId; - } else { - disambiguate = false; - displayName = mxEvent.getSender(); - mxid = mxEvent.getSender(); - } const {msgtype} = mxEvent.getContent(); + const disambiguate = mxEvent.sender?.disambiguate; + const displayName = mxEvent.sender?.rawDisplayName || mxEvent.getSender() || ""; + const mxid = mxEvent.sender?.userId || mxEvent.getSender() || ""; + if (msgtype === 'm.emote') { return ; // emote message must include the name so don't duplicate it } - let flair =
      ; + let flair; if (this.props.enableFlair) { const displayedGroups = this._getDisplayedGroups( this.state.userGroups, this.state.relatedGroups, @@ -124,7 +124,7 @@ export default class SenderProfile extends React.Component { const displayNameElement = ( - { displayName || '' } + { displayName } ); @@ -132,7 +132,7 @@ export default class SenderProfile extends React.Component { if (disambiguate) { mxidElement = ( - { `[${mxid || ""}]` } + { `[${mxid}]` } ); } From faca498523460e59215bbe819521fcda01069729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 20 Apr 2021 11:12:16 +0200 Subject: [PATCH 008/198] Don't use gap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/messages/_SenderProfile.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/messages/_SenderProfile.scss b/res/css/views/messages/_SenderProfile.scss index e3a38bad6d..d7763cda08 100644 --- a/res/css/views/messages/_SenderProfile.scss +++ b/res/css/views/messages/_SenderProfile.scss @@ -17,7 +17,6 @@ limitations under the License. .mx_SenderProfile_name { display: flex; align-items: center; - gap: 5px; } .mx_SenderProfile_displayName { @@ -29,4 +28,5 @@ limitations under the License. font-family: monospace; font-size: 1rem; color: gray; + margin-left: 5px; } From 4e1409dc2c7258816e51efb204a09ef608055117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 20 Apr 2021 11:40:50 +0200 Subject: [PATCH 009/198] Add private Co-authored-by: Michael Telatynski <7t3chguy@googlemail.com> --- src/components/views/messages/SenderProfile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx index fa635eaedb..f0dd77f746 100644 --- a/src/components/views/messages/SenderProfile.tsx +++ b/src/components/views/messages/SenderProfile.tsx @@ -36,7 +36,7 @@ interface IState { @replaceableComponent("views.messages.SenderProfile") export default class SenderProfile extends React.Component { static contextType = MatrixClientContext; - unmounted: boolean; + private unmounted: boolean; constructor(props: IProps) { super(props) From ffcd79f4a3215c1153e50ec599a8cd18b2d1e5a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 21 Apr 2021 17:34:03 +0200 Subject: [PATCH 010/198] Remove brackets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/SenderProfile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx index f0dd77f746..b2bfe00168 100644 --- a/src/components/views/messages/SenderProfile.tsx +++ b/src/components/views/messages/SenderProfile.tsx @@ -132,7 +132,7 @@ export default class SenderProfile extends React.Component { if (disambiguate) { mxidElement = ( - { `[${mxid}]` } + { mxid } ); } From eee1294374c568ee3e9c956b5fb3acaf537918a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 21 Apr 2021 17:37:25 +0200 Subject: [PATCH 011/198] Make both have the same baseline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/messages/_SenderProfile.scss | 5 ----- src/components/views/messages/SenderProfile.tsx | 8 +++----- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/res/css/views/messages/_SenderProfile.scss b/res/css/views/messages/_SenderProfile.scss index d7763cda08..14e8f6583f 100644 --- a/res/css/views/messages/_SenderProfile.scss +++ b/res/css/views/messages/_SenderProfile.scss @@ -14,11 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_SenderProfile_name { - display: flex; - align-items: center; -} - .mx_SenderProfile_displayName { font-weight: 600; } diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx index b2bfe00168..70040e4ef7 100644 --- a/src/components/views/messages/SenderProfile.tsx +++ b/src/components/views/messages/SenderProfile.tsx @@ -140,11 +140,9 @@ export default class SenderProfile extends React.Component { return (
      -
      - { displayNameElement } - { mxidElement } - { flair } -
      + { displayNameElement } + { mxidElement } + { flair }
      ); From 9658a760c8bd96dd91498d064e7898c5a70fe19f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 21 Apr 2021 17:41:21 +0200 Subject: [PATCH 012/198] Use timestamp color MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/messages/_SenderProfile.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/messages/_SenderProfile.scss b/res/css/views/messages/_SenderProfile.scss index 14e8f6583f..378d9e6f1a 100644 --- a/res/css/views/messages/_SenderProfile.scss +++ b/res/css/views/messages/_SenderProfile.scss @@ -22,6 +22,6 @@ limitations under the License. font-weight: 600; font-family: monospace; font-size: 1rem; - color: gray; + color: $event-timestamp-color; margin-left: 5px; } From 55365e632b524015f3dca528e18d2c593861bf0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 22 Apr 2021 07:52:39 +0200 Subject: [PATCH 013/198] Use the correct selector in E2EE tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- test/end-to-end-tests/src/usecases/timeline.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/end-to-end-tests/src/usecases/timeline.js b/test/end-to-end-tests/src/usecases/timeline.js index 3889dce108..01dc618571 100644 --- a/test/end-to-end-tests/src/usecases/timeline.js +++ b/test/end-to-end-tests/src/usecases/timeline.js @@ -122,7 +122,7 @@ function getAllEventTiles(session) { } async function getMessageFromEventTile(eventTile) { - const senderElement = await eventTile.$(".mx_SenderProfile_name"); + const senderElement = await eventTile.$(".mx_SenderProfile_displayName"); const className = await (await eventTile.getProperty("className")).jsonValue(); const classNames = className.split(" "); const bodyElement = await eventTile.$(".mx_EventTile_body"); From fa534e4755df403ee23f82ecd420b94c8c184d79 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 27 Apr 2021 14:47:56 +0100 Subject: [PATCH 014/198] Add room intro warning when e2ee is not enabled --- res/css/views/rooms/_NewRoomIntro.scss | 20 ++++++++++++++++++++ src/components/views/rooms/NewRoomIntro.tsx | 20 ++++++++++++++++++++ src/i18n/strings/en_EN.json | 1 + 3 files changed, 41 insertions(+) diff --git a/res/css/views/rooms/_NewRoomIntro.scss b/res/css/views/rooms/_NewRoomIntro.scss index 9c2a428cb3..e9d80c48c9 100644 --- a/res/css/views/rooms/_NewRoomIntro.scss +++ b/res/css/views/rooms/_NewRoomIntro.scss @@ -69,4 +69,24 @@ limitations under the License. font-size: $font-15px; color: $secondary-fg-color; } + + .mx_NewRoomIntro_message:not(:first-child) { + padding-top: 1em; + margin-top: 1em; + border-top: 1px solid currentColor; + } + + .mx_NewRoomIntro_message[role=alert]::before { + --size: 25px; + content: "!"; + float: left; + border-radius: 50%; + width: var(--size); + height: var(--size); + line-height: var(--size); + text-align: center; + background: $button-danger-bg-color; + color: #fff; + margin-right: calc(var(--size) / 2); + } } diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index 3f6054304d..9b003ade89 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -31,6 +31,14 @@ import dis from "../../../dispatcher/dispatcher"; import SpaceStore from "../../../stores/SpaceStore"; import {showSpaceInvite} from "../../../utils/space"; +import { privateShouldBeEncrypted } from "../../../createRoom"; + +function hasExpectedEncryptionSettings(room): boolean { + const isEncrypted: boolean = room._client.isRoomEncrypted(room.roomId); + const isPublic: boolean = room.getJoinRule() === "public"; + return isPublic || !privateShouldBeEncrypted() || isEncrypted; +} + const NewRoomIntro = () => { const cli = useContext(MatrixClientContext); const {room, roomId} = useContext(RoomContext); @@ -168,6 +176,18 @@ const NewRoomIntro = () => { return
      { body } + + { !hasExpectedEncryptionSettings(room) &&

      + {_t("Messages in this room are not end-to-end encrypted. " + + "Messages sent in an unencrypted room can be seen by the server and third parties. " + + "Learn more about encryption.", {}, + { + a: sub => {sub}, + })} + +

      }
      ; }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c6f7a8d25e..7ea9227938 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1490,6 +1490,7 @@ "Invite to just this room": "Invite to just this room", "Add a photo, so people can easily spot your room.": "Add a photo, so people can easily spot your room.", "This is the start of .": "This is the start of .", + "Messages in this room are not end-to-end encrypted. Messages sent in an unencrypted room can be seen by the server and third parties. Learn more about encryption.": "Messages in this room are not end-to-end encrypted. Messages sent in an unencrypted room can be seen by the server and third parties. Learn more about encryption.", "No pinned messages.": "No pinned messages.", "Loading...": "Loading...", "Pinned Messages": "Pinned Messages", From b9b237fc9a531eb3d42d1203198bcb03848c86c9 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sat, 8 May 2021 19:48:34 -0400 Subject: [PATCH 015/198] Replace forwarding UI with dialog Replaces the old forwarding UI with a dialog based on designs from https://github.com/vector-im/element-web/issues/14690. Signed-off-by: Robin Townsend --- res/css/_components.scss | 1 + res/css/views/dialogs/_ForwardDialog.scss | 115 ++++++++++ .../views/context_menus/MessageContextMenu.js | 8 +- .../views/dialogs/ForwardDialog.tsx | 213 ++++++++++++++++++ src/i18n/strings/en_EN.json | 5 + 5 files changed, 338 insertions(+), 4 deletions(-) create mode 100644 res/css/views/dialogs/_ForwardDialog.scss create mode 100644 src/components/views/dialogs/ForwardDialog.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index ec9592f3a1..ebb025d880 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -74,6 +74,7 @@ @import "./views/dialogs/_DevtoolsDialog.scss"; @import "./views/dialogs/_EditCommunityPrototypeDialog.scss"; @import "./views/dialogs/_FeedbackDialog.scss"; +@import "./views/dialogs/_ForwardDialog.scss"; @import "./views/dialogs/_GroupAddressPicker.scss"; @import "./views/dialogs/_HostSignupDialog.scss"; @import "./views/dialogs/_IncomingSasDialog.scss"; diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss new file mode 100644 index 0000000000..a3aa08d04f --- /dev/null +++ b/res/css/views/dialogs/_ForwardDialog.scss @@ -0,0 +1,115 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_ForwardDialog { + width: 520px; + color: $primary-fg-color; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + min-height: 0; + height: 80vh; + + .mx_ForwardDialog_preview { + max-height: 30%; + flex-shrink: 0; + overflow: scroll; + + div { + pointer-events: none; + } + + .mx_EventTile_msgOption { + display: none; + } + } + + .mx_ForwardList { + display: contents; + + .mx_SearchBox { + // To match the space around the title + margin: 0 0 15px 0; + flex-grow: 0; + } + + .mx_ForwardList_content { + flex-grow: 1; + } + + .mx_ForwardList_noResults { + display: block; + margin-top: 24px; + } + + .mx_ForwardList_section { + &:not(:first-child) { + margin-top: 24px; + } + + > h3 { + margin: 0; + color: $secondary-fg-color; + font-size: $font-12px; + font-weight: $font-semi-bold; + line-height: $font-15px; + } + + .mx_ForwardList_entry { + display: flex; + margin-top: 12px; + + .mx_BaseAvatar { + margin-right: 12px; + } + + .mx_ForwardList_entry_name { + font-size: $font-15px; + line-height: 30px; + flex-grow: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin-right: 12px; + } + + .mx_AccessibleButton { + &.mx_ForwardList_sending, &.mx_ForwardList_sent { + &::before { + content: ''; + display: inline-block; + background-color: $button-primary-bg-color; + mask-position: center; + mask-repeat: no-repeat; + mask-size: 14px; + width: 14px; + height: 14px; + margin-right: 5px; + } + } + + &.mx_ForwardList_sending::before { + mask-image: url('$(res)/img/element-icons/circle-sending.svg'); + } + + &.mx_ForwardList_sent::before { + mask-image: url('$(res)/img/element-icons/circle-sent.svg'); + } + } + } + } + } +} diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 35efd12c9c..e069bba975 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -154,11 +154,11 @@ export default class MessageContextMenu extends React.Component { }; onForwardClick = () => { - if (this.props.onCloseDialog) this.props.onCloseDialog(); - dis.dispatch({ - action: 'forward_event', + const ForwardDialog = sdk.getComponent("dialogs.ForwardDialog"); + Modal.createTrackedDialog('Forward Message', '', ForwardDialog, { + cli: MatrixClientPeg.get(), event: this.props.mxEvent, - }); + }, 'mx_Dialog_forwardmessage'); this.closeMenu(); }; diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx new file mode 100644 index 0000000000..ce38a783b3 --- /dev/null +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -0,0 +1,213 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, {useMemo, useState, useEffect} from "react"; +import classnames from "classnames"; +import {MatrixEvent} from "matrix-js-sdk/src/models/event"; +import {Room} from "matrix-js-sdk/src/models/room"; +import {MatrixClient} from "matrix-js-sdk/src/client"; + +import {_t} from "../../../languageHandler"; +import SettingsStore from "../../../settings/SettingsStore"; +import {UIFeature} from "../../../settings/UIFeature"; +import {Layout} from "../../../settings/Layout"; +import {IDialogProps} from "./IDialogProps"; +import BaseDialog from "./BaseDialog"; +import {avatarUrlForUser} from "../../../Avatar"; +import EventTile from "../rooms/EventTile"; +import SearchBox from "../../structures/SearchBox"; +import RoomAvatar from "../avatars/RoomAvatar"; +import AccessibleButton from "../elements/AccessibleButton"; +import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; +import DMRoomMap from "../../../utils/DMRoomMap"; +import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm"; + +const AVATAR_SIZE = 30; + +interface IProps extends IDialogProps { + cli: MatrixClient; + event: MatrixEvent; +} + +interface IEntryProps { + room: Room; + // Callback to forward the message to this room + send(): Promise; +} + +enum SendState { + CanSend, + Sending, + Sent, + Failed, +} + +const Entry: React.FC = ({ room, send }) => { + const [sendState, setSendState] = useState(SendState.CanSend); + + let label; + let className; + if (sendState === SendState.CanSend) { + label = _t("Send"); + className = "mx_ForwardList_canSend"; + } else if (sendState === SendState.Sending) { + label = _t("Sending…"); + className = "mx_ForwardList_sending"; + } else if (sendState === SendState.Sent) { + label = _t("Sent"); + className = "mx_ForwardList_sent"; + } else { + label = _t("Failed to send"); + className = "mx_ForwardList_sendFailed"; + } + + return
      + + { room.name } + { + setSendState(SendState.Sending); + try { + await send(); + setSendState(SendState.Sent); + } catch (e) { + setSendState(SendState.Failed); + } + }} + disabled={sendState !== SendState.CanSend} + > + { label } + +
      ; +}; + +const ForwardDialog: React.FC = ({ cli, event, onFinished }) => { + const userId = cli.getUserId(); + const [profileInfo, setProfileInfo] = useState({}); + useEffect(() => { + cli.getProfileInfo(userId).then(info => setProfileInfo(info)); + }, [cli, userId]); + + // For the message preview we fake the sender as ourselves + const mockEvent = new MatrixEvent({ + type: "m.room.message", + sender: userId, + content: event.getContent(), + unsigned: { + age: 97, + }, + event_id: "$9999999999999999999999999999999999999999999", + room_id: "!999999999999999999:example.org", + }); + mockEvent.sender = { + name: profileInfo.displayname || userId, + userId: userId, + getAvatarUrl: (..._) => { + return avatarUrlForUser( + { avatarUrl: profileInfo.avatar_url }, + AVATAR_SIZE, AVATAR_SIZE, "crop", + ); + }, + getMxcAvatarUrl: () => profileInfo.avatar_url, + }; + + const visibleRooms = useMemo(() => sortRooms( + cli.getVisibleRooms().filter( + room => room.getMyMembership() === "join" && + !(SettingsStore.getValue("feature_spaces") && room.isSpaceRoom()), + ), + ), [cli]); + + const [query, setQuery] = useState(""); + const lcQuery = query.toLowerCase(); + + const [rooms, dms] = visibleRooms.reduce((arr, room) => { + if (room.name.toLowerCase().includes(lcQuery)) { + if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { + arr[1].push(room); + } else { + arr[0].push(room); + } + } + return arr; + }, [[], []]); + + const previewLayout = SettingsStore.getValue("layout"); + + return +
      + +
      +
      +

      { _t("Forward to") }

      + + + { rooms.length > 0 ? ( +
      +

      { _t("Rooms") }

      + { rooms.map(room => { + return cli.sendEvent(room.roomId, event.getType(), event.getContent())} + />; + }) } +
      + ) : undefined } + + { dms.length > 0 ? ( +
      +

      { _t("Direct Messages") }

      + { dms.map(room => { + return cli.sendEvent(room.roomId, event.getType(), event.getContent())} + />; + }) } +
      + ) : null } + + { rooms.length + dms.length < 1 ? + { _t("No results") } + : undefined } +
      +
      +
      ; +}; + +export default ForwardDialog; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index dcad970300..ed49fb6b44 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2187,6 +2187,11 @@ "Report a bug": "Report a bug", "Please view existing bugs on Github first. No match? Start a new one.": "Please view existing bugs on Github first. No match? Start a new one.", "Send feedback": "Send feedback", + "Sending…": "Sending…", + "Sent": "Sent", + "Forward message": "Forward message", + "Forward to": "Forward to", + "Filter your rooms and DMs": "Filter your rooms and DMs", "Confirm abort of host creation": "Confirm abort of host creation", "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Are you sure you wish to abort creation of the host? The process cannot be continued.", "Abort": "Abort", From 74925b2c6d1f7de459bb6f35bb8cf472f476ccc3 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sat, 8 May 2021 19:51:51 -0400 Subject: [PATCH 016/198] Test ForwardDialog Signed-off-by: Robin Townsend --- .../views/dialogs/ForwardDialog-test.js | 124 ++++++++++++++++++ test/test-utils.js | 3 +- 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 test/components/views/dialogs/ForwardDialog-test.js diff --git a/test/components/views/dialogs/ForwardDialog-test.js b/test/components/views/dialogs/ForwardDialog-test.js new file mode 100644 index 0000000000..cabcbb0325 --- /dev/null +++ b/test/components/views/dialogs/ForwardDialog-test.js @@ -0,0 +1,124 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import "../../../skinned-sdk"; + +import React from "react"; +import {configure, mount} from "enzyme"; +import Adapter from "enzyme-adapter-react-16"; +import {act} from "react-dom/test-utils"; + +import * as TestUtils from "../../../test-utils"; +import {MatrixClientPeg} from "../../../../src/MatrixClientPeg"; +import DMRoomMap from "../../../../src/utils/DMRoomMap"; +import ForwardDialog from "../../../../src/components/views/dialogs/ForwardDialog"; + +configure({ adapter: new Adapter() }); + +describe("ForwardDialog", () => { + let client; + let wrapper; + + const message = TestUtils.mkMessage({ + room: "!111111111111111111:example.org", + user: "@alice:example.org", + msg: "Hello world!", + event: true, + }); + + beforeEach(async () => { + TestUtils.stubClient(); + DMRoomMap.makeShared(); + client = MatrixClientPeg.get(); + client.getVisibleRooms = jest.fn().mockReturnValue( + ["a", "A", "b"].map(name => TestUtils.mkStubRoom(name, name)), + ); + client.getUserId = jest.fn().mockReturnValue("@bob:example.org"); + + await act(async () => { + wrapper = mount( + , + ); + // Wait one tick for our profile data to load so the state update happens within act + await new Promise(resolve => setImmediate(resolve)); + }); + }); + + it("shows a preview with us as the sender", () => { + const previewBody = wrapper.find(".mx_EventTile_body"); + expect(previewBody.text()).toBe("Hello world!"); + + // We would just test SenderProfile for the user ID, but it's stubbed + const previewAvatar = wrapper.find(".mx_EventTile_avatar .mx_BaseAvatar_image"); + expect(previewAvatar.prop("title")).toBe("@bob:example.org"); + }); + + it("filters the rooms", () => { + const roomsBefore = wrapper.find(".mx_ForwardList_entry"); + expect(roomsBefore).toHaveLength(3); + + const searchInput = wrapper.find(".mx_SearchBox input"); + searchInput.instance().value = "a"; + searchInput.simulate("change"); + + const roomsAfter = wrapper.find(".mx_ForwardList_entry"); + expect(roomsAfter).toHaveLength(2); + }); + + it("tracks message sending progress across multiple rooms", async () => { + // Make sendEvent require manual resolution so we can see the sending state + let finishSend; + let cancelSend; + client.sendEvent = jest.fn(() => new Promise((resolve, reject) => { + finishSend = resolve; + cancelSend = reject; + })); + + const firstRoom = wrapper.find(".mx_ForwardList_entry").first(); + expect(firstRoom.find(".mx_AccessibleButton").text()).toBe("Send"); + + act(() => { + firstRoom.find(".mx_AccessibleButton").simulate("click"); + }); + expect(firstRoom.find(".mx_AccessibleButton").text()).toBe("Sending…"); + + await act(async () => { + cancelSend(); + // Wait one tick for the button to realize the send failed + await new Promise(resolve => setImmediate(resolve)); + }); + expect(firstRoom.find(".mx_AccessibleButton").text()).toBe("Failed to send"); + + const secondRoom = wrapper.find(".mx_ForwardList_entry").at(1); + expect(secondRoom.find(".mx_AccessibleButton").text()).toBe("Send"); + + act(() => { + secondRoom.find(".mx_AccessibleButton").simulate("click"); + }); + expect(secondRoom.find(".mx_AccessibleButton").text()).toBe("Sending…"); + + await act(async () => { + finishSend(); + // Wait one tick for the button to realize the send succeeded + await new Promise(resolve => setImmediate(resolve)); + }); + expect(secondRoom.find(".mx_AccessibleButton").text()).toBe("Sent"); + }); +}); diff --git a/test/test-utils.js b/test/test-utils.js index 6dc02463a5..985e9f804b 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -218,7 +218,7 @@ export function mkMessage(opts) { return mkEvent(opts); } -export function mkStubRoom(roomId = null) { +export function mkStubRoom(roomId = null, name) { const stubTimeline = { getEvents: () => [] }; return { roomId, @@ -254,6 +254,7 @@ export function mkStubRoom(roomId = null) { on: jest.fn(), removeListener: jest.fn(), getDMInviter: jest.fn(), + name, getAvatarUrl: () => 'mxc://avatar.url/room.png', getMxcAvatarUrl: () => 'mxc://avatar.url/room.png', isSpaceRoom: jest.fn(() => false), From 7fa81766db34449534d265cf7c2e60069049dd5b Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sat, 8 May 2021 20:07:51 -0400 Subject: [PATCH 017/198] Remove old forwarding code This has been replaced by ForwardDialog. Signed-off-by: Robin Townsend --- src/components/structures/RoomView.tsx | 30 +---------- src/components/views/rooms/ForwardMessage.js | 53 ------------------- src/components/views/rooms/RoomHeader.js | 9 ---- .../views/rooms/SimpleRoomHeader.js | 20 ------- src/i18n/strings/en_EN.json | 1 - src/stores/RoomViewStore.tsx | 21 -------- 6 files changed, 2 insertions(+), 132 deletions(-) delete mode 100644 src/components/views/rooms/ForwardMessage.js diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 58a87e6641..d16247e4ec 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -60,7 +60,6 @@ import ScrollPanel from "./ScrollPanel"; import TimelinePanel from "./TimelinePanel"; import ErrorBoundary from "../views/elements/ErrorBoundary"; import RoomPreviewBar from "../views/rooms/RoomPreviewBar"; -import ForwardMessage from "../views/rooms/ForwardMessage"; import SearchBar from "../views/rooms/SearchBar"; import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar"; import PinnedEventsPanel from "../views/rooms/PinnedEventsPanel"; @@ -136,7 +135,6 @@ export interface IState { // Whether to highlight the event scrolled to isInitialEventHighlighted?: boolean; replyToEvent?: MatrixEvent; - forwardingEvent?: MatrixEvent; numUnreadMessages: number; draggingFile: boolean; searching: boolean; @@ -324,7 +322,6 @@ export default class RoomView extends React.Component { initialEventId: RoomViewStore.getInitialEventId(), isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(), replyToEvent: RoomViewStore.getQuotingEvent(), - forwardingEvent: RoomViewStore.getForwardingEvent(), // we should only peek once we have a ready client shouldPeek: this.state.matrixClientIsReady && RoomViewStore.shouldPeek(), showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", roomId), @@ -1394,18 +1391,6 @@ export default class RoomView extends React.Component { dis.dispatch({ action: "open_room_settings" }); }; - private onCancelClick = () => { - console.log("updateTint from onCancelClick"); - this.updateTint(); - if (this.state.forwardingEvent) { - dis.dispatch({ - action: 'forward_event', - event: null, - }); - } - dis.fire(Action.FocusComposer); - }; - private onAppsClick = () => { dis.dispatch({ action: "appsDrawer", @@ -1856,11 +1841,7 @@ export default class RoomView extends React.Component { let aux = null; let previewBar; - let hideCancel = false; - if (this.state.forwardingEvent) { - aux = ; - } else if (this.state.searching) { - hideCancel = true; // has own cancel + if (this.state.searching) { aux = { />; } else if (showRoomUpgradeBar) { aux = ; - hideCancel = true; } else if (this.state.showingPinned) { - hideCancel = true; // has own cancel aux = ; } else if (myMembership !== "join") { // We do have a room object for this room, but we're not currently in it. @@ -1881,7 +1860,6 @@ export default class RoomView extends React.Component { inviterName = this.props.oobData.inviterName; } const invitedEmail = this.props.threepidInvite?.toEmail; - hideCancel = true; previewBar = ( { hideMessagePanel = true; } - const shouldHighlight = this.state.isInitialEventHighlighted; let highlightedEventId = null; - if (this.state.forwardingEvent) { - highlightedEventId = this.state.forwardingEvent.getId(); - } else if (shouldHighlight) { + if (this.state.isInitialEventHighlighted) { highlightedEventId = this.state.initialEventId; } @@ -2091,7 +2066,6 @@ export default class RoomView extends React.Component { onSearchClick={this.onSearchClick} onSettingsClick={this.onSettingsClick} onPinnedClick={this.onPinnedClick} - onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null} onForgetClick={(myMembership === "leave") ? this.onForgetClick : null} onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null} e2eStatus={this.state.e2eStatus} diff --git a/src/components/views/rooms/ForwardMessage.js b/src/components/views/rooms/ForwardMessage.js deleted file mode 100644 index dd894c0dcf..0000000000 --- a/src/components/views/rooms/ForwardMessage.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright 2017 Vector Creations Ltd - Copyright 2017 Michael Telatynski - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { _t } from '../../../languageHandler'; -import {Key} from '../../../Keyboard'; -import {replaceableComponent} from "../../../utils/replaceableComponent"; - -@replaceableComponent("views.rooms.ForwardMessage") -export default class ForwardMessage extends React.Component { - static propTypes = { - onCancelClick: PropTypes.func.isRequired, - }; - - componentDidMount() { - document.addEventListener('keydown', this._onKeyDown); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this._onKeyDown); - } - - _onKeyDown = ev => { - switch (ev.key) { - case Key.ESCAPE: - this.props.onCancelClick(); - break; - } - }; - - render() { - return ( -
      -

      { _t('Please select the destination room for this message') }

      -
      - ); - } -} diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index f856f7f6ef..cd4f89f964 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -22,7 +22,6 @@ import { _t } from '../../../languageHandler'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import RateLimitedFunc from '../../../ratelimitedfunc'; -import {CancelButton} from './SimpleRoomHeader'; import SettingsStore from "../../../settings/SettingsStore"; import RoomHeaderButtons from '../right_panel/RoomHeaderButtons'; import E2EIcon from './E2EIcon'; @@ -44,7 +43,6 @@ export default class RoomHeader extends React.Component { onPinnedClick: PropTypes.func, onSearchClick: PropTypes.func, onLeaveClick: PropTypes.func, - onCancelClick: PropTypes.func, e2eStatus: PropTypes.string, onAppsClick: PropTypes.func, appsShown: PropTypes.bool, @@ -54,7 +52,6 @@ export default class RoomHeader extends React.Component { static defaultProps = { editing: false, inRoom: false, - onCancelClick: null, }; componentDidMount() { @@ -120,13 +117,8 @@ export default class RoomHeader extends React.Component { render() { let searchStatus = null; - let cancelButton = null; let pinnedEventsButton = null; - if (this.props.onCancelClick) { - cancelButton = ; - } - // don't display the search count until the search completes and // gives us a valid (possibly zero) searchCount. if (this.props.searchInfo && @@ -265,7 +257,6 @@ export default class RoomHeader extends React.Component {
      { e2eIcon }
      { name } { topicElement } - { cancelButton } { rightRow }
      diff --git a/src/components/views/rooms/SimpleRoomHeader.js b/src/components/views/rooms/SimpleRoomHeader.js index b2a66f6670..02c1e1e8a1 100644 --- a/src/components/views/rooms/SimpleRoomHeader.js +++ b/src/components/views/rooms/SimpleRoomHeader.js @@ -16,23 +16,9 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import AccessibleButton from '../elements/AccessibleButton'; import * as sdk from '../../../index'; -import { _t } from '../../../languageHandler'; import {replaceableComponent} from "../../../utils/replaceableComponent"; -// cancel button which is shared between room header and simple room header -export function CancelButton(props) { - const {onClick} = props; - - return ( - - {_t("Cancel")} - - ); -} - /* * A stripped-down room header used for things like the user settings * and room directory. @@ -41,18 +27,13 @@ export function CancelButton(props) { export default class SimpleRoomHeader extends React.Component { static propTypes = { title: PropTypes.string, - onCancelClick: PropTypes.func, // `src` to a TintableSvg. Optional. icon: PropTypes.string, }; render() { - let cancelButton; let icon; - if (this.props.onCancelClick) { - cancelButton = ; - } if (this.props.icon) { const TintableSvg = sdk.getComponent('elements.TintableSvg'); icon = { icon } { this.props.title } - { cancelButton }
  • diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ed49fb6b44..6f2a4e8aee 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1458,7 +1458,6 @@ "Encrypting your message...": "Encrypting your message...", "Your message was sent": "Your message was sent", "Failed to send": "Failed to send", - "Please select the destination room for this message": "Please select the destination room for this message", "Scroll to most recent messages": "Scroll to most recent messages", "Close preview": "Close preview", "and %(count)s others...|other": "and %(count)s others...", diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index fe2e0a66b2..37162c8ae7 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -53,8 +53,6 @@ const INITIAL_STATE = { // Any error that has occurred during loading roomLoadError: null, - forwardingEvent: null, - quotingEvent: null, replyingToEvent: null, @@ -149,11 +147,6 @@ class RoomViewStore extends Store { case 'on_logged_out': this.reset(); break; - case 'forward_event': - this.setState({ - forwardingEvent: payload.event, - }); - break; case 'reply_to_event': // If currently viewed room does not match the room in which we wish to reply then change rooms // this can happen when performing a search across all rooms @@ -186,7 +179,6 @@ class RoomViewStore extends Store { roomAlias: payload.room_alias, initialEventId: payload.event_id, isInitialEventHighlighted: payload.highlighted, - forwardingEvent: null, roomLoading: false, roomLoadError: null, // should peek by default @@ -206,14 +198,6 @@ class RoomViewStore extends Store { newState.replyingToEvent = payload.replyingToEvent; } - if (this.state.forwardingEvent) { - dis.dispatch({ - action: 'send_event', - room_id: newState.roomId, - event: this.state.forwardingEvent, - }); - } - this.setState(newState); if (payload.auto_join) { @@ -419,11 +403,6 @@ class RoomViewStore extends Store { return this.state.joinError; } - // The mxEvent if one is about to be forwarded - public getForwardingEvent() { - return this.state.forwardingEvent; - } - // The mxEvent if one is currently being replied to/quoted public getQuotingEvent() { return this.state.replyingToEvent; From 219c983d191d9172e3c68aa11b1765f91ac5c6cd Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sun, 9 May 2021 10:58:44 -0400 Subject: [PATCH 018/198] Use import instead of sdk.getComponent Signed-off-by: Robin Townsend --- src/components/views/context_menus/MessageContextMenu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index e069bba975..fc2c366b07 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -31,6 +31,7 @@ import { isContentActionable } from '../../../utils/EventUtils'; import {MenuItem} from "../../structures/ContextMenu"; import {EventType} from "matrix-js-sdk/src/@types/event"; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import ForwardDialog from "../dialogs/ForwardDialog"; export function canCancel(eventStatus) { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; @@ -154,7 +155,6 @@ export default class MessageContextMenu extends React.Component { }; onForwardClick = () => { - const ForwardDialog = sdk.getComponent("dialogs.ForwardDialog"); Modal.createTrackedDialog('Forward Message', '', ForwardDialog, { cli: MatrixClientPeg.get(), event: this.props.mxEvent, From c96888c9cba48dfb1d318b2cfb89cd699c6a8529 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 10 May 2021 00:38:01 -0400 Subject: [PATCH 019/198] Make ForwardDialog more readable Signed-off-by: Robin Townsend --- .../views/dialogs/ForwardDialog.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index ce38a783b3..b1f558dfef 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -39,6 +39,7 @@ const AVATAR_SIZE = 30; interface IProps extends IDialogProps { cli: MatrixClient; + // The event to forward event: MatrixEvent; } @@ -116,7 +117,7 @@ const ForwardDialog: React.FC = ({ cli, event, onFinished }) => { }); mockEvent.sender = { name: profileInfo.displayname || userId, - userId: userId, + userId, getAvatarUrl: (..._) => { return avatarUrlForUser( { avatarUrl: profileInfo.avatar_url }, @@ -179,28 +180,28 @@ const ForwardDialog: React.FC = ({ cli, event, onFinished }) => { { rooms.length > 0 ? (

    { _t("Rooms") }

    - { rooms.map(room => { - return + cli.sendEvent(room.roomId, event.getType(), event.getContent())} - />; - }) } + />, + ) }
    ) : undefined } { dms.length > 0 ? (

    { _t("Direct Messages") }

    - { dms.map(room => { - return + cli.sendEvent(room.roomId, event.getType(), event.getContent())} - />; - }) } + />, + ) }
    - ) : null } + ) : undefined } { rooms.length + dms.length < 1 ? { _t("No results") } From 100efb1a90c92ac6a8771787cd2564f218507550 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 10 May 2021 00:40:54 -0400 Subject: [PATCH 020/198] Fix ForwardDialog crashing when rendering reply Signed-off-by: Robin Townsend --- src/components/views/context_menus/MessageContextMenu.js | 1 + src/components/views/dialogs/ForwardDialog.tsx | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index fc2c366b07..002542bf88 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -158,6 +158,7 @@ export default class MessageContextMenu extends React.Component { Modal.createTrackedDialog('Forward Message', '', ForwardDialog, { cli: MatrixClientPeg.get(), event: this.props.mxEvent, + permalinkCreator: this.props.permalinkCreator, }, 'mx_Dialog_forwardmessage'); this.closeMenu(); }; diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index b1f558dfef..5cb5d68745 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -33,6 +33,7 @@ import RoomAvatar from "../avatars/RoomAvatar"; import AccessibleButton from "../elements/AccessibleButton"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import DMRoomMap from "../../../utils/DMRoomMap"; +import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm"; const AVATAR_SIZE = 30; @@ -41,6 +42,9 @@ interface IProps extends IDialogProps { cli: MatrixClient; // The event to forward event: MatrixEvent; + // We need a permalink creator for the source room to pass through to EventTile + // in case the event is a reply (even though the user can't get at the link) + permalinkCreator: RoomPermalinkCreator; } interface IEntryProps { @@ -97,7 +101,7 @@ const Entry: React.FC = ({ room, send }) => { ; }; -const ForwardDialog: React.FC = ({ cli, event, onFinished }) => { +const ForwardDialog: React.FC = ({ cli, event, permalinkCreator, onFinished }) => { const userId = cli.getUserId(); const [profileInfo, setProfileInfo] = useState({}); useEffect(() => { @@ -113,7 +117,7 @@ const ForwardDialog: React.FC = ({ cli, event, onFinished }) => { age: 97, }, event_id: "$9999999999999999999999999999999999999999999", - room_id: "!999999999999999999:example.org", + room_id: event.getRoomId(), }); mockEvent.sender = { name: profileInfo.displayname || userId, @@ -165,6 +169,7 @@ const ForwardDialog: React.FC = ({ cli, event, onFinished }) => { mxEvent={mockEvent} layout={previewLayout} enableFlair={SettingsStore.getValue(UIFeature.Flair)} + permalinkCreator={permalinkCreator} />
    From eb07f1fb86de32944b8a0375beec89abdeef63d0 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 10 May 2021 00:54:00 -0400 Subject: [PATCH 021/198] Test that ForwardDialog can render replies Previously ForwardDialog was not giving its EventTile message preview the information it needed to render a ReplyThread. This was a bit tricky to fix since we were pulling a fake event out of thin air, so this ensures it doesn't regress. Signed-off-by: Robin Townsend --- .../views/dialogs/ForwardDialog-test.js | 64 ++++++++++++++----- test/test-utils.js | 1 + 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/test/components/views/dialogs/ForwardDialog-test.js b/test/components/views/dialogs/ForwardDialog-test.js index cabcbb0325..af325ab8f0 100644 --- a/test/components/views/dialogs/ForwardDialog-test.js +++ b/test/components/views/dialogs/ForwardDialog-test.js @@ -24,44 +24,51 @@ import {act} from "react-dom/test-utils"; import * as TestUtils from "../../../test-utils"; import {MatrixClientPeg} from "../../../../src/MatrixClientPeg"; import DMRoomMap from "../../../../src/utils/DMRoomMap"; +import {RoomPermalinkCreator} from "../../../../src/utils/permalinks/Permalinks"; import ForwardDialog from "../../../../src/components/views/dialogs/ForwardDialog"; configure({ adapter: new Adapter() }); describe("ForwardDialog", () => { - let client; - let wrapper; - - const message = TestUtils.mkMessage({ - room: "!111111111111111111:example.org", + const sourceRoom = "!111111111111111111:example.org"; + const defaultMessage = TestUtils.mkMessage({ + room: sourceRoom, user: "@alice:example.org", msg: "Hello world!", event: true, }); + const defaultRooms = ["a", "A", "b"].map(name => TestUtils.mkStubRoom(name, name)); - beforeEach(async () => { - TestUtils.stubClient(); - DMRoomMap.makeShared(); - client = MatrixClientPeg.get(); - client.getVisibleRooms = jest.fn().mockReturnValue( - ["a", "A", "b"].map(name => TestUtils.mkStubRoom(name, name)), - ); - client.getUserId = jest.fn().mockReturnValue("@bob:example.org"); + const mountForwardDialog = async (message = defaultMessage, rooms = defaultRooms) => { + const client = MatrixClientPeg.get(); + client.getVisibleRooms = jest.fn().mockReturnValue(rooms); + let wrapper; await act(async () => { wrapper = mount( , ); // Wait one tick for our profile data to load so the state update happens within act await new Promise(resolve => setImmediate(resolve)); }); + + return wrapper; + }; + + beforeEach(() => { + TestUtils.stubClient(); + DMRoomMap.makeShared(); + MatrixClientPeg.get().getUserId = jest.fn().mockReturnValue("@bob:example.org"); }); - it("shows a preview with us as the sender", () => { + it("shows a preview with us as the sender", async () => { + const wrapper = await mountForwardDialog(); + const previewBody = wrapper.find(".mx_EventTile_body"); expect(previewBody.text()).toBe("Hello world!"); @@ -70,7 +77,9 @@ describe("ForwardDialog", () => { expect(previewAvatar.prop("title")).toBe("@bob:example.org"); }); - it("filters the rooms", () => { + it("filters the rooms", async () => { + const wrapper = await mountForwardDialog(); + const roomsBefore = wrapper.find(".mx_ForwardList_entry"); expect(roomsBefore).toHaveLength(3); @@ -83,10 +92,12 @@ describe("ForwardDialog", () => { }); it("tracks message sending progress across multiple rooms", async () => { + const wrapper = await mountForwardDialog(); + // Make sendEvent require manual resolution so we can see the sending state let finishSend; let cancelSend; - client.sendEvent = jest.fn(() => new Promise((resolve, reject) => { + MatrixClientPeg.get().sendEvent = jest.fn(() => new Promise((resolve, reject) => { finishSend = resolve; cancelSend = reject; })); @@ -121,4 +132,25 @@ describe("ForwardDialog", () => { }); expect(secondRoom.find(".mx_AccessibleButton").text()).toBe("Sent"); }); + + it("can render replies", async () => { + const replyMessage = TestUtils.mkEvent({ + type: "m.room.message", + room: "!111111111111111111:example.org", + user: "@alice:example.org", + content: { + msgtype: "m.text", + body: "> <@bob:example.org> Hi Alice!\n\nHi Bob!", + "m.relates_to": { + "m.in_reply_to": { + event_id: "$2222222222222222222222222222222222222222222", + }, + }, + }, + event: true, + }); + + const wrapper = await mountForwardDialog(replyMessage); + expect(wrapper.find("ReplyThread")).toBeTruthy(); + }); }); diff --git a/test/test-utils.js b/test/test-utils.js index 985e9f804b..8c9c62844a 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -236,6 +236,7 @@ export function mkStubRoom(roomId = null, name) { getPendingEvents: () => [], getLiveTimeline: () => stubTimeline, getUnfilteredTimelineSet: () => null, + findEventById: () => null, getAccountData: () => null, hasMembershipState: () => null, getVersion: () => '1', From 35cf0e1c7efbd4f627e8fcc00cd8f8b96daf1b8e Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 10 May 2021 01:02:12 -0400 Subject: [PATCH 022/198] Find components by name rather than class in ForwardDialog test It makes things shorter and more readable! Signed-off-by: Robin Townsend --- .../views/dialogs/ForwardDialog-test.js | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/test/components/views/dialogs/ForwardDialog-test.js b/test/components/views/dialogs/ForwardDialog-test.js index af325ab8f0..f181157d09 100644 --- a/test/components/views/dialogs/ForwardDialog-test.js +++ b/test/components/views/dialogs/ForwardDialog-test.js @@ -80,15 +80,13 @@ describe("ForwardDialog", () => { it("filters the rooms", async () => { const wrapper = await mountForwardDialog(); - const roomsBefore = wrapper.find(".mx_ForwardList_entry"); - expect(roomsBefore).toHaveLength(3); + expect(wrapper.find("Entry")).toHaveLength(3); - const searchInput = wrapper.find(".mx_SearchBox input"); + const searchInput = wrapper.find("SearchBox input"); searchInput.instance().value = "a"; searchInput.simulate("change"); - const roomsAfter = wrapper.find(".mx_ForwardList_entry"); - expect(roomsAfter).toHaveLength(2); + expect(wrapper.find("Entry")).toHaveLength(2); }); it("tracks message sending progress across multiple rooms", async () => { @@ -102,35 +100,31 @@ describe("ForwardDialog", () => { cancelSend = reject; })); - const firstRoom = wrapper.find(".mx_ForwardList_entry").first(); - expect(firstRoom.find(".mx_AccessibleButton").text()).toBe("Send"); + const firstButton = wrapper.find("Entry AccessibleButton").first(); + expect(firstButton.text()).toBe("Send"); - act(() => { - firstRoom.find(".mx_AccessibleButton").simulate("click"); - }); - expect(firstRoom.find(".mx_AccessibleButton").text()).toBe("Sending…"); + act(() => { firstButton.simulate("click"); }); + expect(firstButton.text()).toBe("Sending…"); await act(async () => { cancelSend(); // Wait one tick for the button to realize the send failed await new Promise(resolve => setImmediate(resolve)); }); - expect(firstRoom.find(".mx_AccessibleButton").text()).toBe("Failed to send"); + expect(firstButton.text()).toBe("Failed to send"); - const secondRoom = wrapper.find(".mx_ForwardList_entry").at(1); - expect(secondRoom.find(".mx_AccessibleButton").text()).toBe("Send"); + const secondButton = wrapper.find("Entry AccessibleButton").at(1); + expect(secondButton.text()).toBe("Send"); - act(() => { - secondRoom.find(".mx_AccessibleButton").simulate("click"); - }); - expect(secondRoom.find(".mx_AccessibleButton").text()).toBe("Sending…"); + act(() => { secondButton.simulate("click"); }); + expect(secondButton.text()).toBe("Sending…"); await act(async () => { finishSend(); // Wait one tick for the button to realize the send succeeded await new Promise(resolve => setImmediate(resolve)); }); - expect(secondRoom.find(".mx_AccessibleButton").text()).toBe("Sent"); + expect(secondButton.text()).toBe("Sent"); }); it("can render replies", async () => { From 09ba74a8514fd907e758f1af24c795fa78ce8643 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 10 May 2021 01:04:25 -0400 Subject: [PATCH 023/198] Disable forward buttons for rooms without send permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …and add a tooltip to explain why they can't accept forwarded messages. It was chosen to disable the buttons rather than hide the entries from the list, since hiding them without explanation could cause confusion. Signed-off-by: Robin Townsend --- .../views/dialogs/ForwardDialog.tsx | 76 ++++++++++++------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 5cb5d68745..fd10a88d93 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -31,6 +31,7 @@ import EventTile from "../rooms/EventTile"; import SearchBox from "../../structures/SearchBox"; import RoomAvatar from "../avatars/RoomAvatar"; import AccessibleButton from "../elements/AccessibleButton"; +import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import DMRoomMap from "../../../utils/DMRoomMap"; import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; @@ -63,41 +64,58 @@ enum SendState { const Entry: React.FC = ({ room, send }) => { const [sendState, setSendState] = useState(SendState.CanSend); - let label; - let className; - if (sendState === SendState.CanSend) { - label = _t("Send"); - className = "mx_ForwardList_canSend"; - } else if (sendState === SendState.Sending) { - label = _t("Sending…"); - className = "mx_ForwardList_sending"; - } else if (sendState === SendState.Sent) { - label = _t("Sent"); - className = "mx_ForwardList_sent"; + let button; + if (room.maySendMessage()) { + let label; + let className; + if (sendState === SendState.CanSend) { + label = _t("Send"); + className = "mx_ForwardList_canSend"; + } else if (sendState === SendState.Sending) { + label = _t("Sending…"); + className = "mx_ForwardList_sending"; + } else if (sendState === SendState.Sent) { + label = _t("Sent"); + className = "mx_ForwardList_sent"; + } else { + label = _t("Failed to send"); + className = "mx_ForwardList_sendFailed"; + } + + button = + { + setSendState(SendState.Sending); + try { + await send(); + setSendState(SendState.Sent); + } catch (e) { + setSendState(SendState.Failed); + } + }} + disabled={sendState !== SendState.CanSend} + > + { label } + ; } else { - label = _t("Failed to send"); - className = "mx_ForwardList_sendFailed"; + button = + {}} + disabled={true} + title={_t("You do not have permission to post to this room")} + > + { _t("Send") } + ; } return
    { room.name } - { - setSendState(SendState.Sending); - try { - await send(); - setSendState(SendState.Sent); - } catch (e) { - setSendState(SendState.Failed); - } - }} - disabled={sendState !== SendState.CanSend} - > - { label } - + { button }
    ; }; From eb779cd3d8db279e9cf91134b1aa5bbf4326662c Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 10 May 2021 01:07:42 -0400 Subject: [PATCH 024/198] Test that forward buttons are disabled for rooms without permission Signed-off-by: Robin Townsend --- test/components/views/dialogs/ForwardDialog-test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/components/views/dialogs/ForwardDialog-test.js b/test/components/views/dialogs/ForwardDialog-test.js index f181157d09..706d47acdc 100644 --- a/test/components/views/dialogs/ForwardDialog-test.js +++ b/test/components/views/dialogs/ForwardDialog-test.js @@ -147,4 +147,17 @@ describe("ForwardDialog", () => { const wrapper = await mountForwardDialog(replyMessage); expect(wrapper.find("ReplyThread")).toBeTruthy(); }); + + it("disables buttons for rooms without send permissions", async () => { + const readOnlyRoom = TestUtils.mkStubRoom("a", "a"); + readOnlyRoom.maySendMessage = jest.fn().mockReturnValue(false); + const rooms = [readOnlyRoom, TestUtils.mkStubRoom("b", "b")]; + + const wrapper = await mountForwardDialog(undefined, rooms); + + const firstButton = wrapper.find("Entry AccessibleButton").first(); + expect(firstButton.prop("disabled")).toBe(true); + const secondButton = wrapper.find("Entry AccessibleButton").last(); + expect(secondButton.prop("disabled")).toBe(false); + }); }); From 5c10e1e5744e7c8b753304e4994c172f4ba493c3 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 10 May 2021 01:16:37 -0400 Subject: [PATCH 025/198] Fix lints Signed-off-by: Robin Townsend --- test/components/views/dialogs/ForwardDialog-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/components/views/dialogs/ForwardDialog-test.js b/test/components/views/dialogs/ForwardDialog-test.js index 706d47acdc..2f062a84cc 100644 --- a/test/components/views/dialogs/ForwardDialog-test.js +++ b/test/components/views/dialogs/ForwardDialog-test.js @@ -133,8 +133,8 @@ describe("ForwardDialog", () => { room: "!111111111111111111:example.org", user: "@alice:example.org", content: { - msgtype: "m.text", - body: "> <@bob:example.org> Hi Alice!\n\nHi Bob!", + "msgtype": "m.text", + "body": "> <@bob:example.org> Hi Alice!\n\nHi Bob!", "m.relates_to": { "m.in_reply_to": { event_id: "$2222222222222222222222222222222222222222222", From bfba2b0b6f09b7d470a6274d07053d0d854f7cd0 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 10 May 2021 11:16:51 -0400 Subject: [PATCH 026/198] Push ForwardDialog scrollbar into the gutter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …to give more space between it and the buttons. Signed-off-by: Robin Townsend --- res/css/views/dialogs/_ForwardDialog.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss index a3aa08d04f..a447b93cae 100644 --- a/res/css/views/dialogs/_ForwardDialog.scss +++ b/res/css/views/dialogs/_ForwardDialog.scss @@ -48,6 +48,8 @@ limitations under the License. .mx_ForwardList_content { flex-grow: 1; + // Push the scrollbar into the gutter + margin-right: -6px; } .mx_ForwardList_noResults { @@ -56,6 +58,8 @@ limitations under the License. } .mx_ForwardList_section { + margin-right: 6px; + &:not(:first-child) { margin-top: 24px; } From 503301aa89e3a8378d9e48c95b37ec19ab4d5e89 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 10 May 2021 13:00:06 -0400 Subject: [PATCH 027/198] Make rooms in ForwardDialog clickable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …so that you can jump to a room easily once you've forwarded a message there. Signed-off-by: Robin Townsend --- res/css/views/dialogs/_ForwardDialog.scss | 29 ++++++---- .../views/dialogs/ForwardDialog.tsx | 53 ++++++++++++------- .../views/dialogs/ForwardDialog-test.js | 10 ++-- 3 files changed, 58 insertions(+), 34 deletions(-) diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss index a447b93cae..30a4cfcacf 100644 --- a/res/css/views/dialogs/_ForwardDialog.scss +++ b/res/css/views/dialogs/_ForwardDialog.scss @@ -74,23 +74,30 @@ limitations under the License. .mx_ForwardList_entry { display: flex; + justify-content: space-between; margin-top: 12px; - .mx_BaseAvatar { + .mx_ForwardList_roomButton { + display: flex; margin-right: 12px; - } - - .mx_ForwardList_entry_name { - font-size: $font-15px; - line-height: 30px; flex-grow: 1; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - margin-right: 12px; + min-width: 0; + + .mx_BaseAvatar { + margin-right: 12px; + } + + .mx_ForwardList_entry_name { + font-size: $font-15px; + line-height: 30px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin-right: 12px; + } } - .mx_AccessibleButton { + .mx_ForwardList_sendButton { &.mx_ForwardList_sending, &.mx_ForwardList_sent { &::before { content: ''; diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index fd10a88d93..ed6973cd4a 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -21,6 +21,7 @@ import {Room} from "matrix-js-sdk/src/models/room"; import {MatrixClient} from "matrix-js-sdk/src/client"; import {_t} from "../../../languageHandler"; +import dis from "../../../dispatcher/dispatcher"; import SettingsStore from "../../../settings/SettingsStore"; import {UIFeature} from "../../../settings/UIFeature"; import {Layout} from "../../../settings/Layout"; @@ -50,8 +51,9 @@ interface IProps extends IDialogProps { interface IEntryProps { room: Room; - // Callback to forward the message to this room - send(): Promise; + event: MatrixEvent; + cli: MatrixClient; + onFinished(success: boolean): void; } enum SendState { @@ -61,9 +63,26 @@ enum SendState { Failed, } -const Entry: React.FC = ({ room, send }) => { +const Entry: React.FC = ({ room, event, cli, onFinished }) => { const [sendState, setSendState] = useState(SendState.CanSend); + const jumpToRoom = () => { + dis.dispatch({ + action: "view_room", + room_id: room.roomId, + }); + onFinished(true); + }; + const send = async () => { + setSendState(SendState.Sending); + try { + await cli.sendEvent(room.roomId, event.getType(), event.getContent()); + setSendState(SendState.Sent); + } catch (e) { + setSendState(SendState.Failed); + } + }; + let button; if (room.maySendMessage()) { let label; @@ -85,16 +104,8 @@ const Entry: React.FC = ({ room, send }) => { button = { - setSendState(SendState.Sending); - try { - await send(); - setSendState(SendState.Sent); - } catch (e) { - setSendState(SendState.Failed); - } - }} + className={`mx_ForwardList_sendButton ${className}`} + onClick={send} disabled={sendState !== SendState.CanSend} > { label } @@ -103,7 +114,7 @@ const Entry: React.FC = ({ room, send }) => { button = {}} disabled={true} title={_t("You do not have permission to post to this room")} @@ -113,8 +124,10 @@ const Entry: React.FC = ({ room, send }) => { } return
    - - { room.name } + + + { room.name } + { button }
    ; }; @@ -207,7 +220,9 @@ const ForwardDialog: React.FC = ({ cli, event, permalinkCreator, onFinis cli.sendEvent(room.roomId, event.getType(), event.getContent())} + event={event} + cli={cli} + onFinished={onFinished} />, ) }
    @@ -220,7 +235,9 @@ const ForwardDialog: React.FC = ({ cli, event, permalinkCreator, onFinis cli.sendEvent(room.roomId, event.getType(), event.getContent())} + event={event} + cli={cli} + onFinished={onFinished} />, ) } diff --git a/test/components/views/dialogs/ForwardDialog-test.js b/test/components/views/dialogs/ForwardDialog-test.js index 2f062a84cc..331ee9d131 100644 --- a/test/components/views/dialogs/ForwardDialog-test.js +++ b/test/components/views/dialogs/ForwardDialog-test.js @@ -100,7 +100,7 @@ describe("ForwardDialog", () => { cancelSend = reject; })); - const firstButton = wrapper.find("Entry AccessibleButton").first(); + const firstButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").first(); expect(firstButton.text()).toBe("Send"); act(() => { firstButton.simulate("click"); }); @@ -113,8 +113,8 @@ describe("ForwardDialog", () => { }); expect(firstButton.text()).toBe("Failed to send"); - const secondButton = wrapper.find("Entry AccessibleButton").at(1); - expect(secondButton.text()).toBe("Send"); + const secondButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").at(1); + expect(secondButton.render().text()).toBe("Send"); act(() => { secondButton.simulate("click"); }); expect(secondButton.text()).toBe("Sending…"); @@ -155,9 +155,9 @@ describe("ForwardDialog", () => { const wrapper = await mountForwardDialog(undefined, rooms); - const firstButton = wrapper.find("Entry AccessibleButton").first(); + const firstButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").first(); expect(firstButton.prop("disabled")).toBe(true); - const secondButton = wrapper.find("Entry AccessibleButton").last(); + const secondButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").last(); expect(secondButton.prop("disabled")).toBe(false); }); }); From 7efbd2d930cc93dffdabe10f84bc320f58cf23c5 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 10 May 2021 13:57:47 -0400 Subject: [PATCH 028/198] Hide unencrypted badge from ForwardDialog preview Signed-off-by: Robin Townsend --- res/css/views/dialogs/_ForwardDialog.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss index 30a4cfcacf..9bd0bd2879 100644 --- a/res/css/views/dialogs/_ForwardDialog.scss +++ b/res/css/views/dialogs/_ForwardDialog.scss @@ -35,6 +35,12 @@ limitations under the License. .mx_EventTile_msgOption { display: none; } + + // When forwarding messages from encrypted rooms, EventTile will complain + // that our preview is unencrypted, which doesn't actually matter + .mx_EventTile_e2eIcon_unencrypted { + display: none; + } } .mx_ForwardList { From d6a25d493abd23d24ba2225f500762cb36b12c17 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 14 May 2021 10:11:59 +0100 Subject: [PATCH 029/198] Create performance monitoring abstraction --- src/components/structures/MatrixChat.tsx | 46 +++------ src/performance/entry-names.ts | 57 +++++++++++ src/performance/index.ts | 120 +++++++++++++++++++++++ 3 files changed, 192 insertions(+), 31 deletions(-) create mode 100644 src/performance/entry-names.ts create mode 100644 src/performance/index.ts diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 288acc108a..64d205c89c 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -86,6 +86,8 @@ import {RoomUpdateCause} from "../../stores/room-list/models"; import defaultDispatcher from "../../dispatcher/dispatcher"; import SecurityCustomisations from "../../customisations/Security"; +import PerformanceMonitor, { PerformanceEntryNames } from "../../performance"; + /** constants for MatrixChat.state.view */ export enum Views { // a special initial state which is only used at startup, while we are @@ -484,42 +486,20 @@ export default class MatrixChat extends React.PureComponent { } startPageChangeTimer() { - // Tor doesn't support performance - if (!performance || !performance.mark) return null; - - // This shouldn't happen because UNSAFE_componentWillUpdate and componentDidUpdate - // are used. - if (this.pageChanging) { - console.warn('MatrixChat.startPageChangeTimer: timer already started'); - return; - } - this.pageChanging = true; - performance.mark('element_MatrixChat_page_change_start'); + PerformanceMonitor.start(PerformanceEntryNames.SWITCH_ROOM); } stopPageChangeTimer() { - // Tor doesn't support performance - if (!performance || !performance.mark) return null; + PerformanceMonitor.stop(PerformanceEntryNames.SWITCH_ROOM); - if (!this.pageChanging) { - console.warn('MatrixChat.stopPageChangeTimer: timer not started'); - return; - } - this.pageChanging = false; - performance.mark('element_MatrixChat_page_change_stop'); - performance.measure( - 'element_MatrixChat_page_change_delta', - 'element_MatrixChat_page_change_start', - 'element_MatrixChat_page_change_stop', - ); - performance.clearMarks('element_MatrixChat_page_change_start'); - performance.clearMarks('element_MatrixChat_page_change_stop'); - const measurement = performance.getEntriesByName('element_MatrixChat_page_change_delta').pop(); + const entries = PerformanceMonitor.getEntries({ + name: PerformanceEntryNames.SWITCH_ROOM, + }); + const measurement = entries.pop(); - // In practice, sometimes the entries list is empty, so we get no measurement - if (!measurement) return null; - - return measurement.duration; + return measurement + ? measurement.duration + : null; } shouldTrackPageChange(prevState: IState, state: IState) { @@ -1632,11 +1612,13 @@ export default class MatrixChat extends React.PureComponent { action: 'start_registration', params: params, }); + Performance.start(PerformanceEntryNames.REGISTER); } else if (screen === 'login') { dis.dispatch({ action: 'start_login', params: params, }); + Performance.start(PerformanceEntryNames.LOGIN); } else if (screen === 'forgot_password') { dis.dispatch({ action: 'start_password_recovery', @@ -1876,6 +1858,7 @@ export default class MatrixChat extends React.PureComponent { // returns a promise which resolves to the new MatrixClient onRegistered(credentials: IMatrixClientCreds) { + Performance.stop(PerformanceEntryNames.REGISTER); return Lifecycle.setLoggedIn(credentials); } @@ -1965,6 +1948,7 @@ export default class MatrixChat extends React.PureComponent { // Create and start the client await Lifecycle.setLoggedIn(credentials); await this.postLoginSetup(); + Performance.stop(PerformanceEntryNames.LOGIN); }; // complete security / e2e setup has finished diff --git a/src/performance/entry-names.ts b/src/performance/entry-names.ts new file mode 100644 index 0000000000..effd9506f6 --- /dev/null +++ b/src/performance/entry-names.ts @@ -0,0 +1,57 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export enum PerformanceEntryNames { + + /** + * Application wide + */ + + APP_STARTUP = "mx_AppStartup", + PAGE_CHANGE = "mx_PageChange", + + /** + * Events + */ + + RESEND_EVENT = "mx_ResendEvent", + SEND_E2EE_EVENT = "mx_SendE2EEEvent", + SEND_ATTACHMENT = "mx_SendAttachment", + + /** + * Rooms + */ + + SWITCH_ROOM = "mx_SwithRoom", + JUMP_TO_ROOM = "mx_JumpToRoom", + JOIN_ROOM = "mx_JoinRoom", + CREATE_DM = "mx_CreateDM", + PEEK_ROOM = "mx_PeekRoom", + + /** + * User + */ + + VERIFY_E2EE_USER = "mx_VerifyE2EEUser", + LOGIN = "mx_Login", + REGISTER = "mx_Register", + + /** + * VoIP + */ + + SETUP_VOIP_CALL = "mx_SetupVoIPCall", +} diff --git a/src/performance/index.ts b/src/performance/index.ts new file mode 100644 index 0000000000..4379ba77e3 --- /dev/null +++ b/src/performance/index.ts @@ -0,0 +1,120 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { string } from "prop-types"; +import { PerformanceEntryNames } from "./entry-names"; + +const START_PREFIX = "start:"; +const STOP_PREFIX = "stop:"; + +export { + PerformanceEntryNames, +} + +interface GetEntriesOptions { + name?: string, + type?: string, +} + +export default class PerformanceMonitor { + /** + * Starts a performance recording + * @param name Name of the recording + * @param id Specify an identifier appended to the measurement name + * @returns {void} + */ + static start(name: string, id?: string): void { + if (!supportsPerformanceApi()) { + return; + } + const key = buildKey(name, id); + + if (!performance.getEntriesByName(key).length) { + console.warn(`Recording already started for: ${name}`); + return; + } + + performance.mark(START_PREFIX + key); + } + + /** + * Stops a performance recording and stores delta duration + * with the start marker + * @param name Name of the recording + * @param id Specify an identifier appended to the measurement name + * @returns {void} + */ + static stop(name: string, id?: string): void { + if (!supportsPerformanceApi()) { + return; + } + const key = buildKey(name, id); + if (!performance.getEntriesByName(START_PREFIX + key).length) { + console.warn(`No recording started for: ${name}`); + return; + } + + performance.mark(STOP_PREFIX + key); + performance.measure( + key, + START_PREFIX + key, + STOP_PREFIX + key, + ); + + this.clear(name, id); + } + + static clear(name: string, id?: string): void { + if (!supportsPerformanceApi()) { + return; + } + const key = buildKey(name, id); + performance.clearMarks(START_PREFIX + key); + performance.clearMarks(STOP_PREFIX + key); + } + + static getEntries({ name, type }: GetEntriesOptions = {}): PerformanceEntry[] { + if (!supportsPerformanceApi()) { + return; + } + + if (!name && !type) { + return performance.getEntries(); + } else if (!name) { + return performance.getEntriesByType(type); + } else { + return performance.getEntriesByName(name, type); + } + } +} + +/** + * Tor browser does not support the Performance API + * @returns {boolean} true if the Performance API is supported + */ +function supportsPerformanceApi(): boolean { + return performance !== undefined && performance.mark !== undefined; +} + +/** + * Internal utility to ensure consistent name for the recording + * @param name Name of the recording + * @param id Specify an identifier appended to the measurement name + * @returns {string} a compound of the name and identifier if present + */ +function buildKey(name: string, id?: string): string { + return `${name}${id ? `:${id}` : ''}`; +} From 6804a26e74267925637296a94ea9e2c927f00aa0 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 14 May 2021 11:04:58 +0100 Subject: [PATCH 030/198] Add performance data collection mechanism --- src/components/structures/MatrixChat.tsx | 14 ++--- src/performance/index.ts | 65 +++++++++++++++++++----- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 64d205c89c..81381c56d3 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -486,14 +486,14 @@ export default class MatrixChat extends React.PureComponent { } startPageChangeTimer() { - PerformanceMonitor.start(PerformanceEntryNames.SWITCH_ROOM); + PerformanceMonitor.start(PerformanceEntryNames.PAGE_CHANGE); } stopPageChangeTimer() { - PerformanceMonitor.stop(PerformanceEntryNames.SWITCH_ROOM); + PerformanceMonitor.stop(PerformanceEntryNames.PAGE_CHANGE); const entries = PerformanceMonitor.getEntries({ - name: PerformanceEntryNames.SWITCH_ROOM, + name: PerformanceEntryNames.PAGE_CHANGE, }); const measurement = entries.pop(); @@ -1612,13 +1612,13 @@ export default class MatrixChat extends React.PureComponent { action: 'start_registration', params: params, }); - Performance.start(PerformanceEntryNames.REGISTER); + PerformanceMonitor.start(PerformanceEntryNames.REGISTER); } else if (screen === 'login') { dis.dispatch({ action: 'start_login', params: params, }); - Performance.start(PerformanceEntryNames.LOGIN); + PerformanceMonitor.start(PerformanceEntryNames.LOGIN); } else if (screen === 'forgot_password') { dis.dispatch({ action: 'start_password_recovery', @@ -1858,7 +1858,7 @@ export default class MatrixChat extends React.PureComponent { // returns a promise which resolves to the new MatrixClient onRegistered(credentials: IMatrixClientCreds) { - Performance.stop(PerformanceEntryNames.REGISTER); + PerformanceMonitor.stop(PerformanceEntryNames.REGISTER); return Lifecycle.setLoggedIn(credentials); } @@ -1948,7 +1948,7 @@ export default class MatrixChat extends React.PureComponent { // Create and start the client await Lifecycle.setLoggedIn(credentials); await this.postLoginSetup(); - Performance.stop(PerformanceEntryNames.LOGIN); + PerformanceMonitor.stop(PerformanceEntryNames.LOGIN); }; // complete security / e2e setup has finished diff --git a/src/performance/index.ts b/src/performance/index.ts index 4379ba77e3..3d903537a6 100644 --- a/src/performance/index.ts +++ b/src/performance/index.ts @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { string } from "prop-types"; import { PerformanceEntryNames } from "./entry-names"; const START_PREFIX = "start:"; @@ -29,6 +28,16 @@ interface GetEntriesOptions { type?: string, } +type PerformanceCallbackFunction = (entry: PerformanceEntry) => void; + +interface PerformanceDataListener { + entryTypes?: string[], + callback: PerformanceCallbackFunction +} + +let listeners: PerformanceDataListener[] = []; +const entries: PerformanceEntry[] = []; + export default class PerformanceMonitor { /** * Starts a performance recording @@ -42,7 +51,7 @@ export default class PerformanceMonitor { } const key = buildKey(name, id); - if (!performance.getEntriesByName(key).length) { + if (performance.getEntriesByName(key).length > 0) { console.warn(`Recording already started for: ${name}`); return; } @@ -57,12 +66,12 @@ export default class PerformanceMonitor { * @param id Specify an identifier appended to the measurement name * @returns {void} */ - static stop(name: string, id?: string): void { + static stop(name: string, id?: string): PerformanceEntry { if (!supportsPerformanceApi()) { return; } const key = buildKey(name, id); - if (!performance.getEntriesByName(START_PREFIX + key).length) { + if (performance.getEntriesByName(START_PREFIX + key).length === 0) { console.warn(`No recording started for: ${name}`); return; } @@ -75,6 +84,17 @@ export default class PerformanceMonitor { ); this.clear(name, id); + + const measurement = performance.getEntriesByName(key).pop(); + + // Keeping a reference to all PerformanceEntry created + // by this abstraction for historical events collection + // when adding a data callback + entries.push(measurement); + + listeners.forEach(listener => emitPerformanceData(listener, measurement)); + + return measurement; } static clear(name: string, id?: string): void { @@ -87,18 +107,37 @@ export default class PerformanceMonitor { } static getEntries({ name, type }: GetEntriesOptions = {}): PerformanceEntry[] { - if (!supportsPerformanceApi()) { - return; - } + return entries.filter(entry => { + const satisfiesName = !name || entry.name === name; + const satisfiedType = !type || entry.entryType === type; + return satisfiesName && satisfiedType; + }); + } - if (!name && !type) { - return performance.getEntries(); - } else if (!name) { - return performance.getEntriesByType(type); - } else { - return performance.getEntriesByName(name, type); + static addPerformanceDataCallback(listener: PerformanceDataListener, buffer = false) { + listeners.push(listener); + + if (buffer) { + entries.forEach(entry => emitPerformanceData(listener, entry)); } } + + static removePerformanceDataCallback(callback?: PerformanceCallbackFunction) { + if (!callback) { + listeners = []; + } else { + listeners.splice( + listeners.findIndex(listener => listener.callback === callback), + 1, + ); + } + } +} + +function emitPerformanceData(listener, entry): void { + if (!listener.entryTypes || listener.entryTypes.includes(entry.entryType)) { + listener.callback(entry) + } } /** From 89832eff9ef9dbe9cf3896d541797e294dd1e840 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 14 May 2021 12:23:23 +0100 Subject: [PATCH 031/198] Add data collection mechanism in end to end test suite --- src/@types/global.d.ts | 3 +++ src/components/structures/MatrixChat.tsx | 2 +- src/performance/index.ts | 25 +++++++++++++++--------- test/end-to-end-tests/.gitignore | 1 + test/end-to-end-tests/src/session.js | 2 +- test/end-to-end-tests/start.js | 22 +++++++++++++++++++-- 6 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index f04a2ff237..dec8559320 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -42,6 +42,7 @@ import {SpaceStoreClass} from "../stores/SpaceStore"; import TypingStore from "../stores/TypingStore"; import { EventIndexPeg } from "../indexing/EventIndexPeg"; import {VoiceRecordingStore} from "../stores/VoiceRecordingStore"; +import PerformanceMonitor, { PerformanceEntryNames } from "../performance"; declare global { interface Window { @@ -79,6 +80,8 @@ declare global { mxVoiceRecordingStore: VoiceRecordingStore; mxTypingStore: TypingStore; mxEventIndexPeg: EventIndexPeg; + mxPerformanceMonitor: PerformanceMonitor; + mxPerformanceEntryNames: PerformanceEntryNames; } interface Document { diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 81381c56d3..5a275b9a99 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1858,7 +1858,6 @@ export default class MatrixChat extends React.PureComponent { // returns a promise which resolves to the new MatrixClient onRegistered(credentials: IMatrixClientCreds) { - PerformanceMonitor.stop(PerformanceEntryNames.REGISTER); return Lifecycle.setLoggedIn(credentials); } @@ -1949,6 +1948,7 @@ export default class MatrixChat extends React.PureComponent { await Lifecycle.setLoggedIn(credentials); await this.postLoginSetup(); PerformanceMonitor.stop(PerformanceEntryNames.LOGIN); + PerformanceMonitor.stop(PerformanceEntryNames.REGISTER); }; // complete security / e2e setup has finished diff --git a/src/performance/index.ts b/src/performance/index.ts index 3d903537a6..bbe8a207fe 100644 --- a/src/performance/index.ts +++ b/src/performance/index.ts @@ -28,10 +28,10 @@ interface GetEntriesOptions { type?: string, } -type PerformanceCallbackFunction = (entry: PerformanceEntry) => void; +type PerformanceCallbackFunction = (entry: PerformanceEntry[]) => void; interface PerformanceDataListener { - entryTypes?: string[], + entryNames?: string[], callback: PerformanceCallbackFunction } @@ -92,7 +92,11 @@ export default class PerformanceMonitor { // when adding a data callback entries.push(measurement); - listeners.forEach(listener => emitPerformanceData(listener, measurement)); + listeners.forEach(listener => { + if (shouldEmit(listener, measurement)) { + listener.callback([measurement]) + } + }); return measurement; } @@ -116,9 +120,11 @@ export default class PerformanceMonitor { static addPerformanceDataCallback(listener: PerformanceDataListener, buffer = false) { listeners.push(listener); - if (buffer) { - entries.forEach(entry => emitPerformanceData(listener, entry)); + const toEmit = entries.filter(entry => shouldEmit(listener, entry)); + if (toEmit.length > 0) { + listener.callback(toEmit); + } } } @@ -134,10 +140,8 @@ export default class PerformanceMonitor { } } -function emitPerformanceData(listener, entry): void { - if (!listener.entryTypes || listener.entryTypes.includes(entry.entryType)) { - listener.callback(entry) - } +function shouldEmit(listener: PerformanceDataListener, entry: PerformanceEntry): boolean { + return !listener.entryNames || listener.entryNames.includes(entry.name); } /** @@ -157,3 +161,6 @@ function supportsPerformanceApi(): boolean { function buildKey(name: string, id?: string): string { return `${name}${id ? `:${id}` : ''}`; } + +window.mxPerformanceMonitor = PerformanceMonitor; +window.mxPerformanceEntryNames = PerformanceEntryNames; diff --git a/test/end-to-end-tests/.gitignore b/test/end-to-end-tests/.gitignore index 61f9012393..9180d32e90 100644 --- a/test/end-to-end-tests/.gitignore +++ b/test/end-to-end-tests/.gitignore @@ -1,3 +1,4 @@ node_modules *.png element/env +performance-entries.json diff --git a/test/end-to-end-tests/src/session.js b/test/end-to-end-tests/src/session.js index 4c611ef877..6c68929a0b 100644 --- a/test/end-to-end-tests/src/session.js +++ b/test/end-to-end-tests/src/session.js @@ -208,7 +208,7 @@ module.exports = class ElementSession { this.log.done(); } - close() { + async close() { return this.browser.close(); } diff --git a/test/end-to-end-tests/start.js b/test/end-to-end-tests/start.js index 234d60da9f..ac06dcd989 100644 --- a/test/end-to-end-tests/start.js +++ b/test/end-to-end-tests/start.js @@ -22,7 +22,7 @@ const fs = require("fs"); const program = require('commander'); program .option('--no-logs', "don't output logs, document html on error", false) - .option('--app-url [url]', "url to test", "http://localhost:5000") + .option('--app-url [url]', "url to test", "http://localhost:8080") .option('--windowed', "dont run tests headless", false) .option('--slow-mo', "type at a human speed", false) .option('--dev-tools', "open chrome devtools in browser window", false) @@ -79,8 +79,26 @@ async function runTests() { await new Promise((resolve) => setTimeout(resolve, 5 * 60 * 1000)); } - await Promise.all(sessions.map((session) => session.close())); + const performanceEntries = {}; + await Promise.all(sessions.map(async (session) => { + // Collecting all performance monitoring data before closing the session + const measurements = await session.page.evaluate(() => { + let measurements = []; + window.mxPerformanceMonitor.addPerformanceDataCallback({ + entryNames: [ + window.mxPerformanceEntryNames.REGISTER, + ], + callback: (events) => { + measurements = JSON.stringify(events); + }, + }, true); + return measurements; + }); + performanceEntries[session.username] = JSON.parse(measurements); + return session.close(); + })); + fs.writeFileSync(`performance-entries.json`, JSON.stringify(performanceEntries)); if (failure) { process.exit(-1); } else { From cbf645785772daa5f49ea05cb9ee1f07cedebafc Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 14 May 2021 12:49:32 +0100 Subject: [PATCH 032/198] Revert app url to use the default that CI relies on --- test/end-to-end-tests/start.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/end-to-end-tests/start.js b/test/end-to-end-tests/start.js index ac06dcd989..f29b485c84 100644 --- a/test/end-to-end-tests/start.js +++ b/test/end-to-end-tests/start.js @@ -22,7 +22,7 @@ const fs = require("fs"); const program = require('commander'); program .option('--no-logs', "don't output logs, document html on error", false) - .option('--app-url [url]', "url to test", "http://localhost:8080") + .option('--app-url [url]', "url to test", "http://localhost:5000") .option('--windowed', "dont run tests headless", false) .option('--slow-mo', "type at a human speed", false) .option('--dev-tools', "open chrome devtools in browser window", false) From e798b36f1db8ea73c81fcc1626e1af31014c0800 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sun, 16 May 2021 08:39:22 -0400 Subject: [PATCH 033/198] Decorate forward dialog room avatars Signed-off-by: Robin Townsend --- res/css/views/dialogs/_ForwardDialog.scss | 2 +- src/components/views/dialogs/ForwardDialog.tsx | 4 ++-- test/test-utils.js | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss index 9bd0bd2879..2a72ea4ffb 100644 --- a/res/css/views/dialogs/_ForwardDialog.scss +++ b/res/css/views/dialogs/_ForwardDialog.scss @@ -89,7 +89,7 @@ limitations under the License. flex-grow: 1; min-width: 0; - .mx_BaseAvatar { + .mx_DecoratedRoomAvatar { margin-right: 12px; } diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index ed6973cd4a..1ef4841851 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -30,7 +30,7 @@ import BaseDialog from "./BaseDialog"; import {avatarUrlForUser} from "../../../Avatar"; import EventTile from "../rooms/EventTile"; import SearchBox from "../../structures/SearchBox"; -import RoomAvatar from "../avatars/RoomAvatar"; +import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import AccessibleButton from "../elements/AccessibleButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; @@ -125,7 +125,7 @@ const Entry: React.FC = ({ room, event, cli, onFinished }) => { return
    - + { room.name } { button } diff --git a/test/test-utils.js b/test/test-utils.js index 8c9c62844a..a2ccf84728 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -253,6 +253,7 @@ export function mkStubRoom(roomId = null, name) { tags: {}, setBlacklistUnverifiedDevices: jest.fn(), on: jest.fn(), + off: jest.fn(), removeListener: jest.fn(), getDMInviter: jest.fn(), name, From 781c0dca68d293c67bfc2f125d1e08fbefaf5b06 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 17 May 2021 09:30:53 +0100 Subject: [PATCH 034/198] Refactor performance monitor to use instance pattern --- src/@types/global.d.ts | 2 +- src/components/structures/MatrixChat.tsx | 16 +-- src/performance/index.ts | 130 +++++++++++++---------- 3 files changed, 81 insertions(+), 67 deletions(-) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index dec8559320..fb3b92e45a 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -81,7 +81,7 @@ declare global { mxTypingStore: TypingStore; mxEventIndexPeg: EventIndexPeg; mxPerformanceMonitor: PerformanceMonitor; - mxPerformanceEntryNames: PerformanceEntryNames; + mxPerformanceEntryNames: any; } interface Document { diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 5a275b9a99..4c7fca2fec 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -486,13 +486,15 @@ export default class MatrixChat extends React.PureComponent { } startPageChangeTimer() { - PerformanceMonitor.start(PerformanceEntryNames.PAGE_CHANGE); + PerformanceMonitor.instance.start(PerformanceEntryNames.PAGE_CHANGE); } stopPageChangeTimer() { - PerformanceMonitor.stop(PerformanceEntryNames.PAGE_CHANGE); + const perfMonitor = PerformanceMonitor.instance; - const entries = PerformanceMonitor.getEntries({ + perfMonitor.stop(PerformanceEntryNames.PAGE_CHANGE); + + const entries = perfMonitor.getEntries({ name: PerformanceEntryNames.PAGE_CHANGE, }); const measurement = entries.pop(); @@ -1612,13 +1614,13 @@ export default class MatrixChat extends React.PureComponent { action: 'start_registration', params: params, }); - PerformanceMonitor.start(PerformanceEntryNames.REGISTER); + PerformanceMonitor.instance.start(PerformanceEntryNames.REGISTER); } else if (screen === 'login') { dis.dispatch({ action: 'start_login', params: params, }); - PerformanceMonitor.start(PerformanceEntryNames.LOGIN); + PerformanceMonitor.instance.start(PerformanceEntryNames.LOGIN); } else if (screen === 'forgot_password') { dis.dispatch({ action: 'start_password_recovery', @@ -1947,8 +1949,8 @@ export default class MatrixChat extends React.PureComponent { // Create and start the client await Lifecycle.setLoggedIn(credentials); await this.postLoginSetup(); - PerformanceMonitor.stop(PerformanceEntryNames.LOGIN); - PerformanceMonitor.stop(PerformanceEntryNames.REGISTER); + PerformanceMonitor.instance.stop(PerformanceEntryNames.LOGIN); + PerformanceMonitor.instance.stop(PerformanceEntryNames.REGISTER); }; // complete security / e2e setup has finished diff --git a/src/performance/index.ts b/src/performance/index.ts index bbe8a207fe..13ad0a55bb 100644 --- a/src/performance/index.ts +++ b/src/performance/index.ts @@ -16,13 +16,6 @@ limitations under the License. import { PerformanceEntryNames } from "./entry-names"; -const START_PREFIX = "start:"; -const STOP_PREFIX = "stop:"; - -export { - PerformanceEntryNames, -} - interface GetEntriesOptions { name?: string, type?: string, @@ -35,28 +28,40 @@ interface PerformanceDataListener { callback: PerformanceCallbackFunction } -let listeners: PerformanceDataListener[] = []; -const entries: PerformanceEntry[] = []; - export default class PerformanceMonitor { + static _instance: PerformanceMonitor; + + private START_PREFIX = "start:" + private STOP_PREFIX = "stop:" + + private listeners: PerformanceDataListener[] = [] + private entries: PerformanceEntry[] = [] + + public static get instance(): PerformanceMonitor { + if (!PerformanceMonitor._instance) { + PerformanceMonitor._instance = new PerformanceMonitor(); + } + return PerformanceMonitor._instance; + } + /** * Starts a performance recording * @param name Name of the recording * @param id Specify an identifier appended to the measurement name * @returns {void} */ - static start(name: string, id?: string): void { - if (!supportsPerformanceApi()) { + start(name: string, id?: string): void { + if (!this.supportsPerformanceApi()) { return; } - const key = buildKey(name, id); + const key = this.buildKey(name, id); if (performance.getEntriesByName(key).length > 0) { console.warn(`Recording already started for: ${name}`); return; } - performance.mark(START_PREFIX + key); + performance.mark(this.START_PREFIX + key); } /** @@ -66,21 +71,21 @@ export default class PerformanceMonitor { * @param id Specify an identifier appended to the measurement name * @returns {void} */ - static stop(name: string, id?: string): PerformanceEntry { - if (!supportsPerformanceApi()) { + stop(name: string, id?: string): PerformanceEntry { + if (!this.supportsPerformanceApi()) { return; } - const key = buildKey(name, id); - if (performance.getEntriesByName(START_PREFIX + key).length === 0) { + const key = this.buildKey(name, id); + if (performance.getEntriesByName(this.START_PREFIX + key).length === 0) { console.warn(`No recording started for: ${name}`); return; } - performance.mark(STOP_PREFIX + key); + performance.mark(this.STOP_PREFIX + key); performance.measure( key, - START_PREFIX + key, - STOP_PREFIX + key, + this.START_PREFIX + key, + this.STOP_PREFIX + key, ); this.clear(name, id); @@ -90,10 +95,10 @@ export default class PerformanceMonitor { // Keeping a reference to all PerformanceEntry created // by this abstraction for historical events collection // when adding a data callback - entries.push(measurement); + this.entries.push(measurement); - listeners.forEach(listener => { - if (shouldEmit(listener, measurement)) { + this.listeners.forEach(listener => { + if (this.shouldEmit(listener, measurement)) { listener.callback([measurement]) } }); @@ -101,66 +106,73 @@ export default class PerformanceMonitor { return measurement; } - static clear(name: string, id?: string): void { - if (!supportsPerformanceApi()) { + clear(name: string, id?: string): void { + if (!this.supportsPerformanceApi()) { return; } - const key = buildKey(name, id); - performance.clearMarks(START_PREFIX + key); - performance.clearMarks(STOP_PREFIX + key); + const key = this.buildKey(name, id); + performance.clearMarks(this.START_PREFIX + key); + performance.clearMarks(this.STOP_PREFIX + key); } - static getEntries({ name, type }: GetEntriesOptions = {}): PerformanceEntry[] { - return entries.filter(entry => { + getEntries({ name, type }: GetEntriesOptions = {}): PerformanceEntry[] { + return this.entries.filter(entry => { const satisfiesName = !name || entry.name === name; const satisfiedType = !type || entry.entryType === type; return satisfiesName && satisfiedType; }); } - static addPerformanceDataCallback(listener: PerformanceDataListener, buffer = false) { - listeners.push(listener); + addPerformanceDataCallback(listener: PerformanceDataListener, buffer = false) { + this.listeners.push(listener); if (buffer) { - const toEmit = entries.filter(entry => shouldEmit(listener, entry)); + const toEmit = this.entries.filter(entry => this.shouldEmit(listener, entry)); if (toEmit.length > 0) { listener.callback(toEmit); } } } - static removePerformanceDataCallback(callback?: PerformanceCallbackFunction) { + removePerformanceDataCallback(callback?: PerformanceCallbackFunction) { if (!callback) { - listeners = []; + this.listeners = []; } else { - listeners.splice( - listeners.findIndex(listener => listener.callback === callback), + this.listeners.splice( + this.listeners.findIndex(listener => listener.callback === callback), 1, ); } } + + /** + * Tor browser does not support the Performance API + * @returns {boolean} true if the Performance API is supported + */ + private supportsPerformanceApi(): boolean { + return performance !== undefined && performance.mark !== undefined; + } + + private shouldEmit(listener: PerformanceDataListener, entry: PerformanceEntry): boolean { + return !listener.entryNames || listener.entryNames.includes(entry.name); + } + + /** + * Internal utility to ensure consistent name for the recording + * @param name Name of the recording + * @param id Specify an identifier appended to the measurement name + * @returns {string} a compound of the name and identifier if present + */ + private buildKey(name: string, id?: string): string { + return `${name}${id ? `:${id}` : ''}`; + } } -function shouldEmit(listener: PerformanceDataListener, entry: PerformanceEntry): boolean { - return !listener.entryNames || listener.entryNames.includes(entry.name); + +// Convienience exports +export { + PerformanceEntryNames, } -/** - * Tor browser does not support the Performance API - * @returns {boolean} true if the Performance API is supported - */ -function supportsPerformanceApi(): boolean { - return performance !== undefined && performance.mark !== undefined; -} - -/** - * Internal utility to ensure consistent name for the recording - * @param name Name of the recording - * @param id Specify an identifier appended to the measurement name - * @returns {string} a compound of the name and identifier if present - */ -function buildKey(name: string, id?: string): string { - return `${name}${id ? `:${id}` : ''}`; -} - -window.mxPerformanceMonitor = PerformanceMonitor; +// Exposing those to the window object to bridge them from tests +window.mxPerformanceMonitor = PerformanceMonitor.instance; window.mxPerformanceEntryNames = PerformanceEntryNames; From f3bebdbc8733f07556f79609e8afdc58778738f4 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 17 May 2021 09:44:36 +0100 Subject: [PATCH 035/198] remove unused import --- src/@types/global.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index fb3b92e45a..63966d96fa 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -42,7 +42,7 @@ import {SpaceStoreClass} from "../stores/SpaceStore"; import TypingStore from "../stores/TypingStore"; import { EventIndexPeg } from "../indexing/EventIndexPeg"; import {VoiceRecordingStore} from "../stores/VoiceRecordingStore"; -import PerformanceMonitor, { PerformanceEntryNames } from "../performance"; +import PerformanceMonitor from "../performance"; declare global { interface Window { From 5b6c5aac16f21be4cb604b93f981120f1fe1b828 Mon Sep 17 00:00:00 2001 From: Germain Date: Mon, 17 May 2021 12:02:24 +0100 Subject: [PATCH 036/198] Fix comment typo Co-authored-by: J. Ryan Stinnett --- src/performance/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/performance/index.ts b/src/performance/index.ts index 13ad0a55bb..7eb6d26567 100644 --- a/src/performance/index.ts +++ b/src/performance/index.ts @@ -168,7 +168,7 @@ export default class PerformanceMonitor { } -// Convienience exports +// Convenience exports export { PerformanceEntryNames, } From 07d74693af2e3e2c11d4e455e35e9ee1267e3272 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 18 May 2021 16:00:39 +0100 Subject: [PATCH 037/198] Start decryption process if needed --- src/Notifier.ts | 2 ++ src/stores/widgets/StopGapWidget.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Notifier.ts b/src/Notifier.ts index 3e927cea0c..4f55046e72 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -331,6 +331,8 @@ export const Notifier = { if (!this.isSyncing) return; // don't alert for any messages initially if (ev.sender && ev.sender.userId === MatrixClientPeg.get().credentials.userId) return; + MatrixClientPeg.get().decryptEventIfNeeded(ev); + // If it's an encrypted event and the type is still 'm.room.encrypted', // it hasn't yet been decrypted, so wait until it is. if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) { diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts index 17371d6d45..b0a76a35af 100644 --- a/src/stores/widgets/StopGapWidget.ts +++ b/src/stores/widgets/StopGapWidget.ts @@ -395,6 +395,7 @@ export class StopGapWidget extends EventEmitter { } private onEvent = (ev: MatrixEvent) => { + MatrixClientPeg.get().decryptEventIfNeeded(ev); if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; if (ev.getRoomId() !== this.eventListenerRoomId) return; this.feedEvent(ev); From bcbfbd508d110ae163597bdbdc384cc2b5ed1264 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 19 May 2021 09:45:37 +0100 Subject: [PATCH 038/198] Fix event start check --- src/performance/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/performance/index.ts b/src/performance/index.ts index 7eb6d26567..bfb5b4a9c7 100644 --- a/src/performance/index.ts +++ b/src/performance/index.ts @@ -56,7 +56,7 @@ export default class PerformanceMonitor { } const key = this.buildKey(name, id); - if (performance.getEntriesByName(key).length > 0) { + if (performance.getEntriesByName(this.START_PREFIX + key).length > 0) { console.warn(`Recording already started for: ${name}`); return; } From 382a08bdb1bd510ac6aa0b7fce7e9ff8ef8e48cd Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 19 May 2021 11:38:10 +0100 Subject: [PATCH 039/198] Delete RoomView dead code --- src/components/structures/RoomView.tsx | 51 ++------------------------ 1 file changed, 3 insertions(+), 48 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index dbfba13297..fb9c0eb3a3 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -811,7 +811,7 @@ export default class RoomView extends React.Component { }; private onEvent = (ev) => { - if (ev.isBeingDecrypted() || ev.isDecryptionFailure() || ev.shouldAttemptDecryption()) return; + if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return; this.handleEffects(ev); }; @@ -831,14 +831,14 @@ export default class RoomView extends React.Component { private onRoomName = (room: Room) => { if (this.state.room && room.roomId == this.state.room.roomId) { - this.forceUpdate(); + // this.forceUpdate(); } }; private onKeyBackupStatus = () => { // Key backup status changes affect whether the in-room recovery // reminder is displayed. - this.forceUpdate(); + // this.forceUpdate(); }; public canResetTimeline = () => { @@ -1598,33 +1598,6 @@ export default class RoomView extends React.Component { this.setState({auxPanelMaxHeight: auxPanelMaxHeight}); }; - private onFullscreenClick = () => { - dis.dispatch({ - action: 'video_fullscreen', - fullscreen: true, - }, true); - }; - - private onMuteAudioClick = () => { - const call = this.getCallForRoom(); - if (!call) { - return; - } - const newState = !call.isMicrophoneMuted(); - call.setMicrophoneMuted(newState); - this.forceUpdate(); // TODO: just update the voip buttons - }; - - private onMuteVideoClick = () => { - const call = this.getCallForRoom(); - if (!call) { - return; - } - const newState = !call.isLocalVideoMuted(); - call.setLocalVideoMuted(newState); - this.forceUpdate(); // TODO: just update the voip buttons - }; - private onStatusBarVisible = () => { if (this.unmounted) return; this.setState({ @@ -1640,24 +1613,6 @@ export default class RoomView extends React.Component { }); }; - /** - * called by the parent component when PageUp/Down/etc is pressed. - * - * We pass it down to the scroll panel. - */ - private handleScrollKey = ev => { - let panel; - if (this.searchResultsPanel.current) { - panel = this.searchResultsPanel.current; - } else if (this.messagePanel) { - panel = this.messagePanel; - } - - if (panel) { - panel.handleScrollKey(ev); - } - }; - /** * get any current call for this room */ From 8f945ce8467d1f4b6984c544e29e30e0c8b6b5d1 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 19 May 2021 11:57:32 +0100 Subject: [PATCH 040/198] Render nothin rather than an empty div --- src/components/views/elements/AccessibleTooltipButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/AccessibleTooltipButton.tsx b/src/components/views/elements/AccessibleTooltipButton.tsx index 3bb264fb3e..c98a7c3156 100644 --- a/src/components/views/elements/AccessibleTooltipButton.tsx +++ b/src/components/views/elements/AccessibleTooltipButton.tsx @@ -73,7 +73,7 @@ export default class AccessibleTooltipButton extends React.PureComponent :
    ; + /> : null; return ( Date: Wed, 19 May 2021 14:32:49 +0100 Subject: [PATCH 041/198] prevent unwarranted RoomView re-render --- src/components/structures/RoomView.tsx | 15 ++++++++++++- src/components/structures/TimelinePanel.js | 25 ---------------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index fb9c0eb3a3..25ebbcf223 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -83,6 +83,7 @@ import { objectHasDiff } from "../../utils/objects"; import SpaceRoomView from "./SpaceRoomView"; import { IOpts } from "../../createRoom"; import {replaceableComponent} from "../../utils/replaceableComponent"; +import _ from 'lodash'; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -175,6 +176,7 @@ export interface IState { 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() + upgradeRecommendation?: { version: string; needsUpgrade: boolean; @@ -528,7 +530,18 @@ export default class RoomView extends React.Component { } shouldComponentUpdate(nextProps, nextState) { - return (objectHasDiff(this.props, nextProps) || objectHasDiff(this.state, nextState)); + const hasPropsDiff = objectHasDiff(this.props, nextProps); + + const newUpgradeRecommendation = nextState.upgradeRecommendation || {} + + const state = _.omit(this.state, ['upgradeRecommendation']); + const newState = _.omit(nextState, ['upgradeRecommendation']) + + const hasStateDiff = + objectHasDiff(state, newState) || + (newUpgradeRecommendation && newUpgradeRecommendation.needsUpgrade === true) + + return hasPropsDiff || hasStateDiff; } componentDidUpdate() { diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index af20c31cb2..832043d3c6 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -36,7 +36,6 @@ import shouldHideEvent from '../../shouldHideEvent'; import EditorStateTransfer from '../../utils/EditorStateTransfer'; import {haveTileForEvent} from "../views/rooms/EventTile"; import {UIFeature} from "../../settings/UIFeature"; -import {objectHasDiff} from "../../utils/objects"; import {replaceableComponent} from "../../utils/replaceableComponent"; import { arrayFastClone } from "../../utils/arrays"; @@ -265,30 +264,6 @@ class TimelinePanel extends React.Component { } } - shouldComponentUpdate(nextProps, nextState) { - if (objectHasDiff(this.props, nextProps)) { - if (DEBUG) { - console.group("Timeline.shouldComponentUpdate: props change"); - console.log("props before:", this.props); - console.log("props after:", nextProps); - console.groupEnd(); - } - return true; - } - - if (objectHasDiff(this.state, nextState)) { - if (DEBUG) { - console.group("Timeline.shouldComponentUpdate: state change"); - console.log("state before:", this.state); - console.log("state after:", nextState); - console.groupEnd(); - } - return true; - } - - return false; - } - componentWillUnmount() { // set a boolean to say we've been unmounted, which any pending // promises can use to throw away their results. From 83224dc7b66b6d3b51d6d58568759611726b0d04 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Wed, 19 May 2021 13:32:27 -0400 Subject: [PATCH 042/198] Ensure forward list room decorations are aligned Signed-off-by: Robin Townsend --- res/css/views/dialogs/_ForwardDialog.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss index 2a72ea4ffb..6fcd7d881a 100644 --- a/res/css/views/dialogs/_ForwardDialog.scss +++ b/res/css/views/dialogs/_ForwardDialog.scss @@ -82,6 +82,7 @@ limitations under the License. display: flex; justify-content: space-between; margin-top: 12px; + height: 32px; .mx_ForwardList_roomButton { display: flex; From 6cb6c7f3d0c538ac5e0742c806849d33ec685631 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Wed, 19 May 2021 13:33:48 -0400 Subject: [PATCH 043/198] Combine forward dialog room and DM lists Signed-off-by: Robin Townsend --- res/css/views/dialogs/_ForwardDialog.scss | 2 +- .../views/dialogs/ForwardDialog.tsx | 46 ++++--------------- 2 files changed, 9 insertions(+), 39 deletions(-) diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss index 6fcd7d881a..47ab219150 100644 --- a/res/css/views/dialogs/_ForwardDialog.scss +++ b/res/css/views/dialogs/_ForwardDialog.scss @@ -63,7 +63,7 @@ limitations under the License. margin-top: 24px; } - .mx_ForwardList_section { + .mx_ForwardList_results { margin-right: 6px; &:not(:first-child) { diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 1ef4841851..b7a176cfce 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -34,7 +34,6 @@ import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import AccessibleButton from "../elements/AccessibleButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; -import DMRoomMap from "../../../utils/DMRoomMap"; import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm"; @@ -162,26 +161,15 @@ const ForwardDialog: React.FC = ({ cli, event, permalinkCreator, onFinis getMxcAvatarUrl: () => profileInfo.avatar_url, }; - const visibleRooms = useMemo(() => sortRooms( + const [query, setQuery] = useState(""); + const lcQuery = query.toLowerCase(); + + const rooms = useMemo(() => sortRooms( cli.getVisibleRooms().filter( room => room.getMyMembership() === "join" && !(SettingsStore.getValue("feature_spaces") && room.isSpaceRoom()), ), - ), [cli]); - - const [query, setQuery] = useState(""); - const lcQuery = query.toLowerCase(); - - const [rooms, dms] = visibleRooms.reduce((arr, room) => { - if (room.name.toLowerCase().includes(lcQuery)) { - if (DMRoomMap.shared().getUserIdForRoomId(room.roomId)) { - arr[1].push(room); - } else { - arr[0].push(room); - } - } - return arr; - }, [[], []]); + ), [cli]).filter(room => room.name.toLowerCase().includes(lcQuery)); const previewLayout = SettingsStore.getValue("layout"); @@ -214,8 +202,7 @@ const ForwardDialog: React.FC = ({ cli, event, permalinkCreator, onFinis /> { rooms.length > 0 ? ( -
    -

    { _t("Rooms") }

    +
    { rooms.map(room => = ({ cli, event, permalinkCreator, onFinis />, ) }
    - ) : undefined } - - { dms.length > 0 ? ( -
    -

    { _t("Direct Messages") }

    - { dms.map(room => - , - ) } -
    - ) : undefined } - - { rooms.length + dms.length < 1 ? + ) : { _t("No results") } - : undefined } + }
    ; From d3623217068ca9d2bc8cb170ac1f9f8329b9de3a Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 20 May 2021 15:25:20 +0100 Subject: [PATCH 044/198] Simplify SenderProfile DOM structure --- res/css/views/rooms/_IRCLayout.scss | 15 ++++++--------- src/components/views/messages/SenderProfile.js | 17 +++++------------ 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss index b6b901757c..48505fbb53 100644 --- a/res/css/views/rooms/_IRCLayout.scss +++ b/res/css/views/rooms/_IRCLayout.scss @@ -177,16 +177,13 @@ $irc-line-height: $font-18px; .mx_SenderProfile_hover { background-color: $primary-bg-color; overflow: hidden; + display: flex; - > span { - display: flex; - - > .mx_SenderProfile_name { - overflow: hidden; - text-overflow: ellipsis; - min-width: var(--name-width); - text-align: end; - } + > .mx_SenderProfile_name { + overflow: hidden; + text-overflow: ellipsis; + min-width: var(--name-width); + text-align: end; } } diff --git a/src/components/views/messages/SenderProfile.js b/src/components/views/messages/SenderProfile.js index bd10526799..f1855de99e 100644 --- a/src/components/views/messages/SenderProfile.js +++ b/src/components/views/messages/SenderProfile.js @@ -110,19 +110,12 @@ export default class SenderProfile extends React.Component { const nameElem = name || ''; - // Name + flair - const nameFlair = - - { nameElem } - - { flair } - ; - return ( -
    -
    - { nameFlair } -
    +
    + + { nameElem } + + { flair }
    ); } From 171539d42d560bf37abbfdfba86bb0f22986f84e Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 20 May 2021 15:26:02 +0100 Subject: [PATCH 045/198] Simplify EventTile structure Only render MessageTimestamp to the DOM when a tile is hovered --- src/components/structures/MessagePanel.js | 65 +++++++++++------------ src/components/views/rooms/EventTile.tsx | 46 ++++++++++------ 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index d1071a9e19..2fb9a2df29 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -645,39 +645,36 @@ export default class MessagePanel extends React.Component { // use txnId as key if available so that we don't remount during sending ret.push( -
  • - - - -
  • , + + + , ); return ret; @@ -779,7 +776,7 @@ export default class MessagePanel extends React.Component { } _collectEventNode = (eventId, node) => { - this.eventNodes[eventId] = node; + this.eventNodes[eventId] = node?.ref?.current; } // once dynamic content in the events load, make the scrollPanel check the diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 19c5a7acaa..884669e398 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -277,6 +277,9 @@ interface IProps { // Helper to build permalinks for the room permalinkCreator?: RoomPermalinkCreator; + + // Symbol of the root node + as?: string } interface IState { @@ -291,6 +294,8 @@ interface IState { previouslyRequestedKeys: boolean; // The Relations model from the JS SDK for reactions to `mxEvent` reactions: Relations; + + hover: boolean; } @replaceableComponent("views.rooms.EventTile") @@ -322,6 +327,8 @@ export default class EventTile extends React.Component { previouslyRequestedKeys: false, // The Relations model from the JS SDK for reactions to `mxEvent` reactions: this.getReactions(), + + hover: false, }; // don't do RR animations until we are mounted @@ -333,6 +340,8 @@ export default class EventTile extends React.Component { // to determine if we've already subscribed and use a combination of other flags to find // out if we should even be subscribed at all. this.isListeningForReceipts = false; + + this.ref = React.createRef(); } /** @@ -960,7 +969,7 @@ export default class EventTile extends React.Component { onFocusChange={this.onActionBarFocusChange} /> : undefined; - const timestamp = this.props.mxEvent.getTs() ? + const timestamp = this.props.mxEvent.getTs() && this.state.hover ? : null; const keyRequestHelpText = @@ -1131,11 +1140,20 @@ export default class EventTile extends React.Component { // tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers return ( -
    - { ircTimestamp } - { sender } - { ircPadlock } -
    + React.createElement(this.props.as || "div", { + "ref": this.ref, + "className": classes, + "tabIndex": -1, + "aria-live": ariaLive, + "aria-atomic": "true", + "data-scroll-tokens": this.props["data-scroll-tokens"], + "onMouseEnter": () => this.setState({ hover: true }), + "onMouseLeave": () => this.setState({ hover: false }), + }, [ + ircTimestamp, + sender, + ircPadlock, +
    { groupTimestamp } { groupPadlock } { thread } @@ -1152,16 +1170,12 @@ export default class EventTile extends React.Component { { keyRequestInfo } { reactionsRow } { actionBar } -
    - {msgOption} - { - // The avatar goes after the event tile as it's absolutely positioned to be over the - // event tile line, so needs to be later in the DOM so it appears on top (this avoids - // the need for further z-indexing chaos) - } - { avatar } -
    - ); +
    , + msgOption, + avatar, + + ]) + ) } } } From f058fd8869dca84b05d70fc1390da262d2e38439 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 20 May 2021 15:39:25 +0100 Subject: [PATCH 046/198] Reduce amount of DOM nodes --- res/css/views/rooms/_IRCLayout.scss | 3 +-- src/components/views/elements/ReplyThread.js | 2 +- src/components/views/rooms/EventTile.tsx | 23 ++++++++++++-------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss index 48505fbb53..cf61ce569d 100644 --- a/res/css/views/rooms/_IRCLayout.scss +++ b/res/css/views/rooms/_IRCLayout.scss @@ -115,8 +115,7 @@ $irc-line-height: $font-18px; .mx_EventTile_line { .mx_EventTile_e2eIcon, .mx_TextualEvent, - .mx_MTextBody, - .mx_ReplyThread_wrapper_empty { + .mx_MTextBody { display: inline-block; } } diff --git a/src/components/views/elements/ReplyThread.js b/src/components/views/elements/ReplyThread.js index 870803995d..c336f34b51 100644 --- a/src/components/views/elements/ReplyThread.js +++ b/src/components/views/elements/ReplyThread.js @@ -214,7 +214,7 @@ export default class ReplyThread extends React.Component { static makeThread(parentEv, onHeightChanged, permalinkCreator, ref, layout) { if (!ReplyThread.getParentEventId(parentEv)) { - return
    ; + return null; } return { let left = 0; const receipts = this.props.readReceipts || []; + + if (receipts.length === 0) { + return null; + } + for (let i = 0; i < receipts.length; ++i) { const receipt = receipts[i]; @@ -699,10 +704,14 @@ export default class EventTile extends React.Component { } } - return - { remText } - { avatars } - ; + return ( +
    + + { remText } + { avatars } + ; +
    + ) } onSenderProfileClick = event => { @@ -1032,11 +1041,7 @@ export default class EventTile extends React.Component { let msgOption; if (this.props.showReadReceipts) { const readAvatars = this.getReadAvatars(); - msgOption = ( -
    - { readAvatars } -
    - ); + msgOption = readAvatars; } switch (this.props.tileShape) { From 9e55f2409214f97014143549997e5d445d9787ed Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 20 May 2021 16:11:33 +0100 Subject: [PATCH 047/198] Remove extraenous DOM nodes --- src/components/structures/ContextMenu.tsx | 2 +- src/components/structures/ToastContainer.tsx | 22 ++++++++++--------- src/components/views/rooms/RoomHeader.js | 18 +++++++-------- .../views/rooms/SimpleRoomHeader.js | 12 +++++----- src/components/views/rooms/WhoIsTypingTile.js | 2 +- 5 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index ad0f75e162..f9de113d07 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -389,7 +389,7 @@ export class ContextMenu extends React.PureComponent { } render(): React.ReactChild { - return ReactDOM.createPortal(this.renderMenu(), getOrCreateContainer()); + return ReactDOM.createPortal(this.renderMenu(), document.body); } } diff --git a/src/components/structures/ToastContainer.tsx b/src/components/structures/ToastContainer.tsx index 1fd3e3419f..273c8a079f 100644 --- a/src/components/structures/ToastContainer.tsx +++ b/src/components/structures/ToastContainer.tsx @@ -55,6 +55,7 @@ export default class ToastContainer extends React.Component<{}, IState> { const totalCount = this.state.toasts.length; const isStacked = totalCount > 1; let toast; + let containerClasses; if (totalCount !== 0) { const topToast = this.state.toasts[0]; const {title, icon, key, component, className, props} = topToast; @@ -79,16 +80,17 @@ export default class ToastContainer extends React.Component<{}, IState> {
    {React.createElement(component, toastProps)}
    ); + + containerClasses = classNames("mx_ToastContainer", { + "mx_ToastContainer_stacked": isStacked, + }); } - - const containerClasses = classNames("mx_ToastContainer", { - "mx_ToastContainer_stacked": isStacked, - }); - - return ( -
    - {toast} -
    - ); + return toast + ? ( +
    + {toast} +
    + ) + : null; } } diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 6d3b50c10d..a527d7625c 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -257,16 +257,14 @@ export default class RoomHeader extends React.Component { const e2eIcon = this.props.e2eStatus ? : undefined; return ( -
    -
    -
    { roomAvatar }
    -
    { e2eIcon }
    - { name } - { topicElement } - { cancelButton } - { rightRow } - -
    +
    +
    { roomAvatar }
    +
    { e2eIcon }
    + { name } + { topicElement } + { cancelButton } + { rightRow } +
    ); } diff --git a/src/components/views/rooms/SimpleRoomHeader.js b/src/components/views/rooms/SimpleRoomHeader.js index b2a66f6670..9aedb38654 100644 --- a/src/components/views/rooms/SimpleRoomHeader.js +++ b/src/components/views/rooms/SimpleRoomHeader.js @@ -62,13 +62,11 @@ export default class SimpleRoomHeader extends React.Component { } return ( -
    -
    -
    - { icon } - { this.props.title } - { cancelButton } -
    +
    +
    + { icon } + { this.props.title } + { cancelButton }
    ); diff --git a/src/components/views/rooms/WhoIsTypingTile.js b/src/components/views/rooms/WhoIsTypingTile.js index a25b43fc3a..396d64a6f8 100644 --- a/src/components/views/rooms/WhoIsTypingTile.js +++ b/src/components/views/rooms/WhoIsTypingTile.js @@ -204,7 +204,7 @@ export default class WhoIsTypingTile extends React.Component { this.props.whoIsTypingLimit, ); if (!typingString) { - return (
    ); + return null; } return ( From 229c4b98b44d14117a272c817ffb95ed622ec15d Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 20 May 2021 18:01:38 +0100 Subject: [PATCH 048/198] use userGroups cached value to avoid re-render --- src/components/views/elements/Flair.js | 2 +- .../views/messages/SenderProfile.js | 35 ++++++++++++------- src/stores/FlairStore.js | 4 +++ 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/components/views/elements/Flair.js b/src/components/views/elements/Flair.js index 73d5b91511..23858b860d 100644 --- a/src/components/views/elements/Flair.js +++ b/src/components/views/elements/Flair.js @@ -116,7 +116,7 @@ export default class Flair extends React.Component { render() { if (this.state.profiles.length === 0) { - return ; + return null; } const avatars = this.state.profiles.map((profile, index) => { return ; diff --git a/src/components/views/messages/SenderProfile.js b/src/components/views/messages/SenderProfile.js index f1855de99e..8f10954370 100644 --- a/src/components/views/messages/SenderProfile.js +++ b/src/components/views/messages/SenderProfile.js @@ -31,21 +31,23 @@ export default class SenderProfile extends React.Component { static contextType = MatrixClientContext; - state = { - userGroups: null, - relatedGroups: [], - }; + constructor(props) { + super(props); + const senderId = this.props.mxEvent.getSender(); + this.state = { + userGroups: FlairStore.cachedPublicisedGroups(senderId) || [], + relatedGroups: [], + }; + } componentDidMount() { this.unmounted = false; this._updateRelatedGroups(); - FlairStore.getPublicisedGroupsCached( - this.context, this.props.mxEvent.getSender(), - ).then((userGroups) => { - if (this.unmounted) return; - this.setState({userGroups}); - }); + if (this.state.userGroups.length === 0) { + this.getPublicisedGroups(); + } + this.context.on('RoomState.events', this.onRoomStateEvents); } @@ -55,6 +57,15 @@ export default class SenderProfile extends React.Component { this.context.removeListener('RoomState.events', this.onRoomStateEvents); } + async getPublicisedGroups() { + if (!this.unmounted) { + const userGroups = await FlairStore.getPublicisedGroupsCached( + this.context, this.props.mxEvent.getSender(), + ); + this.setState({userGroups}); + } + } + onRoomStateEvents = event => { if (event.getType() === 'm.room.related_groups' && event.getRoomId() === this.props.mxEvent.getRoomId() @@ -93,10 +104,10 @@ export default class SenderProfile extends React.Component { const {msgtype} = mxEvent.getContent(); if (msgtype === 'm.emote') { - return ; // emote message must include the name so don't duplicate it + return null; // emote message must include the name so don't duplicate it } - let flair =
    ; + let flair = null; if (this.props.enableFlair) { const displayedGroups = this._getDisplayedGroups( this.state.userGroups, this.state.relatedGroups, diff --git a/src/stores/FlairStore.js b/src/stores/FlairStore.js index 53d07d0452..23254b98ab 100644 --- a/src/stores/FlairStore.js +++ b/src/stores/FlairStore.js @@ -65,6 +65,10 @@ class FlairStore extends EventEmitter { delete this._userGroups[userId]; } + cachedPublicisedGroups(userId) { + return this._userGroups[userId]; + } + getPublicisedGroupsCached(matrixClient, userId) { if (this._userGroups[userId]) { return Promise.resolve(this._userGroups[userId]); From 0f63098c59674623b754e2b74a22b58cf985d443 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 20 May 2021 18:02:44 +0100 Subject: [PATCH 049/198] Remove typo semicolon --- src/components/views/rooms/EventTile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index c85945be7a..82d97bff98 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -709,7 +709,7 @@ export default class EventTile extends React.Component { { remText } { avatars } - ; +
    ) } From 47e007e08f9bedaf47cf59a63c9bd04219195d76 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 21 May 2021 10:20:24 +0100 Subject: [PATCH 050/198] batch load events in ReplyThread before adding them to the state --- src/components/views/elements/ReplyThread.js | 42 +++++++++----------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/components/views/elements/ReplyThread.js b/src/components/views/elements/ReplyThread.js index c336f34b51..bbced5328f 100644 --- a/src/components/views/elements/ReplyThread.js +++ b/src/components/views/elements/ReplyThread.js @@ -269,36 +269,27 @@ export default class ReplyThread extends React.Component { const {parentEv} = this.props; // at time of making this component we checked that props.parentEv has a parentEventId const ev = await this.getEvent(ReplyThread.getParentEventId(parentEv)); + if (this.unmounted) return; if (ev) { + const loadedEv = await this.getNextEvent(ev); this.setState({ events: [ev], - }, this.loadNextEvent); + loadedEv, + loading: false, + }); } else { this.setState({err: true}); } } - async loadNextEvent() { - if (this.unmounted) return; - const ev = this.state.events[0]; - const inReplyToEventId = ReplyThread.getParentEventId(ev); - - if (!inReplyToEventId) { - this.setState({ - loading: false, - }); - return; - } - - const loadedEv = await this.getEvent(inReplyToEventId); - if (this.unmounted) return; - - if (loadedEv) { - this.setState({loadedEv}); - } else { - this.setState({err: true}); + async getNextEvent(ev) { + try { + const inReplyToEventId = ReplyThread.getParentEventId(ev); + return await this.getEvent(inReplyToEventId); + } catch (e) { + return null; } } @@ -326,13 +317,18 @@ export default class ReplyThread extends React.Component { this.initialize(); } - onQuoteClick() { + async onQuoteClick() { const events = [this.state.loadedEv, ...this.state.events]; + let loadedEv = null; + if (events.length > 0) { + loadedEv = await this.getNextEvent(events[0]); + } + this.setState({ - loadedEv: null, + loadedEv, events, - }, this.loadNextEvent); + }); dis.fire(Action.FocusComposer); } From 5ba419db54476ea8a268e3816401c68c1745ee75 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 21 May 2021 10:21:54 +0100 Subject: [PATCH 051/198] split room header and header wrapper --- src/components/views/rooms/RoomHeader.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index a527d7625c..6d3b50c10d 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -257,14 +257,16 @@ export default class RoomHeader extends React.Component { const e2eIcon = this.props.e2eStatus ? : undefined; return ( -
    -
    { roomAvatar }
    -
    { e2eIcon }
    - { name } - { topicElement } - { cancelButton } - { rightRow } - +
    +
    +
    { roomAvatar }
    +
    { e2eIcon }
    + { name } + { topicElement } + { cancelButton } + { rightRow } + +
    ); } From ccfd6ba4b11c649e0655c72e683e4afb2bee0b11 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 21 May 2021 12:53:26 +0100 Subject: [PATCH 052/198] fix linting issues --- src/components/structures/RoomView.tsx | 4 ++-- src/components/views/rooms/EventTile.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 25ebbcf223..bb4e06bcfd 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -844,14 +844,14 @@ export default class RoomView extends React.Component { private onRoomName = (room: Room) => { if (this.state.room && room.roomId == this.state.room.roomId) { - // this.forceUpdate(); + this.forceUpdate(); } }; private onKeyBackupStatus = () => { // Key backup status changes affect whether the in-room recovery // reminder is displayed. - // this.forceUpdate(); + this.forceUpdate(); }; public canResetTimeline = () => { diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 82d97bff98..bd89acaef8 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -302,6 +302,7 @@ interface IState { export default class EventTile extends React.Component { private suppressReadReceiptAnimation: boolean; private isListeningForReceipts: boolean; + private ref: React.RefObject; private tile = React.createRef(); private replyThread = React.createRef(); From c428736191837360ed3842acb965b242c1c2c4f0 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 21 May 2021 14:59:26 +0100 Subject: [PATCH 053/198] Update MessagePanel test to account for new DOM structure --- test/components/structures/MessagePanel-test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/components/structures/MessagePanel-test.js b/test/components/structures/MessagePanel-test.js index dc70e3f7f6..5b466b4bb0 100644 --- a/test/components/structures/MessagePanel-test.js +++ b/test/components/structures/MessagePanel-test.js @@ -309,7 +309,7 @@ describe('MessagePanel', function() { const rm = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_RoomView_myReadMarker_container'); // it should follow the
  • which wraps the event tile for event 4 - const eventContainer = ReactDOM.findDOMNode(tiles[4]).parentNode; + const eventContainer = ReactDOM.findDOMNode(tiles[4]); expect(rm.previousSibling).toEqual(eventContainer); }); @@ -365,7 +365,7 @@ describe('MessagePanel', function() { const tiles = TestUtils.scryRenderedComponentsWithType( mp, sdk.getComponent('rooms.EventTile')); const tileContainers = tiles.map(function(t) { - return ReactDOM.findDOMNode(t).parentNode; + return ReactDOM.findDOMNode(t); }); // find the
  • which wraps the read marker @@ -460,7 +460,7 @@ describe('MessagePanel', function() { />, ); const Dates = res.find(sdk.getComponent('messages.DateSeparator')); - + expect(Dates.length).toEqual(1); }); }); From 7a045021514788f67c52bf6324e9bf16fceb42cc Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Fri, 21 May 2021 12:41:29 -0400 Subject: [PATCH 054/198] Iterate on forward dialog design feedback Signed-off-by: Robin Townsend --- res/css/views/dialogs/_ForwardDialog.scss | 80 ++++++++++------ .../views/dialogs/ForwardDialog.tsx | 93 +++++++++++-------- .../elements/AccessibleTooltipButton.tsx | 6 +- src/i18n/strings/en_EN.json | 6 +- .../views/dialogs/ForwardDialog-test.js | 12 +-- 5 files changed, 116 insertions(+), 81 deletions(-) diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss index 47ab219150..1eb37e09da 100644 --- a/res/css/views/dialogs/_ForwardDialog.scss +++ b/res/css/views/dialogs/_ForwardDialog.scss @@ -23,7 +23,15 @@ limitations under the License. min-height: 0; height: 80vh; - .mx_ForwardDialog_preview { + > h3 { + margin: 0 0 6px; + color: $secondary-fg-color; + font-size: $font-12px; + font-weight: $font-semi-bold; + line-height: $font-15px; + } + + > .mx_ForwardDialog_preview { max-height: 30%; flex-shrink: 0; overflow: scroll; @@ -43,7 +51,14 @@ limitations under the License. } } - .mx_ForwardList { + > hr { + width: 100%; + border: none; + border-top: 1px solid $input-border-color; + margin: 12px 0; + } + + > .mx_ForwardList { display: contents; .mx_SearchBox { @@ -54,8 +69,6 @@ limitations under the License. .mx_ForwardList_content { flex-grow: 1; - // Push the scrollbar into the gutter - margin-right: -6px; } .mx_ForwardList_noResults { @@ -64,30 +77,24 @@ limitations under the License. } .mx_ForwardList_results { - margin-right: 6px; - &:not(:first-child) { margin-top: 24px; } - > h3 { - margin: 0; - color: $secondary-fg-color; - font-size: $font-12px; - font-weight: $font-semi-bold; - line-height: $font-15px; - } - .mx_ForwardList_entry { display: flex; justify-content: space-between; - margin-top: 12px; height: 32px; + padding: 6px; + border-radius: 8px; + + &:hover { + background-color: $groupFilterPanel-bg-color; + } .mx_ForwardList_roomButton { display: flex; margin-right: 12px; - flex-grow: 1; min-width: 0; .mx_DecoratedRoomAvatar { @@ -105,26 +112,39 @@ limitations under the License. } .mx_ForwardList_sendButton { - &.mx_ForwardList_sending, &.mx_ForwardList_sent { - &::before { - content: ''; - display: inline-block; - background-color: $button-primary-bg-color; - mask-position: center; - mask-repeat: no-repeat; - mask-size: 14px; - width: 14px; - height: 14px; - margin-right: 5px; - } + position: relative; + + &:not(.mx_ForwardList_canSend) .mx_ForwardList_sendLabel { + // Hide the "Send" label while preserving button size + visibility: hidden; } - &.mx_ForwardList_sending::before { + .mx_ForwardList_sendIcon, .mx_NotificationBadge { + position: absolute; + } + + .mx_NotificationBadge { + opacity: 0.4; + } + + &.mx_ForwardList_sending .mx_ForwardList_sendIcon { + background-color: $button-primary-bg-color; mask-image: url('$(res)/img/element-icons/circle-sending.svg'); + mask-position: center; + mask-repeat: no-repeat; + mask-size: 14px; + width: 14px; + height: 14px; } - &.mx_ForwardList_sent::before { + &.mx_ForwardList_sent .mx_ForwardList_sendIcon { + background-color: $button-primary-bg-color; mask-image: url('$(res)/img/element-icons/circle-sent.svg'); + mask-position: center; + mask-repeat: no-repeat; + mask-size: 14px; + width: 14px; + height: 14px; } } } diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index b7a176cfce..3448e636d9 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -31,9 +31,11 @@ import {avatarUrlForUser} from "../../../Avatar"; import EventTile from "../rooms/EventTile"; import SearchBox from "../../structures/SearchBox"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; -import AccessibleButton from "../elements/AccessibleButton"; +import {Alignment} from '../elements/Tooltip'; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; +import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState"; +import NotificationBadge from "../rooms/NotificationBadge"; import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm"; @@ -82,52 +84,60 @@ const Entry: React.FC = ({ room, event, cli, onFinished }) => { } }; - let button; - if (room.maySendMessage()) { - let label; - let className; - if (sendState === SendState.CanSend) { - label = _t("Send"); - className = "mx_ForwardList_canSend"; - } else if (sendState === SendState.Sending) { - label = _t("Sending…"); - className = "mx_ForwardList_sending"; - } else if (sendState === SendState.Sent) { - label = _t("Sent"); - className = "mx_ForwardList_sent"; + let className; + let disabled = false; + let title; + let icon; + if (sendState === SendState.CanSend) { + className = "mx_ForwardList_canSend"; + if (room.maySendMessage()) { + title = _t("Send"); } else { - label = _t("Failed to send"); - className = "mx_ForwardList_sendFailed"; + disabled = true; + title = _t("You do not have permission to do this"); } - - button = - - { label } - ; + } else if (sendState === SendState.Sending) { + className = "mx_ForwardList_sending"; + disabled = true; + title = _t("Sending…"); + icon =
    ; + } else if (sendState === SendState.Sent) { + className = "mx_ForwardList_sent"; + disabled = true; + title = _t("Sent"); + icon =
    ; } else { - button = - {}} - disabled={true} - title={_t("You do not have permission to post to this room")} - > - { _t("Send") } - ; + className = "mx_ForwardList_sendFailed"; + disabled = true; + title = _t("Failed to send"); + icon = ; } return
    - + { room.name } - - { button } + + +
    { _t("Send") }
    + { icon } +
    ; }; @@ -180,6 +190,7 @@ const ForwardDialog: React.FC = ({ cli, event, permalinkCreator, onFinis onFinished={onFinished} fixedWidth={false} > +

    { _t("Message preview") }

    = ({ cli, event, permalinkCreator, onFinis permalinkCreator={permalinkCreator} />
    +
    -

    { _t("Forward to") }

    { @@ -28,6 +28,7 @@ interface ITooltipProps extends React.ComponentProps { tooltipClassName?: string; forceHide?: boolean; yOffset?: number; + alignment?: Alignment; } interface IState { @@ -66,13 +67,14 @@ export default class AccessibleTooltipButton extends React.PureComponent :
    ; return ( debug logs to help us track down the problem.": "PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.", "Report a bug": "Report a bug", "Please view existing bugs on Github first. No match? Start a new one.": "Please view existing bugs on Github first. No match? Start a new one.", + "You do not have permission to do this": "You do not have permission to do this", "Sending…": "Sending…", "Sent": "Sent", + "Open link": "Open link", "Forward message": "Forward message", - "Forward to": "Forward to", - "Filter your rooms and DMs": "Filter your rooms and DMs", + "Message preview": "Message preview", + "Search for rooms or people": "Search for rooms or people", "Confirm abort of host creation": "Confirm abort of host creation", "Are you sure you wish to abort creation of the host? The process cannot be continued.": "Are you sure you wish to abort creation of the host? The process cannot be continued.", "Abort": "Abort", diff --git a/test/components/views/dialogs/ForwardDialog-test.js b/test/components/views/dialogs/ForwardDialog-test.js index 331ee9d131..ce62b4fa7c 100644 --- a/test/components/views/dialogs/ForwardDialog-test.js +++ b/test/components/views/dialogs/ForwardDialog-test.js @@ -101,30 +101,30 @@ describe("ForwardDialog", () => { })); const firstButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").first(); - expect(firstButton.text()).toBe("Send"); + expect(firstButton.render().is(".mx_ForwardList_canSend")).toBe(true); act(() => { firstButton.simulate("click"); }); - expect(firstButton.text()).toBe("Sending…"); + expect(firstButton.render().is(".mx_ForwardList_sending")).toBe(true); await act(async () => { cancelSend(); // Wait one tick for the button to realize the send failed await new Promise(resolve => setImmediate(resolve)); }); - expect(firstButton.text()).toBe("Failed to send"); + expect(firstButton.render().is(".mx_ForwardList_sendFailed")).toBe(true); const secondButton = wrapper.find("AccessibleButton.mx_ForwardList_sendButton").at(1); - expect(secondButton.render().text()).toBe("Send"); + expect(secondButton.render().is(".mx_ForwardList_canSend")).toBe(true); act(() => { secondButton.simulate("click"); }); - expect(secondButton.text()).toBe("Sending…"); + expect(secondButton.render().is(".mx_ForwardList_sending")).toBe(true); await act(async () => { finishSend(); // Wait one tick for the button to realize the send succeeded await new Promise(resolve => setImmediate(resolve)); }); - expect(secondButton.text()).toBe("Sent"); + expect(secondButton.render().is(".mx_ForwardList_sent")).toBe(true); }); it("can render replies", async () => { From cd460a2555026fe0ceaf8bb53d31af7ad6405b64 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Fri, 21 May 2021 12:59:13 -0400 Subject: [PATCH 055/198] Adjust forward dialog copy Signed-off-by: Robin Townsend --- src/components/views/dialogs/ForwardDialog.tsx | 2 +- src/i18n/strings/en_EN.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 3448e636d9..ff08ce43df 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -94,7 +94,7 @@ const Entry: React.FC = ({ room, event, cli, onFinished }) => { title = _t("Send"); } else { disabled = true; - title = _t("You do not have permission to do this"); + title = _t("You don't have permission to do this"); } } else if (sendState === SendState.Sending) { className = "mx_ForwardList_sending"; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ebe5f54f61..ceb48852c5 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2202,7 +2202,7 @@ "PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.": "PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.", "Report a bug": "Report a bug", "Please view existing bugs on Github first. No match? Start a new one.": "Please view existing bugs on Github first. No match? Start a new one.", - "You do not have permission to do this": "You do not have permission to do this", + "You don't have permission to do this": "You don't have permission to do this", "Sending…": "Sending…", "Sent": "Sent", "Open link": "Open link", From 4851e962973f2607a5dd278feec7aee83566c647 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 09:17:29 +0100 Subject: [PATCH 056/198] Switch rooms documentation and polishing --- src/components/structures/MessagePanel.js | 1 + src/components/structures/RoomView.tsx | 10 ++++++---- src/components/views/rooms/EventTile.tsx | 6 +++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 2fb9a2df29..388c248a61 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -650,6 +650,7 @@ export default class MessagePanel extends React.Component { as="li" data-scroll-tokens={scrollToken} ref={this._collectEventNode.bind(this, eventId)} + alwaysShowTimestamps={this.props.alwaysShowTimestamps} mxEvent={mxEv} continuation={continuation} isRedacted={mxEv.isRedacted()} diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index bb4e06bcfd..2b50309d52 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -83,7 +83,7 @@ import { objectHasDiff } from "../../utils/objects"; import SpaceRoomView from "./SpaceRoomView"; import { IOpts } from "../../createRoom"; import {replaceableComponent} from "../../utils/replaceableComponent"; -import _ from 'lodash'; +import { omit } from 'lodash'; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -532,14 +532,16 @@ export default class RoomView extends React.Component { shouldComponentUpdate(nextProps, nextState) { const hasPropsDiff = objectHasDiff(this.props, nextProps); + // React only shallow comparison and we only want to trigger + // a component re-render if a room requires an upgrade const newUpgradeRecommendation = nextState.upgradeRecommendation || {} - const state = _.omit(this.state, ['upgradeRecommendation']); - const newState = _.omit(nextState, ['upgradeRecommendation']) + const state = omit(this.state, ['upgradeRecommendation']); + const newState = omit(nextState, ['upgradeRecommendation']) const hasStateDiff = objectHasDiff(state, newState) || - (newUpgradeRecommendation && newUpgradeRecommendation.needsUpgrade === true) + (newUpgradeRecommendation.needsUpgrade === true) return hasPropsDiff || hasStateDiff; } diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index bd89acaef8..54f5be7f21 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -280,6 +280,9 @@ interface IProps { // Symbol of the root node as?: string + + // whether or not to always show timestamps + alwaysShowTimestamps?: boolean } interface IState { @@ -979,7 +982,8 @@ export default class EventTile extends React.Component { onFocusChange={this.onActionBarFocusChange} /> : undefined; - const timestamp = this.props.mxEvent.getTs() && this.state.hover ? + const showTimestamp = this.props.mxEvent.getTs() && (this.props.alwaysShowTimestamps || this.state.hover); + const timestamp = showTimestamp ? : null; const keyRequestHelpText = From 400917623ce24f37b4eb12fda5769531f7c635c5 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 24 May 2021 08:33:28 -0400 Subject: [PATCH 057/198] Make myself the copyright holder for forward dialog code Signed-off-by: Robin Townsend --- res/css/views/dialogs/_ForwardDialog.scss | 2 +- src/components/views/dialogs/ForwardDialog.tsx | 2 +- test/components/views/dialogs/ForwardDialog-test.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss index 1eb37e09da..593fe12531 100644 --- a/res/css/views/dialogs/_ForwardDialog.scss +++ b/res/css/views/dialogs/_ForwardDialog.scss @@ -1,5 +1,5 @@ /* -Copyright 2021 The Matrix.org Foundation C.I.C. +Copyright 2021 Robin Townsend. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index ff08ce43df..66b4b49997 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -1,5 +1,5 @@ /* -Copyright 2021 The Matrix.org Foundation C.I.C. +Copyright 2021 Robin Townsend. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/test/components/views/dialogs/ForwardDialog-test.js b/test/components/views/dialogs/ForwardDialog-test.js index ce62b4fa7c..fddba1d5ae 100644 --- a/test/components/views/dialogs/ForwardDialog-test.js +++ b/test/components/views/dialogs/ForwardDialog-test.js @@ -1,5 +1,5 @@ /* -Copyright 2021 The Matrix.org Foundation C.I.C. +Copyright 2021 Robin Townsend. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 6ced61b7096ed31cd00ada688495235fa61b9df8 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 24 May 2021 08:34:03 -0400 Subject: [PATCH 058/198] Use camelCase Signed-off-by: Robin Townsend --- src/components/views/context_menus/MessageContextMenu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 5494b0a32f..4120f7be9e 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -161,7 +161,7 @@ export default class MessageContextMenu extends React.Component { cli: MatrixClientPeg.get(), event: this.props.mxEvent, permalinkCreator: this.props.permalinkCreator, - }, 'mx_Dialog_forwardmessage'); + }, 'mx_Dialog_forwardMessage'); this.closeMenu(); }; From 121ed5eba996f41ce78c8b789278d196443e53b1 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 24 May 2021 08:51:04 -0400 Subject: [PATCH 059/198] Pass Matrix client around as matrixClient Signed-off-by: Robin Townsend --- .../views/context_menus/MessageContextMenu.js | 2 +- src/components/views/dialogs/ForwardDialog.tsx | 10 +++++----- test/components/views/dialogs/ForwardDialog-test.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 4120f7be9e..08fc5844ec 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -158,7 +158,7 @@ export default class MessageContextMenu extends React.Component { onForwardClick = () => { Modal.createTrackedDialog('Forward Message', '', ForwardDialog, { - cli: MatrixClientPeg.get(), + matrixClient: MatrixClientPeg.get(), event: this.props.mxEvent, permalinkCreator: this.props.permalinkCreator, }, 'mx_Dialog_forwardMessage'); diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 66b4b49997..3087ce51a1 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -42,7 +42,7 @@ import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/Recent const AVATAR_SIZE = 30; interface IProps extends IDialogProps { - cli: MatrixClient; + matrixClient: MatrixClient; // The event to forward event: MatrixEvent; // We need a permalink creator for the source room to pass through to EventTile @@ -53,7 +53,7 @@ interface IProps extends IDialogProps { interface IEntryProps { room: Room; event: MatrixEvent; - cli: MatrixClient; + matrixClient: MatrixClient; onFinished(success: boolean): void; } @@ -64,7 +64,7 @@ enum SendState { Failed, } -const Entry: React.FC = ({ room, event, cli, onFinished }) => { +const Entry: React.FC = ({ room, event, matrixClient: cli, onFinished }) => { const [sendState, setSendState] = useState(SendState.CanSend); const jumpToRoom = () => { @@ -141,7 +141,7 @@ const Entry: React.FC = ({ room, event, cli, onFinished }) => {
    ; }; -const ForwardDialog: React.FC = ({ cli, event, permalinkCreator, onFinished }) => { +const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCreator, onFinished }) => { const userId = cli.getUserId(); const [profileInfo, setProfileInfo] = useState({}); useEffect(() => { @@ -219,7 +219,7 @@ const ForwardDialog: React.FC = ({ cli, event, permalinkCreator, onFinis key={room.roomId} room={room} event={event} - cli={cli} + matrixClient={cli} onFinished={onFinished} />, ) } diff --git a/test/components/views/dialogs/ForwardDialog-test.js b/test/components/views/dialogs/ForwardDialog-test.js index fddba1d5ae..004954c713 100644 --- a/test/components/views/dialogs/ForwardDialog-test.js +++ b/test/components/views/dialogs/ForwardDialog-test.js @@ -47,7 +47,7 @@ describe("ForwardDialog", () => { await act(async () => { wrapper = mount( Date: Mon, 24 May 2021 08:55:08 -0400 Subject: [PATCH 060/198] Give forward dialog send buttons an accessible label Signed-off-by: Robin Townsend --- src/components/views/dialogs/ForwardDialog.tsx | 6 +++--- src/i18n/strings/en_EN.json | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 3087ce51a1..59475b1b51 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -99,13 +99,13 @@ const Entry: React.FC = ({ room, event, matrixClient: cli, onFinish } else if (sendState === SendState.Sending) { className = "mx_ForwardList_sending"; disabled = true; - title = _t("Sending…"); - icon =
    ; + title = _t("Sending"); + icon =
    ; } else if (sendState === SendState.Sent) { className = "mx_ForwardList_sent"; disabled = true; title = _t("Sent"); - icon =
    ; + icon =
    ; } else { className = "mx_ForwardList_sendFailed"; disabled = true; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c2c6aa9e6d..817765ca69 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2203,7 +2203,7 @@ "Report a bug": "Report a bug", "Please view existing bugs on Github first. No match? Start a new one.": "Please view existing bugs on Github first. No match? Start a new one.", "You don't have permission to do this": "You don't have permission to do this", - "Sending…": "Sending…", + "Sending": "Sending", "Sent": "Sent", "Open link": "Open link", "Forward message": "Forward message", @@ -2668,7 +2668,6 @@ "Some of your messages have not been sent": "Some of your messages have not been sent", "Delete all": "Delete all", "Retry all": "Retry all", - "Sending": "Sending", "You can select all or individual messages to retry or delete": "You can select all or individual messages to retry or delete", "Connectivity to the server has been lost.": "Connectivity to the server has been lost.", "Sent messages will be stored until your connection has returned.": "Sent messages will be stored until your connection has returned.", From 170b11d130e28b75285baa0a807b41f833435831 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 25 May 2021 12:13:16 +0100 Subject: [PATCH 061/198] Convert RightPanel to Typescript --- .../{RightPanel.js => RightPanel.tsx} | 126 +++++++++--------- src/components/views/right_panel/UserInfo.tsx | 20 ++- 2 files changed, 70 insertions(+), 76 deletions(-) rename src/components/structures/{RightPanel.js => RightPanel.tsx} (79%) diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.tsx similarity index 79% rename from src/components/structures/RightPanel.js rename to src/components/structures/RightPanel.tsx index d8c763eabd..15fa94e035 100644 --- a/src/components/structures/RightPanel.js +++ b/src/components/structures/RightPanel.tsx @@ -1,6 +1,6 @@ /* Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> -Copyright 2015 - 2020 The Matrix.org Foundation C.I.C. +Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,13 +16,14 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import {Room} from "matrix-js-sdk/src/models/room"; +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"; -import * as sdk from '../../index'; import dis from '../../dispatcher/dispatcher'; import RateLimitedFunc from '../../ratelimitedfunc'; -import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker'; import GroupStore from '../../stores/GroupStore'; import { RightPanelPhases, @@ -36,50 +37,70 @@ import RoomSummaryCard from "../views/right_panel/RoomSummaryCard"; import WidgetCard from "../views/right_panel/WidgetCard"; import {replaceableComponent} from "../../utils/replaceableComponent"; import SettingsStore from "../../settings/SettingsStore"; +import {ActionPayload} from "../../dispatcher/payloads"; +import MemberList from "../views/rooms/MemberList"; +import GroupMemberList from "../views/groups/GroupMemberList"; +import GroupRoomList from "../views/groups/GroupRoomList"; +import GroupRoomInfo from "../views/groups/GroupRoomInfo"; +import UserInfo from "../views/right_panel/UserInfo"; +import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo"; +import FilePanel from "./FilePanel"; +import NotificationPanel from "./NotificationPanel"; +import ResizeNotifier from "../../utils/ResizeNotifier"; + +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 + resizeNotifier: ResizeNotifier; +} + +interface IState { + phase: RightPanelPhases; + isUserPrivilegedInGroup?: boolean; + member?: RoomMember; + verificationRequest?: VerificationRequest; + verificationRequestPromise?: Promise; + space?: Room; + widgetId?: string; + groupRoomId?: string; + groupId?: string; + event: MatrixEvent; +} @replaceableComponent("structures.RightPanel") -export default class RightPanel extends React.Component { - static get propTypes() { - return { - room: PropTypes.instanceOf(Room), // if showing panels for a given room, this is set - groupId: PropTypes.string, // if showing panels for a given group, this is set - user: PropTypes.object, // used if we know the user ahead of opening the panel - }; - } - +export default class RightPanel extends React.Component { static contextType = MatrixClientContext; + private readonly delayedUpdate: RateLimitedFunc; + private dispatcherRef: string; + constructor(props, context) { super(props, context); this.state = { ...RightPanelStore.getSharedInstance().roomPanelPhaseParams, - phase: this._getPhaseFromProps(), + phase: this.getPhaseFromProps(), isUserPrivilegedInGroup: null, - member: this._getUserForPanel(), + member: this.getUserForPanel(), }; - this.onAction = this.onAction.bind(this); - this.onRoomStateMember = this.onRoomStateMember.bind(this); - this.onGroupStoreUpdated = this.onGroupStoreUpdated.bind(this); - this.onInviteToGroupButtonClick = this.onInviteToGroupButtonClick.bind(this); - this.onAddRoomToGroupButtonClick = this.onAddRoomToGroupButtonClick.bind(this); - this._delayedUpdate = new RateLimitedFunc(() => { + this.delayedUpdate = new RateLimitedFunc(() => { this.forceUpdate(); }, 500); } - // Helper function to split out the logic for _getPhaseFromProps() and the constructor + // Helper function to split out the logic for getPhaseFromProps() and the constructor // as both are called at the same time in the constructor. - _getUserForPanel() { + private getUserForPanel() { if (this.state && this.state.member) return this.state.member; const lastParams = RightPanelStore.getSharedInstance().roomPanelPhaseParams; return this.props.user || lastParams['member']; } // gets the current phase from the props and also maybe the store - _getPhaseFromProps() { + private getPhaseFromProps() { const rps = RightPanelStore.getSharedInstance(); - const userForPanel = this._getUserForPanel(); + const userForPanel = this.getUserForPanel(); if (this.props.groupId) { if (!RIGHT_PANEL_PHASES_NO_ARGS.includes(rps.groupPanelPhase)) { dis.dispatch({action: Action.SetRightPanelPhase, phase: RightPanelPhases.GroupMemberList}); @@ -118,7 +139,7 @@ export default class RightPanel extends React.Component { this.dispatcherRef = dis.register(this.onAction); const cli = this.context; cli.on("RoomState.members", this.onRoomStateMember); - this._initGroupStore(this.props.groupId); + this.initGroupStore(this.props.groupId); } componentWillUnmount() { @@ -126,61 +147,47 @@ export default class RightPanel extends React.Component { if (this.context) { this.context.removeListener("RoomState.members", this.onRoomStateMember); } - this._unregisterGroupStore(this.props.groupId); + this.unregisterGroupStore(); } // TODO: [REACT-WARNING] Replace with appropriate lifecycle event UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line camelcase if (newProps.groupId !== this.props.groupId) { - this._unregisterGroupStore(this.props.groupId); - this._initGroupStore(newProps.groupId); + this.unregisterGroupStore(); + this.initGroupStore(newProps.groupId); } } - _initGroupStore(groupId) { + private initGroupStore(groupId: string) { if (!groupId) return; GroupStore.registerListener(groupId, this.onGroupStoreUpdated); } - _unregisterGroupStore() { + private unregisterGroupStore() { GroupStore.unregisterListener(this.onGroupStoreUpdated); } - onGroupStoreUpdated() { + private onGroupStoreUpdated = () => { this.setState({ isUserPrivilegedInGroup: GroupStore.isUserPrivileged(this.props.groupId), }); - } + }; - onInviteToGroupButtonClick() { - showGroupInviteDialog(this.props.groupId).then(() => { - this.setState({ - phase: RightPanelPhases.GroupMemberList, - }); - }); - } - - onAddRoomToGroupButtonClick() { - showGroupAddRoomDialog(this.props.groupId).then(() => { - this.forceUpdate(); - }); - } - - onRoomStateMember(ev, state, member) { + private onRoomStateMember = (ev: MatrixEvent, _, member: RoomMember) => { if (!this.props.room || member.roomId !== this.props.room.roomId) { return; } // redraw the badge on the membership list if (this.state.phase === RightPanelPhases.RoomMemberList && member.roomId === this.props.room.roomId) { - this._delayedUpdate(); + this.delayedUpdate(); } else if (this.state.phase === RightPanelPhases.RoomMemberInfo && member.roomId === this.props.room.roomId && member.userId === this.state.member.userId) { // refresh the member info (e.g. new power level) - this._delayedUpdate(); + this.delayedUpdate(); } - } + }; - onAction(payload) { + private onAction = (payload: ActionPayload) => { if (payload.action === Action.AfterRightPanelPhaseChange) { this.setState({ phase: payload.phase, @@ -194,9 +201,9 @@ export default class RightPanel extends React.Component { space: payload.space, }); } - } + }; - onClose = () => { + private onClose = () => { // 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. @@ -224,16 +231,6 @@ export default class RightPanel extends React.Component { }; render() { - const MemberList = sdk.getComponent('rooms.MemberList'); - const UserInfo = sdk.getComponent('right_panel.UserInfo'); - const ThirdPartyMemberInfo = sdk.getComponent('rooms.ThirdPartyMemberInfo'); - const NotificationPanel = sdk.getComponent('structures.NotificationPanel'); - const FilePanel = sdk.getComponent('structures.FilePanel'); - - const GroupMemberList = sdk.getComponent('groups.GroupMemberList'); - const GroupRoomList = sdk.getComponent('groups.GroupRoomList'); - const GroupRoomInfo = sdk.getComponent('groups.GroupRoomInfo'); - let panel =
    ; const roomId = this.props.room ? this.props.room.roomId : undefined; @@ -285,6 +282,7 @@ export default class RightPanel extends React.Component { user={this.state.member} groupId={this.props.groupId} key={this.state.member.userId} + phase={this.state.phase} onClose={this.onClose} />; break; diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index 9798b282f6..9ed2943bbb 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -25,6 +25,7 @@ import {User} from 'matrix-js-sdk/src/models/user'; import {Room} from 'matrix-js-sdk/src/models/room'; import {EventTimeline} from 'matrix-js-sdk/src/models/event-timeline'; import {MatrixEvent} from 'matrix-js-sdk/src/models/event'; +import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; import dis from '../../../dispatcher/dispatcher'; import Modal from '../../../Modal'; @@ -1529,21 +1530,16 @@ interface IProps { user: Member; groupId?: string; room?: Room; - phase: RightPanelPhases.RoomMemberInfo | RightPanelPhases.GroupMemberInfo | RightPanelPhases.SpaceMemberInfo; + phase: RightPanelPhases.RoomMemberInfo + | RightPanelPhases.GroupMemberInfo + | RightPanelPhases.SpaceMemberInfo + | RightPanelPhases.EncryptionPanel; onClose(): void; + verificationRequest?: VerificationRequest; + verificationRequestPromise?: Promise; } -interface IPropsWithEncryptionPanel extends React.ComponentProps { - user: Member; - groupId: void; - room: Room; - phase: RightPanelPhases.EncryptionPanel; - onClose(): void; -} - -type Props = IProps | IPropsWithEncryptionPanel; - -const UserInfo: React.FC = ({ +const UserInfo: React.FC = ({ user, groupId, room, From 152c178ea9786fa071df680598600c4ba34fa18d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 25 May 2021 12:15:37 +0100 Subject: [PATCH 062/198] Convert NotificationPanel to Typescript --- ...ficationPanel.js => NotificationPanel.tsx} | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) rename src/components/structures/{NotificationPanel.js => NotificationPanel.tsx} (76%) diff --git a/src/components/structures/NotificationPanel.js b/src/components/structures/NotificationPanel.tsx similarity index 76% rename from src/components/structures/NotificationPanel.js rename to src/components/structures/NotificationPanel.tsx index 41aafc8b13..7e1566521d 100644 --- a/src/components/structures/NotificationPanel.js +++ b/src/components/structures/NotificationPanel.tsx @@ -1,7 +1,5 @@ /* -Copyright 2016 OpenMarket Ltd -Copyright 2019 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2016, 2019, 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,29 +14,25 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; -import PropTypes from "prop-types"; +import React from "react"; import { _t } from '../../languageHandler'; import {MatrixClientPeg} from "../../MatrixClientPeg"; -import * as sdk from "../../index"; import BaseCard from "../views/right_panel/BaseCard"; import {replaceableComponent} from "../../utils/replaceableComponent"; +import TimelinePanel from "./TimelinePanel"; +import Spinner from "../views/elements/Spinner"; + +interface IProps { + onClose(): void; +} /* * Component which shows the global notification list using a TimelinePanel */ @replaceableComponent("structures.NotificationPanel") -class NotificationPanel extends React.Component { - static propTypes = { - onClose: PropTypes.func.isRequired, - }; - +class NotificationPanel extends React.Component { render() { - // wrap a TimelinePanel with the jump-to-event bits turned off. - const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); - const Loader = sdk.getComponent("elements.Spinner"); - const emptyState = (

    {_t('You’re all caught up')}

    {_t('You have no visible notifications.')}

    @@ -47,6 +41,7 @@ class NotificationPanel extends React.Component { let content; const timelineSet = MatrixClientPeg.get().getNotifTimelineSet(); if (timelineSet) { + // wrap a TimelinePanel with the jump-to-event bits turned off. content = ( ; + content = ; } return From 13427aaf0778a8a09d574d99ea33388586ee9a79 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 25 May 2021 13:02:09 +0100 Subject: [PATCH 063/198] Add a pulse animation to the pinned messages indicator and move it --- res/css/views/rooms/_RoomHeader.scss | 41 ++++++++++++++++++------ res/img/element-icons/room/pin.svg | 10 +++--- src/components/views/rooms/RoomHeader.js | 11 +------ 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index 387d1588a3..a102dfcdd7 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -281,18 +281,39 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/pin.svg'); } -.mx_RoomHeader_pinsIndicator { - position: absolute; - right: 0; - bottom: 4px; - width: 8px; - height: 8px; - border-radius: 8px; - background-color: $pinned-color; -} +$dot-size: 8px; +$pulse-color: $pinned-unread-color; .mx_RoomHeader_pinsIndicatorUnread { - background-color: $pinned-unread-color; + position: absolute; + right: 0; + top: 0; + margin: 4px; + width: $dot-size; + height: $dot-size; + border-radius: 50%; + transform: scale(1); + background: rgba($pulse-color, 1); + box-shadow: 0 0 0 0 rgba($pulse-color, 1); + animation: mx_RoomHeader_indicator_pulse 2s infinite; + animation-iteration-count: 1; +} + +@keyframes mx_RoomHeader_indicator_pulse { + 0% { + transform: scale(0.95); + box-shadow: 0 0 0 0 rgba($pulse-color, 0.7); + } + + 70% { + transform: scale(1); + box-shadow: 0 0 0 10px rgba($pulse-color, 0); + } + + 100% { + transform: scale(0.95); + box-shadow: 0 0 0 0 rgba($pulse-color, 0); + } } @media only screen and (max-width: 480px) { diff --git a/res/img/element-icons/room/pin.svg b/res/img/element-icons/room/pin.svg index 16941b329b..2448fc61c5 100644 --- a/res/img/element-icons/room/pin.svg +++ b/res/img/element-icons/room/pin.svg @@ -1,7 +1,7 @@ - - - - - + + + + + diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 6d3b50c10d..7eeb5875ef 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -110,13 +110,6 @@ export default class RoomHeader extends React.Component { return true; } - _hasPins() { - const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", ''); - if (!currentPinEvent) return false; - - return !(currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0); - } - render() { let searchStatus = null; let cancelButton = null; @@ -184,9 +177,7 @@ export default class RoomHeader extends React.Component { if (this.props.onPinnedClick && SettingsStore.getValue('feature_pinning')) { let pinsIndicator = null; if (this._hasUnreadPins()) { - pinsIndicator = (
    ); - } else if (this._hasPins()) { - pinsIndicator = (
    ); + pinsIndicator = (
    ); } pinnedEventsButton = From 96928e5d319653352ad8a5ae56840cfb5dae1e02 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 25 May 2021 13:17:14 +0100 Subject: [PATCH 064/198] Header Buttons switch to a fragment from an array of nodes --- .../views/right_panel/GroupHeaderButtons.tsx | 14 ++++++++------ src/components/views/right_panel/HeaderButtons.tsx | 2 +- .../views/right_panel/RoomHeaderButtons.tsx | 10 ++++------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/views/right_panel/GroupHeaderButtons.tsx b/src/components/views/right_panel/GroupHeaderButtons.tsx index f006975b08..5ae5709d44 100644 --- a/src/components/views/right_panel/GroupHeaderButtons.tsx +++ b/src/components/views/right_panel/GroupHeaderButtons.tsx @@ -84,19 +84,21 @@ export default class GroupHeaderButtons extends HeaderButtons { }; renderButtons() { - return [ - + , - + , - ]; + /> + ; } } diff --git a/src/components/views/right_panel/HeaderButtons.tsx b/src/components/views/right_panel/HeaderButtons.tsx index 2144292679..6b8445e9b9 100644 --- a/src/components/views/right_panel/HeaderButtons.tsx +++ b/src/components/views/right_panel/HeaderButtons.tsx @@ -95,7 +95,7 @@ export default abstract class HeaderButtons extends React.Component diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index 0571622e64..2a7a9e191b 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -81,23 +81,21 @@ export default class RoomHeaderButtons extends HeaderButtons { }; public renderButtons() { - return [ + return <> , + /> , - ]; + /> + ; } } From 02d11b8926494b16f897e49f97076af8461c1278 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 25 May 2021 13:53:46 +0100 Subject: [PATCH 065/198] Extend HeaderButton and HeaderButtons to be more generic --- .../views/right_panel/HeaderButton.tsx | 26 ++++++++----------- .../views/right_panel/HeaderButtons.tsx | 4 +-- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/components/views/right_panel/HeaderButton.tsx b/src/components/views/right_panel/HeaderButton.tsx index 2bc360e380..8451f69fd3 100644 --- a/src/components/views/right_panel/HeaderButton.tsx +++ b/src/components/views/right_panel/HeaderButton.tsx @@ -29,8 +29,6 @@ interface IProps { isHighlighted: boolean; // click handler onClick: () => void; - // The badge to display above the icon - badge?: React.ReactNode; // The parameters to track the click event analytics: Parameters; @@ -40,31 +38,29 @@ interface IProps { title: string; } -// TODO: replace this, the composer buttons and the right panel buttons with a unified -// representation +// TODO: replace this, the composer buttons and the right panel buttons with a unified representation @replaceableComponent("views.right_panel.HeaderButton") export default class HeaderButton extends React.Component { - constructor(props: IProps) { - super(props); - this.onClick = this.onClick.bind(this); - } - - private onClick() { + private onClick = () => { Analytics.trackEvent(...this.props.analytics); this.props.onClick(); - } + }; public render() { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const {isHighlighted, onClick, analytics, name, title, ...props} = this.props; + const classes = classNames({ mx_RightPanel_headerButton: true, - mx_RightPanel_headerButton_highlight: this.props.isHighlighted, - [`mx_RightPanel_${this.props.name}`]: true, + mx_RightPanel_headerButton_highlight: isHighlighted, + [`mx_RightPanel_${name}`]: true, }); return ; diff --git a/src/components/views/right_panel/HeaderButtons.tsx b/src/components/views/right_panel/HeaderButtons.tsx index 6b8445e9b9..281d7edd8b 100644 --- a/src/components/views/right_panel/HeaderButtons.tsx +++ b/src/components/views/right_panel/HeaderButtons.tsx @@ -43,11 +43,11 @@ interface IState { interface IProps {} @replaceableComponent("views.right_panel.HeaderButtons") -export default abstract class HeaderButtons extends React.Component { +export default abstract class HeaderButtons

    extends React.Component { private storeToken: EventSubscription; private dispatcherRef: string; - constructor(props: IProps, kind: HeaderKind) { + constructor(props: IProps & P, kind: HeaderKind) { super(props); const rps = RightPanelStore.getSharedInstance(); From a6ca8f797dd851552608b5ea0c8355d34bb9d25c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 25 May 2021 15:34:44 +0100 Subject: [PATCH 066/198] Fix useAsyncMemo out-of-order resolutions --- src/hooks/useAsyncMemo.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/hooks/useAsyncMemo.ts b/src/hooks/useAsyncMemo.ts index 38c70de259..eda44b1ae4 100644 --- a/src/hooks/useAsyncMemo.ts +++ b/src/hooks/useAsyncMemo.ts @@ -21,7 +21,15 @@ type Fn = () => Promise; export const useAsyncMemo = (fn: Fn, deps: DependencyList, initialValue?: T): T => { const [value, setValue] = useState(initialValue); useEffect(() => { - fn().then(setValue); + let discard = false; + fn().then(v => { + if (!discard) { + setValue(v); + } + }); + return () => { + discard = true; + }; }, deps); // eslint-disable-line react-hooks/exhaustive-deps return value; }; From 4fa6d3599b2b1902f5faa78cf9a5f130397de1ae Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 25 May 2021 15:44:39 +0100 Subject: [PATCH 067/198] Convert PinnedEventTile to Typescript --- ...PinnedEventTile.js => PinnedEventTile.tsx} | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) rename src/components/views/rooms/{PinnedEventTile.js => PinnedEventTile.tsx} (76%) diff --git a/src/components/views/rooms/PinnedEventTile.js b/src/components/views/rooms/PinnedEventTile.tsx similarity index 76% rename from src/components/views/rooms/PinnedEventTile.js rename to src/components/views/rooms/PinnedEventTile.tsx index 78cf422cc6..482fd6ab17 100644 --- a/src/components/views/rooms/PinnedEventTile.js +++ b/src/components/views/rooms/PinnedEventTile.tsx @@ -1,5 +1,6 @@ /* Copyright 2017 Travis Ralston +Copyright 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,7 +16,9 @@ limitations under the License. */ import React from "react"; -import PropTypes from 'prop-types'; +import { Room } from "matrix-js-sdk/src/models/room"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + import {MatrixClientPeg} from "../../../MatrixClientPeg"; import dis from "../../../dispatcher/dispatcher"; import AccessibleButton from "../elements/AccessibleButton"; @@ -25,15 +28,15 @@ import { _t } from '../../../languageHandler'; import {formatFullDate} from '../../../DateUtils'; import {replaceableComponent} from "../../../utils/replaceableComponent"; -@replaceableComponent("views.rooms.PinnedEventTile") -export default class PinnedEventTile extends React.Component { - static propTypes = { - mxRoom: PropTypes.object.isRequired, - mxEvent: PropTypes.object.isRequired, - onUnpinned: PropTypes.func, - }; +interface IProps { + mxRoom: Room; + mxEvent: MatrixEvent; + onUnpinned?(): void; +} - onTileClicked = () => { +@replaceableComponent("views.rooms.PinnedEventTile") +export default class PinnedEventTile extends React.Component { + private onTileClicked = () => { dis.dispatch({ action: 'view_room', event_id: this.props.mxEvent.getId(), @@ -42,7 +45,7 @@ export default class PinnedEventTile extends React.Component { }); }; - onUnpinClicked = () => { + private onUnpinClicked = () => { const pinnedEvents = this.props.mxRoom.currentState.getStateEvents("m.room.pinned_events", ""); if (!pinnedEvents || !pinnedEvents.getContent().pinned) { // Nothing to do: already unpinned @@ -60,7 +63,7 @@ export default class PinnedEventTile extends React.Component { } }; - _canUnpin() { + private canUnpin() { return this.props.mxRoom.currentState.mayClientSendStateEvent('m.room.pinned_events', MatrixClientPeg.get()); } @@ -71,10 +74,16 @@ export default class PinnedEventTile extends React.Component { const avatarSize = 40; let unpinButton = null; - if (this._canUnpin()) { + if (this.canUnpin()) { unpinButton = ( - {_t('Unpin + {_t('Unpin ); } @@ -82,14 +91,22 @@ export default class PinnedEventTile extends React.Component { return (

    - + { _t("Jump to message") } { unpinButton }
    - + { senderProfile ? senderProfile.name : sender } From 59f4c728c9a9b2b6072ae26fb1e5f0953fe17fd0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 25 May 2021 16:10:44 +0100 Subject: [PATCH 068/198] Initial cut of Pinned event card in the right panel --- res/css/_components.scss | 2 +- res/css/structures/_RightPanel.scss | 42 +++ .../right_panel/_PinnedMessagesCard.scss | 241 ++++++++++++++++++ res/css/views/rooms/_PinnedEventsPanel.scss | 37 --- res/css/views/rooms/_RoomHeader.scss | 39 --- src/components/structures/RightPanel.tsx | 11 +- src/components/structures/RoomView.tsx | 15 -- .../views/right_panel/PinnedMessagesCard.tsx | 145 +++++++++++ .../views/right_panel/RoomHeaderButtons.tsx | 46 +++- src/components/views/right_panel/UserInfo.tsx | 3 - src/components/views/rooms/RoomHeader.js | 49 +--- src/settings/Settings.tsx | 4 - src/stores/RightPanelStorePhases.ts | 1 + 13 files changed, 483 insertions(+), 152 deletions(-) create mode 100644 res/css/views/right_panel/_PinnedMessagesCard.scss delete mode 100644 res/css/views/rooms/_PinnedEventsPanel.scss create mode 100644 src/components/views/right_panel/PinnedMessagesCard.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index c8985cbb51..418b8f51c9 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -179,6 +179,7 @@ @import "./views/messages/_common_CryptoEvent.scss"; @import "./views/right_panel/_BaseCard.scss"; @import "./views/right_panel/_EncryptionInfo.scss"; +@import "./views/right_panel/_PinnedMessagesCard.scss"; @import "./views/right_panel/_RoomSummaryCard.scss"; @import "./views/right_panel/_UserInfo.scss"; @import "./views/right_panel/_VerificationPanel.scss"; @@ -203,7 +204,6 @@ @import "./views/rooms/_NewRoomIntro.scss"; @import "./views/rooms/_NotificationBadge.scss"; @import "./views/rooms/_PinnedEventTile.scss"; -@import "./views/rooms/_PinnedEventsPanel.scss"; @import "./views/rooms/_PresenceLabel.scss"; @import "./views/rooms/_ReplyPreview.scss"; @import "./views/rooms/_RoomBreadcrumbs.scss"; diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index 5515fe4060..e24100a9bb 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -98,6 +98,48 @@ limitations under the License. mask-position: center; } +$dot-size: 8px; +$pulse-color: $pinned-unread-color; + +.mx_RightPanel_pinnedMessagesButton { + &::before { + mask-image: url('$(res)/img/element-icons/room/pin.svg'); + mask-position: center; + } + + .mx_RightPanel_pinnedMessagesButton_unreadIndicator { + position: absolute; + right: 0; + top: 0; + margin: 4px; + width: $dot-size; + height: $dot-size; + border-radius: 50%; + transform: scale(1); + background: rgba($pulse-color, 1); + box-shadow: 0 0 0 0 rgba($pulse-color, 1); + animation: mx_RightPanel_indicator_pulse 2s infinite; + animation-iteration-count: 1; + } +} + +@keyframes mx_RightPanel_indicator_pulse { + 0% { + transform: scale(0.95); + box-shadow: 0 0 0 0 rgba($pulse-color, 0.7); + } + + 70% { + transform: scale(1); + box-shadow: 0 0 0 10px rgba($pulse-color, 0); + } + + 100% { + transform: scale(0.95); + box-shadow: 0 0 0 0 rgba($pulse-color, 0); + } +} + .mx_RightPanel_headerButton_highlight { &::before { background-color: $accent-color !important; diff --git a/res/css/views/right_panel/_PinnedMessagesCard.scss b/res/css/views/right_panel/_PinnedMessagesCard.scss new file mode 100644 index 0000000000..4cbc8b9abc --- /dev/null +++ b/res/css/views/right_panel/_PinnedMessagesCard.scss @@ -0,0 +1,241 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_PinnedMessagesCard { + .mx_BaseCard_header { + text-align: center; + margin-top: 20px; + + h2 { + font-weight: $font-semi-bold; + font-size: $font-18px; + margin: 12px 0 4px; + } + + .mx_RoomSummaryCard_alias { + font-size: $font-13px; + color: $secondary-fg-color; + } + + h2, .mx_RoomSummaryCard_alias { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + white-space: pre-wrap; + } + + .mx_RoomSummaryCard_avatar { + display: inline-flex; + + .mx_RoomSummaryCard_e2ee { + display: inline-block; + position: relative; + width: 54px; + height: 54px; + border-radius: 50%; + background-color: #737d8c; + margin-top: -3px; // alignment + margin-left: -10px; // overlap + border: 3px solid $dark-panel-bg-color; + + &::before { + content: ''; + position: absolute; + top: 13px; + left: 13px; + height: 28px; + width: 28px; + mask-size: cover; + mask-repeat: no-repeat; + mask-position: center; + mask-image: url('$(res)/img/e2e/disabled.svg'); + background-color: #ffffff; + } + } + + .mx_RoomSummaryCard_e2ee_normal { + background-color: #424446; + &::before { + mask-image: url('$(res)/img/e2e/normal.svg'); + } + } + + .mx_RoomSummaryCard_e2ee_verified { + background-color: #0dbd8b; + &::before { + mask-image: url('$(res)/img/e2e/verified.svg'); + } + } + + .mx_RoomSummaryCard_e2ee_warning { + background-color: #ff4b55; + &::before { + mask-image: url('$(res)/img/e2e/warning.svg'); + } + } + } + } + + .mx_RoomSummaryCard_aboutGroup { + .mx_RoomSummaryCard_Button { + padding-left: 44px; + + &::before { + content: ''; + position: absolute; + top: 8px; + left: 10px; + height: 24px; + width: 24px; + mask-repeat: no-repeat; + mask-position: center; + background-color: $icon-button-color; + } + } + } + + .mx_RoomSummaryCard_appsGroup { + .mx_RoomSummaryCard_Button { + // this button is special so we have to override some of the original styling + // as we will be applying it in its children + padding: 0; + height: auto; + color: $tertiary-fg-color; + + .mx_RoomSummaryCard_icon_app { + padding: 10px 48px 10px 12px; // based on typical mx_RoomSummaryCard_Button padding + text-overflow: ellipsis; + overflow: hidden; + + .mx_BaseAvatar_image { + vertical-align: top; + margin-right: 12px; + } + + span { + color: $primary-fg-color; + } + } + + .mx_RoomSummaryCard_app_pinToggle, + .mx_RoomSummaryCard_app_options { + position: absolute; + top: 0; + height: 100%; // to give bigger interactive zone + width: 24px; + padding: 12px 4px; + box-sizing: border-box; + min-width: 24px; // prevent flexbox crushing + + &:hover { + &::after { + content: ''; + position: absolute; + height: 24px; + width: 24px; + top: 8px; // equal to padding-top of parent + left: 0; + border-radius: 12px; + background-color: rgba(141, 151, 165, 0.1); + } + } + + &::before { + content: ''; + position: absolute; + height: 16px; + width: 16px; + mask-repeat: no-repeat; + mask-position: center; + mask-size: 16px; + background-color: $icon-button-color; + } + } + + .mx_RoomSummaryCard_app_pinToggle { + right: 24px; + + &::before { + mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); + } + } + + .mx_RoomSummaryCard_app_options { + right: 48px; + display: none; + + &::before { + mask-image: url('$(res)/img/element-icons/room/ellipsis.svg'); + } + } + + &.mx_RoomSummaryCard_Button_pinned { + &::after { + opacity: 0.2; + } + + .mx_RoomSummaryCard_app_pinToggle::before { + background-color: $accent-color; + } + } + + &:hover { + .mx_RoomSummaryCard_icon_app { + padding-right: 72px; + } + + .mx_RoomSummaryCard_app_options { + display: unset; + } + } + + &::before { + content: unset; + } + + &::after { + top: 8px; // re-align based on the height change + pointer-events: none; // pass through to the real button + } + } + } + + .mx_AccessibleButton_kind_link { + padding: 0; + margin-top: 12px; + margin-bottom: 12px; + font-size: $font-13px; + font-weight: $font-semi-bold; + } +} + +.mx_RoomSummaryCard_icon_people::before { + mask-image: url("$(res)/img/element-icons/room/members.svg"); +} + +.mx_RoomSummaryCard_icon_files::before { + mask-image: url('$(res)/img/element-icons/room/files.svg'); +} + +.mx_RoomSummaryCard_icon_share::before { + mask-image: url('$(res)/img/element-icons/room/share.svg'); +} + +.mx_RoomSummaryCard_icon_settings::before { + mask-image: url('$(res)/img/element-icons/settings.svg'); +} diff --git a/res/css/views/rooms/_PinnedEventsPanel.scss b/res/css/views/rooms/_PinnedEventsPanel.scss deleted file mode 100644 index 663d5bdf6e..0000000000 --- a/res/css/views/rooms/_PinnedEventsPanel.scss +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright 2017 Travis Ralston - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_PinnedEventsPanel { - border-top: 1px solid $primary-hairline-color; -} - -.mx_PinnedEventsPanel_body { - max-height: 300px; - overflow-y: auto; - padding-bottom: 15px; -} - -.mx_PinnedEventsPanel_header { - margin: 0; - padding-top: 8px; - padding-bottom: 15px; -} - -.mx_PinnedEventsPanel_cancel { - margin: 12px; - float: right; - display: inline-block; -} diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index a102dfcdd7..4142b0a2ef 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -277,45 +277,6 @@ limitations under the License. margin-top: 18px; } -.mx_RoomHeader_pinnedButton::before { - mask-image: url('$(res)/img/element-icons/room/pin.svg'); -} - -$dot-size: 8px; -$pulse-color: $pinned-unread-color; - -.mx_RoomHeader_pinsIndicatorUnread { - position: absolute; - right: 0; - top: 0; - margin: 4px; - width: $dot-size; - height: $dot-size; - border-radius: 50%; - transform: scale(1); - background: rgba($pulse-color, 1); - box-shadow: 0 0 0 0 rgba($pulse-color, 1); - animation: mx_RoomHeader_indicator_pulse 2s infinite; - animation-iteration-count: 1; -} - -@keyframes mx_RoomHeader_indicator_pulse { - 0% { - transform: scale(0.95); - box-shadow: 0 0 0 0 rgba($pulse-color, 0.7); - } - - 70% { - transform: scale(1); - box-shadow: 0 0 0 10px rgba($pulse-color, 0); - } - - 100% { - transform: scale(0.95); - box-shadow: 0 0 0 0 rgba($pulse-color, 0); - } -} - @media only screen and (max-width: 480px) { .mx_RoomHeader_wrapper { padding: 0; diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 15fa94e035..e3237d98a6 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -26,9 +26,9 @@ import dis from '../../dispatcher/dispatcher'; import RateLimitedFunc from '../../ratelimitedfunc'; import GroupStore from '../../stores/GroupStore'; import { - RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS, RIGHT_PANEL_SPACE_PHASES, + RightPanelPhases, } from "../../stores/RightPanelStorePhases"; import RightPanelStore from "../../stores/RightPanelStore"; import MatrixClientContext from "../../contexts/MatrixClientContext"; @@ -47,6 +47,7 @@ import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo"; import FilePanel from "./FilePanel"; import NotificationPanel from "./NotificationPanel"; import ResizeNotifier from "../../utils/ResizeNotifier"; +import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard"; interface IProps { room?: Room; // if showing panels for a given room, this is set @@ -294,7 +295,13 @@ export default class RightPanel extends React.Component { break; case RightPanelPhases.NotificationPanel: - panel = ; + if (SettingsStore.getValue("feature_pinning")) { + panel = ; + } + break; + + case RightPanelPhases.PinnedMessages: + panel = ; break; case RightPanelPhases.FilePanel: diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index d822b6a839..e8ad1d7e45 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -155,7 +155,6 @@ export interface IState { canPeek: boolean; showApps: boolean; isPeeking: boolean; - showingPinned: boolean; showReadReceipts: boolean; showRightPanel: boolean; // error object, as from the matrix client/server API @@ -232,7 +231,6 @@ export default class RoomView extends React.Component { canPeek: false, showApps: false, isPeeking: false, - showingPinned: false, showReadReceipts: true, showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, joining: false, @@ -327,7 +325,6 @@ export default class RoomView extends React.Component { forwardingEvent: RoomViewStore.getForwardingEvent(), // we should only peek once we have a ready client shouldPeek: this.state.matrixClientIsReady && RoomViewStore.shouldPeek(), - showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", roomId), showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId), wasContextSwitch: RoomViewStore.getWasContextSwitch(), }; @@ -1375,13 +1372,6 @@ export default class RoomView extends React.Component { return ret; } - private onPinnedClick = () => { - const nowShowingPinned = !this.state.showingPinned; - const roomId = this.state.room.roomId; - this.setState({showingPinned: nowShowingPinned, searching: false}); - SettingsStore.setValue("PinnedEvents.isOpen", roomId, SettingLevel.ROOM_DEVICE, nowShowingPinned); - }; - private onCallPlaced = (type: PlaceCallType) => { dis.dispatch({ action: 'place_call', @@ -1498,7 +1488,6 @@ export default class RoomView extends React.Component { private onSearchClick = () => { this.setState({ searching: !this.state.searching, - showingPinned: false, }); }; @@ -1825,9 +1814,6 @@ export default class RoomView extends React.Component { } else if (showRoomUpgradeBar) { aux = ; hideCancel = true; - } else if (this.state.showingPinned) { - hideCancel = true; // has own cancel - aux = ; } else if (myMembership !== "join") { // We do have a room object for this room, but we're not currently in it. // We may have a 3rd party invite to it. @@ -2045,7 +2031,6 @@ export default class RoomView extends React.Component { inRoom={myMembership === 'join'} onSearchClick={this.onSearchClick} onSettingsClick={this.onSettingsClick} - onPinnedClick={this.onPinnedClick} onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null} onForgetClick={(myMembership === "leave") ? this.onForgetClick : null} onLeaveClick={(myMembership === "join") ? this.onLeaveClick : null} diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx new file mode 100644 index 0000000000..46625b6e07 --- /dev/null +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -0,0 +1,145 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, {useCallback, useContext, useEffect, useState} from "react"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { EventType } from 'matrix-js-sdk/src/@types/event'; + +import { _t } from "../../../languageHandler"; +import BaseCard from "./BaseCard"; +import Spinner from "../elements/Spinner"; +import MatrixClientContext from "../../../contexts/MatrixClientContext"; +import { useEventEmitter } from "../../../hooks/useEventEmitter"; +import PinningUtils from "../../../utils/PinningUtils"; +import { useAsyncMemo } from "../../../hooks/useAsyncMemo"; +import PinnedEventTile from "../rooms/PinnedEventTile"; + +interface IProps { + room: Room; + onClose(): void; +} + +export const usePinnedEvents = (room: Room): string[] => { + const [pinnedEvents, setPinnedEvents] = useState([]); + + const update = useCallback((ev?: MatrixEvent) => { + if (!room) return; + if (ev && ev.getType() !== EventType.RoomPinnedEvents) return; + setPinnedEvents(room.currentState.getStateEvents(EventType.RoomPinnedEvents, "")?.getContent()?.pinned || []); + }, [room]); + + useEventEmitter(room.currentState, "RoomState.events", update); + useEffect(() => { + update(); + return () => { + setPinnedEvents([]); + }; + }, [update]); + return pinnedEvents; +}; + +const ReadPinsEventId = "im.vector.room.read_pins"; +const ReadPinsNumIds = 10; + +export const useReadPinnedEvents = (room: Room): Set => { + const [readPinnedEvents, setReadPinnedEvents] = useState>(new Set()); + + const update = useCallback((ev?: MatrixEvent) => { + if (!room) return; + if (ev && ev.getType() !== ReadPinsEventId) return; + const readPins = room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids; + setReadPinnedEvents(new Set(readPins || [])); + }, [room]); + + useEventEmitter(room, "Room.accountData", update); + useEffect(() => { + update(); + return () => { + setReadPinnedEvents(new Set()); + }; + }, [update]); + return readPinnedEvents; +}; + +const PinnedMessagesCard = ({ room, onClose }: IProps) => { + const cli = useContext(MatrixClientContext); + const pinnedEventIds = usePinnedEvents(room); + const readPinnedEvents = useReadPinnedEvents(room); + + useEffect(() => { + const newlyRead = pinnedEventIds.filter(id => !readPinnedEvents.has(id)); + if (newlyRead.length > 0) { + // Only keep the last N event IDs to avoid infinite growth + cli.setRoomAccountData(room.roomId, ReadPinsEventId, { + event_ids: [ + ...newlyRead.reverse(), + ...readPinnedEvents, + ].splice(0, ReadPinsNumIds), + }); + } + }, [cli, room.roomId, pinnedEventIds, readPinnedEvents]); + + const pinnedEvents = useAsyncMemo(() => { + const promises = pinnedEventIds.map(async eventId => { + const timelineSet = room.getUnfilteredTimelineSet(); + const localEvent = timelineSet?.getTimelineForEvent(eventId)?.getEvents().find(e => e.getId() === eventId); + if (localEvent) return localEvent; + + try { + const evJson = await cli.fetchRoomEvent(room.roomId, eventId); + const event = new MatrixEvent(evJson); + if (event.isEncrypted()) { + await cli.decryptEventIfNeeded(event); // TODO await? + } + if (event && PinningUtils.isPinnable(event)) { + return event; + } + } catch (err) { + console.error("Error looking up pinned event " + eventId + " in room " + room.roomId); + console.error(err); + } + return null; + }); + + return Promise.all(promises); + }, [cli, room, pinnedEventIds], null); + + let content; + if (!pinnedEvents) { + content = ; + } else if (pinnedEvents.length > 0) { + content = pinnedEvents.filter(Boolean).map(ev => ( + {}} + /> + )); + } else { + content =
    +

    {_t("You’re all caught up")}

    +

    {_t("You have no visible notifications.")}

    +
    ; + } + + return + { content } + ; +}; + +export default PinnedMessagesCard; diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index 2a7a9e191b..3d3f33be00 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -18,7 +18,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React from "react"; +import { Room } from "matrix-js-sdk/src/models/room"; + import {_t} from '../../../languageHandler'; import HeaderButton from './HeaderButton'; import HeaderButtons, {HeaderKind} from './HeaderButtons'; @@ -27,6 +29,8 @@ import {Action} from "../../../dispatcher/actions"; import {ActionPayload} from "../../../dispatcher/payloads"; import RightPanelStore from "../../../stores/RightPanelStore"; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import {useSettingValue} from "../../../hooks/useSettings"; +import { useReadPinnedEvents, usePinnedEvents } from './PinnedMessagesCard'; const ROOM_INFO_PHASES = [ RightPanelPhases.RoomSummary, @@ -38,9 +42,35 @@ const ROOM_INFO_PHASES = [ RightPanelPhases.Room3pidMemberInfo, ]; +const PinnedMessagesHeaderButton = ({ room, isHighlighted, onClick }) => { + const pinningEnabled = useSettingValue("feature_pinning"); + const pinnedEvents = usePinnedEvents(pinningEnabled && room); + const readPinnedEvents = useReadPinnedEvents(pinningEnabled && room); + if (!pinningEnabled) return null; + + let unreadIndicator; + if (pinnedEvents.some(id => !readPinnedEvents.has(id))) { + unreadIndicator =
    ; + } + + return + { unreadIndicator } + ; +}; + +interface IProps { + room?: Room; +} + @replaceableComponent("views.right_panel.RoomHeaderButtons") -export default class RoomHeaderButtons extends HeaderButtons { - constructor(props) { +export default class RoomHeaderButtons extends HeaderButtons { + constructor(props: IProps) { super(props, HeaderKind.Room); } @@ -80,8 +110,18 @@ export default class RoomHeaderButtons extends HeaderButtons { this.setPhase(RightPanelPhases.NotificationPanel); }; + private onPinnedMessagesClicked = () => { + // This toggles for us, if needed + this.setPhase(RightPanelPhases.PinnedMessages); + }; + public renderButtons() { return <> + { } else { setPowerLevels({}); } - return () => { - setPowerLevels({}); - }; }, [room]); useEventEmitter(cli, "RoomState.events", update); diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 7eeb5875ef..217b3ea5c2 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -40,7 +40,6 @@ export default class RoomHeader extends React.Component { oobData: PropTypes.object, inRoom: PropTypes.bool, onSettingsClick: PropTypes.func, - onPinnedClick: PropTypes.func, onSearchClick: PropTypes.func, onLeaveClick: PropTypes.func, onCancelClick: PropTypes.func, @@ -59,14 +58,12 @@ export default class RoomHeader extends React.Component { componentDidMount() { const cli = MatrixClientPeg.get(); cli.on("RoomState.events", this._onRoomStateEvents); - cli.on("Room.accountData", this._onRoomAccountData); } componentWillUnmount() { const cli = MatrixClientPeg.get(); if (cli) { cli.removeListener("RoomState.events", this._onRoomStateEvents); - cli.removeListener("Room.accountData", this._onRoomAccountData); } } @@ -79,41 +76,14 @@ export default class RoomHeader extends React.Component { this._rateLimitedUpdate(); }; - _onRoomAccountData = (event, room) => { - if (!this.props.room || room.roomId !== this.props.room.roomId) return; - if (event.getType() !== "im.vector.room.read_pins") return; - - this._rateLimitedUpdate(); - }; - _rateLimitedUpdate = new RateLimitedFunc(function() { /* eslint-disable babel/no-invalid-this */ this.forceUpdate(); }, 500); - _hasUnreadPins() { - const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", ''); - if (!currentPinEvent) return false; - if (currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0) { - return false; // no pins == nothing to read - } - - const readPinsEvent = this.props.room.getAccountData("im.vector.room.read_pins"); - if (readPinsEvent && readPinsEvent.getContent()) { - const readStateEvents = readPinsEvent.getContent().event_ids || []; - if (readStateEvents) { - return !readStateEvents.includes(currentPinEvent.getId()); - } - } - - // There's pins, and we haven't read any of them - return true; - } - render() { let searchStatus = null; let cancelButton = null; - let pinnedEventsButton = null; if (this.props.onCancelClick) { cancelButton = ; @@ -174,22 +144,6 @@ export default class RoomHeader extends React.Component { />; } - if (this.props.onPinnedClick && SettingsStore.getValue('feature_pinning')) { - let pinsIndicator = null; - if (this._hasUnreadPins()) { - pinsIndicator = (
    ); - } - - pinnedEventsButton = - - { pinsIndicator } - ; - } - let forgetButton; if (this.props.onForgetClick) { forgetButton = @@ -239,7 +193,6 @@ export default class RoomHeader extends React.Component {
    { videoCallButton } { voiceCallButton } - { pinnedEventsButton } { forgetButton } { appsButton } { searchButton } @@ -256,7 +209,7 @@ export default class RoomHeader extends React.Component { { topicElement } { cancelButton } { rightRow } - +
    ); diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 6ff14c16b5..155d039572 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -601,10 +601,6 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td('Enable widget screenshots on supported widgets'), default: false, }, - "PinnedEvents.isOpen": { - supportedLevels: [SettingLevel.ROOM_DEVICE], - default: false, - }, "promptBeforeInviteUnknownUsers": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Prompt before sending invites to potentially invalid matrix IDs'), diff --git a/src/stores/RightPanelStorePhases.ts b/src/stores/RightPanelStorePhases.ts index aea78c7460..0d96f92945 100644 --- a/src/stores/RightPanelStorePhases.ts +++ b/src/stores/RightPanelStorePhases.ts @@ -24,6 +24,7 @@ export enum RightPanelPhases { EncryptionPanel = 'EncryptionPanel', RoomSummary = 'RoomSummary', Widget = 'Widget', + PinnedMessages = "PinnedMessages", Room3pidMemberInfo = 'Room3pidMemberInfo', // Group stuff From c1f397dcf7bfaad0e894b4d949b94094108813b6 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 25 May 2021 16:20:23 +0100 Subject: [PATCH 069/198] delint --- src/components/structures/RoomView.tsx | 2 -- src/components/views/right_panel/HeaderButtons.tsx | 2 +- src/contexts/RoomContext.ts | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index e8ad1d7e45..482f4a45a7 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -54,7 +54,6 @@ import RoomContext from "../../contexts/RoomContext"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import { E2EStatus, shieldStatusForRoom } from '../../utils/ShieldUtils'; import { Action } from "../../dispatcher/actions"; -import { SettingLevel } from "../../settings/SettingLevel"; import { IMatrixClientCreds } from "../../MatrixClientPeg"; import ScrollPanel from "./ScrollPanel"; import TimelinePanel from "./TimelinePanel"; @@ -63,7 +62,6 @@ import RoomPreviewBar from "../views/rooms/RoomPreviewBar"; import ForwardMessage from "../views/rooms/ForwardMessage"; import SearchBar from "../views/rooms/SearchBar"; import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar"; -import PinnedEventsPanel from "../views/rooms/PinnedEventsPanel"; import AuxPanel from "../views/rooms/AuxPanel"; import RoomHeader from "../views/rooms/RoomHeader"; import { XOR } from "../../@types/common"; diff --git a/src/components/views/right_panel/HeaderButtons.tsx b/src/components/views/right_panel/HeaderButtons.tsx index 281d7edd8b..0c7d9ee299 100644 --- a/src/components/views/right_panel/HeaderButtons.tsx +++ b/src/components/views/right_panel/HeaderButtons.tsx @@ -43,7 +43,7 @@ interface IState { interface IProps {} @replaceableComponent("views.right_panel.HeaderButtons") -export default abstract class HeaderButtons

    extends React.Component { +export default abstract class HeaderButtons

    extends React.Component { private storeToken: EventSubscription; private dispatcherRef: string; diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts index 30ff74b071..7f2c0bb157 100644 --- a/src/contexts/RoomContext.ts +++ b/src/contexts/RoomContext.ts @@ -31,7 +31,6 @@ const RoomContext = createContext({ canPeek: false, showApps: false, isPeeking: false, - showingPinned: false, showReadReceipts: true, showRightPanel: true, joining: false, From fd74a946e087b0ae42dbc74cbd4a30ee5386c895 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 25 May 2021 17:24:43 +0100 Subject: [PATCH 070/198] add header --- .../views/right_panel/PinnedMessagesCard.tsx | 6 +- .../views/rooms/PinnedEventsPanel.js | 145 ------------------ src/i18n/strings/en_EN.json | 10 +- 3 files changed, 10 insertions(+), 151 deletions(-) delete mode 100644 src/components/views/rooms/PinnedEventsPanel.js diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 46625b6e07..579b12c3cf 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -137,7 +137,11 @@ const PinnedMessagesCard = ({ room, onClose }: IProps) => {

    ; } - return + return { _t("Pinned") }} + className="mx_NotificationPanel" + onClose={onClose} + > { content } ; }; diff --git a/src/components/views/rooms/PinnedEventsPanel.js b/src/components/views/rooms/PinnedEventsPanel.js deleted file mode 100644 index 4b310dbbca..0000000000 --- a/src/components/views/rooms/PinnedEventsPanel.js +++ /dev/null @@ -1,145 +0,0 @@ -/* -Copyright 2017 Travis Ralston -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from "react"; -import PropTypes from 'prop-types'; -import {MatrixClientPeg} from "../../../MatrixClientPeg"; -import AccessibleButton from "../elements/AccessibleButton"; -import PinnedEventTile from "./PinnedEventTile"; -import { _t } from '../../../languageHandler'; -import PinningUtils from "../../../utils/PinningUtils"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; - -@replaceableComponent("views.rooms.PinnedEventsPanel") -export default class PinnedEventsPanel extends React.Component { - static propTypes = { - // The Room from the js-sdk we're going to show pinned events for - room: PropTypes.object.isRequired, - - onCancelClick: PropTypes.func, - }; - - state = { - loading: true, - }; - - componentDidMount() { - this._updatePinnedMessages(); - MatrixClientPeg.get().on("RoomState.events", this._onStateEvent); - } - - componentWillUnmount() { - if (MatrixClientPeg.get()) { - MatrixClientPeg.get().removeListener("RoomState.events", this._onStateEvent); - } - } - - _onStateEvent = ev => { - if (ev.getRoomId() === this.props.room.roomId && ev.getType() === "m.room.pinned_events") { - this._updatePinnedMessages(); - } - }; - - _updatePinnedMessages = () => { - const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", ""); - if (!pinnedEvents || !pinnedEvents.getContent().pinned) { - this.setState({ loading: false, pinned: [] }); - } else { - const promises = []; - const cli = MatrixClientPeg.get(); - - pinnedEvents.getContent().pinned.map((eventId) => { - promises.push(cli.getEventTimeline(this.props.room.getUnfilteredTimelineSet(), eventId, 0).then( - (timeline) => { - const event = timeline.getEvents().find((e) => e.getId() === eventId); - return {eventId, timeline, event}; - }).catch((err) => { - console.error("Error looking up pinned event " + eventId + " in room " + this.props.room.roomId); - console.error(err); - return null; // return lack of context to avoid unhandled errors - })); - }); - - Promise.all(promises).then((contexts) => { - // Filter out the messages before we try to render them - const pinned = contexts.filter((context) => PinningUtils.isPinnable(context.event)); - - this.setState({ loading: false, pinned }); - }); - } - - this._updateReadState(); - }; - - _updateReadState() { - const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", ""); - if (!pinnedEvents) return; // nothing to read - - let readStateEvents = []; - const readPinsEvent = this.props.room.getAccountData("im.vector.room.read_pins"); - if (readPinsEvent && readPinsEvent.getContent()) { - readStateEvents = readPinsEvent.getContent().event_ids || []; - } - - if (!readStateEvents.includes(pinnedEvents.getId())) { - readStateEvents.push(pinnedEvents.getId()); - - // Only keep the last 10 event IDs to avoid infinite growth - readStateEvents = readStateEvents.reverse().splice(0, 10).reverse(); - - MatrixClientPeg.get().setRoomAccountData(this.props.room.roomId, "im.vector.room.read_pins", { - event_ids: readStateEvents, - }); - } - } - - _getPinnedTiles() { - if (this.state.pinned.length === 0) { - return (
    { _t("No pinned messages.") }
    ); - } - - return this.state.pinned.map((context) => { - return ( - - ); - }); - } - - render() { - let tiles =
    { _t("Loading...") }
    ; - if (this.state && !this.state.loading) { - tiles = this._getPinnedTiles(); - } - - return ( -
    -
    - - - -

    { _t("Pinned Messages") }

    - { tiles } -
    -
    - ); - } -} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7ceb039822..0aebfc48ce 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1508,9 +1508,6 @@ "Invite to just this room": "Invite to just this room", "Add a photo, so people can easily spot your room.": "Add a photo, so people can easily spot your room.", "This is the start of .": "This is the start of .", - "No pinned messages.": "No pinned messages.", - "Loading...": "Loading...", - "Pinned Messages": "Pinned Messages", "Unpin Message": "Unpin Message", "Jump to message": "Jump to message", "%(duration)ss": "%(duration)ss", @@ -1718,6 +1715,10 @@ "The homeserver the user you’re verifying is connected to": "The homeserver the user you’re verifying is connected to", "Yours, or the other users’ internet connection": "Yours, or the other users’ internet connection", "Yours, or the other users’ session": "Yours, or the other users’ session", + "You’re all caught up": "You’re all caught up", + "You have no visible notifications.": "You have no visible notifications.", + "Pinned": "Pinned", + "Pinned Messages": "Pinned Messages", "Room Info": "Room Info", "You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets", "Unpin": "Unpin", @@ -1896,6 +1897,7 @@ "Add rooms to this community": "Add rooms to this community", "Filter community rooms": "Filter community rooms", "Something went wrong when trying to get your communities.": "Something went wrong when trying to get your communities.", + "Loading...": "Loading...", "Display your community flair in rooms configured to show it.": "Display your community flair in rooms configured to show it.", "You're not currently a member of any communities.": "You're not currently a member of any communities.", "Frequently Used": "Frequently Used", @@ -2626,8 +2628,6 @@ "Create a new community": "Create a new community", "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.", "Communities are changing to Spaces": "Communities are changing to Spaces", - "You’re all caught up": "You’re all caught up", - "You have no visible notifications.": "You have no visible notifications.", "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.", "%(brand)s failed to get the public room list.": "%(brand)s failed to get the public room list.", "The homeserver may be unavailable or overloaded.": "The homeserver may be unavailable or overloaded.", From 27ad90760d117e6c8c203d04669c715d564a01e5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 26 May 2021 13:51:17 +0100 Subject: [PATCH 071/198] Iterate pinned messages --- .../right_panel/_PinnedMessagesCard.scss | 222 +----------------- res/css/views/rooms/_PinnedEventTile.scss | 122 ++++++---- .../views/right_panel/PinnedMessagesCard.tsx | 61 +++-- .../views/right_panel/RoomHeaderButtons.tsx | 2 +- .../views/rooms/PinnedEventTile.tsx | 132 +++++------ src/i18n/strings/en_EN.json | 6 +- src/stores/RightPanelStorePhases.ts | 1 + 7 files changed, 185 insertions(+), 361 deletions(-) diff --git a/res/css/views/right_panel/_PinnedMessagesCard.scss b/res/css/views/right_panel/_PinnedMessagesCard.scss index 4cbc8b9abc..b6b8238bed 100644 --- a/res/css/views/right_panel/_PinnedMessagesCard.scss +++ b/res/css/views/right_panel/_PinnedMessagesCard.scss @@ -15,227 +15,21 @@ limitations under the License. */ .mx_PinnedMessagesCard { + padding-top: 0; + .mx_BaseCard_header { text-align: center; - margin-top: 20px; + margin-top: 0; + border-bottom: 1px solid $menu-border-color; - h2 { + > h2 { font-weight: $font-semi-bold; font-size: $font-18px; - margin: 12px 0 4px; + margin: 8px 0; } - .mx_RoomSummaryCard_alias { - font-size: $font-13px; - color: $secondary-fg-color; - } - - h2, .mx_RoomSummaryCard_alias { - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; - white-space: pre-wrap; - } - - .mx_RoomSummaryCard_avatar { - display: inline-flex; - - .mx_RoomSummaryCard_e2ee { - display: inline-block; - position: relative; - width: 54px; - height: 54px; - border-radius: 50%; - background-color: #737d8c; - margin-top: -3px; // alignment - margin-left: -10px; // overlap - border: 3px solid $dark-panel-bg-color; - - &::before { - content: ''; - position: absolute; - top: 13px; - left: 13px; - height: 28px; - width: 28px; - mask-size: cover; - mask-repeat: no-repeat; - mask-position: center; - mask-image: url('$(res)/img/e2e/disabled.svg'); - background-color: #ffffff; - } - } - - .mx_RoomSummaryCard_e2ee_normal { - background-color: #424446; - &::before { - mask-image: url('$(res)/img/e2e/normal.svg'); - } - } - - .mx_RoomSummaryCard_e2ee_verified { - background-color: #0dbd8b; - &::before { - mask-image: url('$(res)/img/e2e/verified.svg'); - } - } - - .mx_RoomSummaryCard_e2ee_warning { - background-color: #ff4b55; - &::before { - mask-image: url('$(res)/img/e2e/warning.svg'); - } - } + .mx_BaseCard_close { + margin-right: 6px; } } - - .mx_RoomSummaryCard_aboutGroup { - .mx_RoomSummaryCard_Button { - padding-left: 44px; - - &::before { - content: ''; - position: absolute; - top: 8px; - left: 10px; - height: 24px; - width: 24px; - mask-repeat: no-repeat; - mask-position: center; - background-color: $icon-button-color; - } - } - } - - .mx_RoomSummaryCard_appsGroup { - .mx_RoomSummaryCard_Button { - // this button is special so we have to override some of the original styling - // as we will be applying it in its children - padding: 0; - height: auto; - color: $tertiary-fg-color; - - .mx_RoomSummaryCard_icon_app { - padding: 10px 48px 10px 12px; // based on typical mx_RoomSummaryCard_Button padding - text-overflow: ellipsis; - overflow: hidden; - - .mx_BaseAvatar_image { - vertical-align: top; - margin-right: 12px; - } - - span { - color: $primary-fg-color; - } - } - - .mx_RoomSummaryCard_app_pinToggle, - .mx_RoomSummaryCard_app_options { - position: absolute; - top: 0; - height: 100%; // to give bigger interactive zone - width: 24px; - padding: 12px 4px; - box-sizing: border-box; - min-width: 24px; // prevent flexbox crushing - - &:hover { - &::after { - content: ''; - position: absolute; - height: 24px; - width: 24px; - top: 8px; // equal to padding-top of parent - left: 0; - border-radius: 12px; - background-color: rgba(141, 151, 165, 0.1); - } - } - - &::before { - content: ''; - position: absolute; - height: 16px; - width: 16px; - mask-repeat: no-repeat; - mask-position: center; - mask-size: 16px; - background-color: $icon-button-color; - } - } - - .mx_RoomSummaryCard_app_pinToggle { - right: 24px; - - &::before { - mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); - } - } - - .mx_RoomSummaryCard_app_options { - right: 48px; - display: none; - - &::before { - mask-image: url('$(res)/img/element-icons/room/ellipsis.svg'); - } - } - - &.mx_RoomSummaryCard_Button_pinned { - &::after { - opacity: 0.2; - } - - .mx_RoomSummaryCard_app_pinToggle::before { - background-color: $accent-color; - } - } - - &:hover { - .mx_RoomSummaryCard_icon_app { - padding-right: 72px; - } - - .mx_RoomSummaryCard_app_options { - display: unset; - } - } - - &::before { - content: unset; - } - - &::after { - top: 8px; // re-align based on the height change - pointer-events: none; // pass through to the real button - } - } - } - - .mx_AccessibleButton_kind_link { - padding: 0; - margin-top: 12px; - margin-bottom: 12px; - font-size: $font-13px; - font-weight: $font-semi-bold; - } -} - -.mx_RoomSummaryCard_icon_people::before { - mask-image: url("$(res)/img/element-icons/room/members.svg"); -} - -.mx_RoomSummaryCard_icon_files::before { - mask-image: url('$(res)/img/element-icons/room/files.svg'); -} - -.mx_RoomSummaryCard_icon_share::before { - mask-image: url('$(res)/img/element-icons/room/share.svg'); -} - -.mx_RoomSummaryCard_icon_settings::before { - mask-image: url('$(res)/img/element-icons/settings.svg'); } diff --git a/res/css/views/rooms/_PinnedEventTile.scss b/res/css/views/rooms/_PinnedEventTile.scss index 030a76674a..8e96f3caeb 100644 --- a/res/css/views/rooms/_PinnedEventTile.scss +++ b/res/css/views/rooms/_PinnedEventTile.scss @@ -16,62 +16,90 @@ limitations under the License. .mx_PinnedEventTile { min-height: 40px; - margin-bottom: 5px; width: 100%; - border-radius: 5px; // for the hover -} + padding: 0 4px 12px; -.mx_PinnedEventTile:hover { - background-color: $event-selected-color; -} + display: grid; + grid-template-areas: "avatar name remove" + "content content content" + "footer footer footer"; + grid-template-rows: max-content auto max-content; + grid-template-columns: 24px auto 24px; + grid-row-gap: 12px; + grid-column-gap: 8px; -.mx_PinnedEventTile .mx_PinnedEventTile_sender, -.mx_PinnedEventTile .mx_PinnedEventTile_timestamp { - color: #868686; - font-size: 0.8em; - vertical-align: top; - display: inline-block; - padding-bottom: 3px; -} + & + .mx_PinnedEventTile { + padding: 12px 4px; + border-top: 1px solid $menu-border-color; + } -.mx_PinnedEventTile .mx_PinnedEventTile_timestamp { - padding-left: 15px; - display: none; -} + .mx_PinnedEventTile_senderAvatar { + grid-area: avatar; + } -.mx_PinnedEventTile .mx_PinnedEventTile_senderAvatar .mx_BaseAvatar { - float: left; - margin-right: 10px; -} + .mx_PinnedEventTile_sender { + grid-area: name; + font-weight: $font-semi-bold; + font-size: $font-15px; + line-height: $font-24px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } -.mx_PinnedEventTile_actions { - float: right; - margin-right: 10px; - display: none; -} + .mx_PinnedEventTile_unpinButton { + visibility: hidden; + grid-area: remove; + position: relative; + width: 24px; + height: 24px; + border-radius: 8px; -.mx_PinnedEventTile:hover .mx_PinnedEventTile_timestamp { - display: inline-block; -} + &:hover { + background-color: $roomheader-addroom-bg-color; + } -.mx_PinnedEventTile:hover .mx_PinnedEventTile_actions { - display: block; -} + &::before { + content: ""; + position: absolute; + //top: 0; + //left: 0; + height: inherit; + width: inherit; + background: $secondary-fg-color; + mask-position: center; + mask-size: 8px; + mask-repeat: no-repeat; + mask-image: url('$(res)/img/image-view/close.svg'); + } + } -.mx_PinnedEventTile_unpinButton { - display: inline-block; - cursor: pointer; - margin-left: 10px; -} + .mx_PinnedEventTile_message { + grid-area: content; + } -.mx_PinnedEventTile_gotoButton { - display: inline-block; - font-size: 0.7em; // Smaller text to avoid conflicting with the layout -} + .mx_PinnedEventTile_footer { + grid-area: footer; + font-size: 10px; + line-height: 12px; -.mx_PinnedEventTile_message { - margin-left: 50px; - position: relative; - top: 0; - left: 0; + .mx_PinnedEventTile_timestamp { + font-size: inherit; + line-height: inherit; + color: $secondary-fg-color; + } + + .mx_AccessibleButton_kind_link { + padding: 0; + margin-left: 12px; + font-size: inherit; + line-height: inherit; + } + } + + &:hover { + .mx_PinnedEventTile_unpinButton { + visibility: visible; + } + } } diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 579b12c3cf..48be8cd706 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -16,6 +16,7 @@ limitations under the License. import React, {useCallback, useContext, useEffect, useState} from "react"; import { Room } from "matrix-js-sdk/src/models/room"; +import { RoomState } from "matrix-js-sdk/src/models/room-state"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { EventType } from 'matrix-js-sdk/src/@types/event'; @@ -42,7 +43,7 @@ export const usePinnedEvents = (room: Room): string[] => { setPinnedEvents(room.currentState.getStateEvents(EventType.RoomPinnedEvents, "")?.getContent()?.pinned || []); }, [room]); - useEventEmitter(room.currentState, "RoomState.events", update); + useEventEmitter(room?.currentState, "RoomState.events", update); useEffect(() => { update(); return () => { @@ -53,7 +54,6 @@ export const usePinnedEvents = (room: Room): string[] => { }; const ReadPinsEventId = "im.vector.room.read_pins"; -const ReadPinsNumIds = 10; export const useReadPinnedEvents = (room: Room): Set => { const [readPinnedEvents, setReadPinnedEvents] = useState>(new Set()); @@ -75,20 +75,36 @@ export const useReadPinnedEvents = (room: Room): Set => { return readPinnedEvents; }; +const useRoomState = (room: Room, mapper: (state: RoomState) => T): T => { + const [value, setValue] = useState(room ? mapper(room.currentState) : undefined); + + const update = useCallback(() => { + if (!room) return; + setValue(mapper(room.currentState)); + }, [room, mapper]); + + useEventEmitter(room?.currentState, "RoomState.events", update); + useEffect(() => { + update(); + return () => { + setValue(undefined); + }; + }, [update]); + return value; +}; + const PinnedMessagesCard = ({ room, onClose }: IProps) => { const cli = useContext(MatrixClientContext); + const canUnpin = useRoomState(room, state => state.mayClientSendStateEvent(EventType.RoomPinnedEvents, cli)); const pinnedEventIds = usePinnedEvents(room); const readPinnedEvents = useReadPinnedEvents(room); useEffect(() => { const newlyRead = pinnedEventIds.filter(id => !readPinnedEvents.has(id)); if (newlyRead.length > 0) { - // Only keep the last N event IDs to avoid infinite growth + // clear out any read pinned events which no longer are pinned cli.setRoomAccountData(room.roomId, ReadPinsEventId, { - event_ids: [ - ...newlyRead.reverse(), - ...readPinnedEvents, - ].splice(0, ReadPinsNumIds), + event_ids: pinnedEventIds, }); } }, [cli, room.roomId, pinnedEventIds, readPinnedEvents]); @@ -122,24 +138,35 @@ const PinnedMessagesCard = ({ room, onClose }: IProps) => { if (!pinnedEvents) { content = ; } else if (pinnedEvents.length > 0) { - content = pinnedEvents.filter(Boolean).map(ev => ( - {}} - /> + let onUnpinClicked; + if (canUnpin) { + onUnpinClicked = async (event: MatrixEvent) => { + const pinnedEvents = room.currentState.getStateEvents(EventType.RoomPinnedEvents, ""); + if (pinnedEvents?.getContent()?.pinned) { + const pinned = pinnedEvents.getContent().pinned; + const index = pinned.indexOf(event.getId()); + if (index !== -1) { + pinned.splice(index, 1); + await cli.sendStateEvent(room.roomId, EventType.RoomPinnedEvents, { pinned }, ""); + } + } + }; + } + + // show them in reverse, with latest pinned at the top + content = pinnedEvents.filter(Boolean).reverse().map(ev => ( + )); } else { - content =
    + content =

    {_t("You’re all caught up")}

    {_t("You have no visible notifications.")}

    ; } return { _t("Pinned") }} - className="mx_NotificationPanel" + header={

    { _t("Pinned messages") }

    } + className="mx_PinnedMessagesCard" onClose={onClose} > { content } diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index 3d3f33be00..7ff7207fb6 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -55,7 +55,7 @@ const PinnedMessagesHeaderButton = ({ room, isHighlighted, onClick }) => { return { + static contextType = MatrixClientContext; + private onTileClicked = () => { dis.dispatch({ action: 'view_room', - event_id: this.props.mxEvent.getId(), + event_id: this.props.event.getId(), highlighted: true, - room_id: this.props.mxEvent.getRoomId(), + room_id: this.props.event.getRoomId(), }); }; - private onUnpinClicked = () => { - const pinnedEvents = this.props.mxRoom.currentState.getStateEvents("m.room.pinned_events", ""); - if (!pinnedEvents || !pinnedEvents.getContent().pinned) { - // Nothing to do: already unpinned - if (this.props.onUnpinned) this.props.onUnpinned(); - } else { - const pinned = pinnedEvents.getContent().pinned; - const index = pinned.indexOf(this.props.mxEvent.getId()); - if (index !== -1) { - pinned.splice(index, 1); - MatrixClientPeg.get().sendStateEvent(this.props.mxRoom.roomId, 'm.room.pinned_events', {pinned}, '') - .then(() => { - if (this.props.onUnpinned) this.props.onUnpinned(); - }); - } else if (this.props.onUnpinned) this.props.onUnpinned(); - } - }; - - private canUnpin() { - return this.props.mxRoom.currentState.mayClientSendStateEvent('m.room.pinned_events', MatrixClientPeg.get()); - } - render() { - const sender = this.props.mxEvent.getSender(); - // Get the latest sender profile rather than historical - const senderProfile = this.props.mxRoom.getMember(sender); - const avatarSize = 40; + const sender = this.props.event.getSender(); + const senderProfile = this.props.room.getMember(sender); let unpinButton = null; - if (this.canUnpin()) { + if (this.props.onUnpinClicked) { unpinButton = ( - - {_t('Unpin - + ); } - return ( -
    -
    - - { _t("Jump to message") } - - { unpinButton } -
    + return
    + - - - - - { senderProfile ? senderProfile.name : sender } - - - { formatFullDate(new Date(this.props.mxEvent.getTs())) } - -
    - {}} // we need to give this, apparently - /> -
    + + { senderProfile?.name || sender } + + + { unpinButton } + +
    + {}} // we need to give this, apparently + />
    - ); + +
    + + { formatDate(new Date(this.props.event.getTs())) } + + + + { _t("View message") } + +
    +
    ; } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 0aebfc48ce..d5b37a7d2d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1509,7 +1509,7 @@ "Add a photo, so people can easily spot your room.": "Add a photo, so people can easily spot your room.", "This is the start of .": "This is the start of .", "Unpin Message": "Unpin Message", - "Jump to message": "Jump to message", + "View message": "View message", "%(duration)ss": "%(duration)ss", "%(duration)sm": "%(duration)sm", "%(duration)sh": "%(duration)sh", @@ -1717,8 +1717,7 @@ "Yours, or the other users’ session": "Yours, or the other users’ session", "You’re all caught up": "You’re all caught up", "You have no visible notifications.": "You have no visible notifications.", - "Pinned": "Pinned", - "Pinned Messages": "Pinned Messages", + "Pinned messages": "Pinned messages", "Room Info": "Room Info", "You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets", "Unpin": "Unpin", @@ -1952,7 +1951,6 @@ "Rotate Right": "Rotate Right", "Download": "Download", "Information": "Information", - "View message": "View message", "Language Dropdown": "Language Dropdown", "%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s", "%(severalUsers)sjoined %(count)s times|other": "%(severalUsers)sjoined %(count)s times", diff --git a/src/stores/RightPanelStorePhases.ts b/src/stores/RightPanelStorePhases.ts index 0d96f92945..d62f6c6110 100644 --- a/src/stores/RightPanelStorePhases.ts +++ b/src/stores/RightPanelStorePhases.ts @@ -44,6 +44,7 @@ export enum RightPanelPhases { export const RIGHT_PANEL_PHASES_NO_ARGS = [ RightPanelPhases.RoomSummary, RightPanelPhases.NotificationPanel, + RightPanelPhases.PinnedMessages, RightPanelPhases.FilePanel, RightPanelPhases.RoomMemberList, RightPanelPhases.GroupMemberList, From 54d8953024b1026fd636f2bed4e45cacda12a360 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 26 May 2021 14:00:53 +0100 Subject: [PATCH 072/198] delint --- res/css/views/rooms/_PinnedEventTile.scss | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/res/css/views/rooms/_PinnedEventTile.scss b/res/css/views/rooms/_PinnedEventTile.scss index 8e96f3caeb..15b3c16faa 100644 --- a/res/css/views/rooms/_PinnedEventTile.scss +++ b/res/css/views/rooms/_PinnedEventTile.scss @@ -20,9 +20,10 @@ limitations under the License. padding: 0 4px 12px; display: grid; - grid-template-areas: "avatar name remove" - "content content content" - "footer footer footer"; + grid-template-areas: + "avatar name remove" + "content content content" + "footer footer footer"; grid-template-rows: max-content auto max-content; grid-template-columns: 24px auto 24px; grid-row-gap: 12px; From 0758c09d9e9760d3a2bae3b562016ff73af82ec9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 26 May 2021 14:06:12 +0100 Subject: [PATCH 073/198] i18n --- src/i18n/strings/en_EN.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index d5b37a7d2d..7c24364725 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1508,7 +1508,7 @@ "Invite to just this room": "Invite to just this room", "Add a photo, so people can easily spot your room.": "Add a photo, so people can easily spot your room.", "This is the start of .": "This is the start of .", - "Unpin Message": "Unpin Message", + "Unpin": "Unpin", "View message": "View message", "%(duration)ss": "%(duration)ss", "%(duration)sm": "%(duration)sm", @@ -1720,7 +1720,6 @@ "Pinned messages": "Pinned messages", "Room Info": "Room Info", "You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets", - "Unpin": "Unpin", "Unpin a widget to view it in this panel": "Unpin a widget to view it in this panel", "Options": "Options", "Set my room layout for everyone": "Set my room layout for everyone", @@ -2464,6 +2463,7 @@ "Unable to reject invite": "Unable to reject invite", "Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)", "Forward Message": "Forward Message", + "Unpin Message": "Unpin Message", "Pin Message": "Pin Message", "Unhide Preview": "Unhide Preview", "Share Permalink": "Share Permalink", From 1ff870927a425145d8676e0e87a97feadc313efa Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 26 May 2021 15:40:24 +0100 Subject: [PATCH 074/198] When pinning a message automatically mark it as read --- .../views/context_menus/MessageContextMenu.js | 37 +++++++++---------- .../views/right_panel/PinnedMessagesCard.tsx | 2 +- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 365f2ab1de..d9f21906fe 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -31,6 +31,7 @@ import { isContentActionable } from '../../../utils/EventUtils'; import {MenuItem} from "../../structures/ContextMenu"; import {EventType} from "matrix-js-sdk/src/@types/event"; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard"; export function canCancel(eventStatus) { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; @@ -82,7 +83,7 @@ export default class MessageContextMenu extends React.Component { const canRedact = room.currentState.maySendRedactionForEvent(this.props.mxEvent, cli.credentials.userId) && this.props.mxEvent.getType() !== EventType.RoomServerAcl && this.props.mxEvent.getType() !== EventType.RoomEncryption; - let canPin = room.currentState.mayClientSendStateEvent('m.room.pinned_events', cli); + let canPin = room.currentState.mayClientSendStateEvent(EventType.RoomPinnedEvents, cli); // HACK: Intentionally say we can't pin if the user doesn't want to use the functionality if (!SettingsStore.getValue("feature_pinning")) canPin = false; @@ -92,7 +93,7 @@ export default class MessageContextMenu extends React.Component { _isPinned() { const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); - const pinnedEvent = room.currentState.getStateEvents('m.room.pinned_events', ''); + const pinnedEvent = room.currentState.getStateEvents(EventType.RoomPinnedEvents, ''); if (!pinnedEvent) return false; const content = pinnedEvent.getContent(); return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId()); @@ -165,25 +166,23 @@ export default class MessageContextMenu extends React.Component { }; onPinClick = () => { - MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '') - .catch((e) => { - // Intercept the Event Not Found error and fall through the promise chain with no event. - if (e.errcode === "M_NOT_FOUND") return null; - throw e; - }) - .then((event) => { - const eventIds = (event ? event.pinned : []) || []; - if (!eventIds.includes(this.props.mxEvent.getId())) { - // Not pinned - add - eventIds.push(this.props.mxEvent.getId()); - } else { - // Pinned - remove - eventIds.splice(eventIds.indexOf(this.props.mxEvent.getId()), 1); - } + const cli = MatrixClientPeg.get(); + const room = cli.getRoom(this.props.mxEvent.getRoomId()); + const eventId = this.props.mxEvent.getId(); - const cli = MatrixClientPeg.get(); - cli.sendStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', {pinned: eventIds}, ''); + const pinnedIds = room?.currentState?.getStateEvents(EventType.RoomPinnedEvents, "")?.pinned || []; + if (pinnedIds.includes(eventId)) { + pinnedIds.splice(pinnedIds.indexOf(eventId), 1); + } else { + pinnedIds.push(eventId); + cli.setRoomAccountData(room.roomId, ReadPinsEventId, { + event_ids: [ + ...room.getAccountData(ReadPinsEventId)?.getContent()?.event_ids, + eventId, + ], }); + } + cli.sendStateEvent(this.props.mxEvent.getRoomId(), EventType.RoomPinnedEvents, { pinned: pinnedIds }, ""); this.closeMenu(); }; diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 48be8cd706..a3f1f2d9df 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -53,7 +53,7 @@ export const usePinnedEvents = (room: Room): string[] => { return pinnedEvents; }; -const ReadPinsEventId = "im.vector.room.read_pins"; +export const ReadPinsEventId = "im.vector.room.read_pins"; export const useReadPinnedEvents = (room: Room): Set => { const [readPinnedEvents, setReadPinnedEvents] = useState>(new Set()); From 1a51ed9ffd78618c6bf21850eff397f3126c9fc9 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 28 May 2021 09:34:08 +0100 Subject: [PATCH 075/198] Make breadcrumb animation run on the compositing layer --- res/css/structures/_RoomView.scss | 1 + res/css/views/rooms/_RoomBreadcrumbs.scss | 6 +++--- src/components/structures/ContextMenu.tsx | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index cdbe47178d..3d3654bda0 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -237,6 +237,7 @@ hr.mx_RoomView_myReadMarker { position: relative; top: -1px; z-index: 1; + will-change: width; transition: width 400ms easeinsine 1s, opacity 400ms easeinsine 1s; width: 99%; opacity: 1; diff --git a/res/css/views/rooms/_RoomBreadcrumbs.scss b/res/css/views/rooms/_RoomBreadcrumbs.scss index 6512797401..152b0a45cd 100644 --- a/res/css/views/rooms/_RoomBreadcrumbs.scss +++ b/res/css/views/rooms/_RoomBreadcrumbs.scss @@ -32,14 +32,14 @@ limitations under the License. // first triggering the enter state with the newest breadcrumb off screen (-40px) then // sliding it into view. &.mx_RoomBreadcrumbs-enter { - margin-left: -40px; // 32px for the avatar, 8px for the margin + transform: translateX(-40px); // 32px for the avatar, 8px for the margin } &.mx_RoomBreadcrumbs-enter-active { - margin-left: 0; + transform: translateX(0); // Timing function is as-requested by design. // NOTE: The transition time MUST match the value passed to CSSTransition! - transition: margin-left 640ms cubic-bezier(0.66, 0.02, 0.36, 1); + transition: transform 640ms cubic-bezier(0.66, 0.02, 0.36, 1); } .mx_RoomBreadcrumbs_placeholder { diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 78a9d26934..9d8665c176 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -390,7 +390,7 @@ export class ContextMenu extends React.PureComponent { } render(): React.ReactChild { - return ReactDOM.createPortal(this.renderMenu(), document.body); + return ReactDOM.createPortal(this.renderMenu(), getOrCreateContainer()); } } From 53ebf3b8e31560ee71d744d203f653b3dd36b32c Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Sat, 29 May 2021 01:37:19 -0500 Subject: [PATCH 076/198] Don't include via when sharing room alias Signed-off-by: Aaron Raimist --- src/utils/permalinks/Permalinks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/permalinks/Permalinks.ts b/src/utils/permalinks/Permalinks.ts index d87c826cc2..cecd79dd53 100644 --- a/src/utils/permalinks/Permalinks.ts +++ b/src/utils/permalinks/Permalinks.ts @@ -149,7 +149,7 @@ export class RoomPermalinkCreator { // Prefer to use canonical alias for permalink if possible const alias = this.room.getCanonicalAlias(); if (alias) { - return getPermalinkConstructor().forRoom(alias, this._serverCandidates); + return getPermalinkConstructor().forRoom(alias); } } return getPermalinkConstructor().forRoom(this.roomId, this._serverCandidates); From ccdd2311f447111f4cf56725834ba08be78abc08 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Sat, 29 May 2021 01:38:41 -0500 Subject: [PATCH 077/198] Make "share this room" use aliases if possible Signed-off-by: Aaron Raimist --- src/utils/permalinks/Permalinks.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/permalinks/Permalinks.ts b/src/utils/permalinks/Permalinks.ts index cecd79dd53..2f268aaa0c 100644 --- a/src/utils/permalinks/Permalinks.ts +++ b/src/utils/permalinks/Permalinks.ts @@ -293,16 +293,16 @@ export function makeRoomPermalink(roomId: string): string { // If the roomId isn't actually a room ID, don't try to list the servers. // Aliases are already routable, and don't need extra information. - if (roomId[0] !== '!') return getPermalinkConstructor().forRoom(roomId, []); + if (roomId[0] !== '!') return getPermalinkConstructor().forShareableRoom(roomId, []); const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); if (!room) { - return getPermalinkConstructor().forRoom(roomId, []); + return getPermalinkConstructor().forShareableRoom(roomId, []); } const permalinkCreator = new RoomPermalinkCreator(room); permalinkCreator.load(); - return permalinkCreator.forRoom(); + return permalinkCreator.forShareableRoom(); } export function makeGroupPermalink(groupId: string): string { From ebd7cd6212905862912784add85fa936709a8b65 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 1 Jun 2021 11:21:59 +0100 Subject: [PATCH 078/198] Add CSS containement rules for shorter reflow operations --- res/css/_common.scss | 1 + res/css/structures/_ContextualMenu.scss | 1 + res/css/structures/_LeftPanel.scss | 2 ++ res/css/structures/_RightPanel.scss | 1 + res/css/structures/_RoomView.scss | 2 ++ res/css/structures/_ScrollPanel.scss | 3 +++ res/css/views/avatars/_DecoratedRoomAvatar.scss | 1 + res/css/views/rooms/_RoomSublist.scss | 1 + res/css/views/rooms/_RoomTile.scss | 4 ++++ 9 files changed, 16 insertions(+) diff --git a/res/css/_common.scss b/res/css/_common.scss index a05ec7eadd..b128a82442 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -291,6 +291,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus { .mx_Dialog_staticWrapper .mx_Dialog { z-index: 4010; + contain: content; } .mx_Dialog_background { diff --git a/res/css/structures/_ContextualMenu.scss b/res/css/structures/_ContextualMenu.scss index 4b33427a87..d7f2cb76e8 100644 --- a/res/css/structures/_ContextualMenu.scss +++ b/res/css/structures/_ContextualMenu.scss @@ -38,6 +38,7 @@ limitations under the License. position: absolute; font-size: $font-14px; z-index: 5001; + contain: content; } .mx_ContextualMenu_right { diff --git a/res/css/structures/_LeftPanel.scss b/res/css/structures/_LeftPanel.scss index 7c3cd1c513..c7dd678c07 100644 --- a/res/css/structures/_LeftPanel.scss +++ b/res/css/structures/_LeftPanel.scss @@ -25,6 +25,7 @@ $roomListCollapsedWidth: 68px; // Create a row-based flexbox for the GroupFilterPanel and the room list display: flex; + contain: content; .mx_LeftPanel_GroupFilterPanelContainer { flex-grow: 0; @@ -70,6 +71,7 @@ $roomListCollapsedWidth: 68px; // aligned correctly. This is also a row-based flexbox. display: flex; align-items: center; + contain: content; &.mx_IndicatorScrollbar_leftOverflow { mask-image: linear-gradient(90deg, transparent, black 5%); diff --git a/res/css/structures/_RightPanel.scss b/res/css/structures/_RightPanel.scss index 5515fe4060..48f19c1cd5 100644 --- a/res/css/structures/_RightPanel.scss +++ b/res/css/structures/_RightPanel.scss @@ -25,6 +25,7 @@ limitations under the License. padding: 4px 0; box-sizing: border-box; height: 100%; + contain: strict; .mx_RoomView_MessageList { padding: 14px 18px; // top and bottom is 4px smaller to balance with the padding set above diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index cdbe47178d..b019fe7e01 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -152,6 +152,7 @@ limitations under the License. flex: 1; display: flex; flex-direction: column; + contain: content; } .mx_RoomView_statusArea { @@ -221,6 +222,7 @@ limitations under the License. .mx_RoomView_MessageList li { clear: both; + contain: content; } li.mx_RoomView_myReadMarker_container { diff --git a/res/css/structures/_ScrollPanel.scss b/res/css/structures/_ScrollPanel.scss index a4e501b339..7b75c69e86 100644 --- a/res/css/structures/_ScrollPanel.scss +++ b/res/css/structures/_ScrollPanel.scss @@ -21,5 +21,8 @@ limitations under the License. display: flex; flex-direction: column; justify-content: flex-end; + + content-visibility: auto; + contain-intrinsic-size: 50px; } } diff --git a/res/css/views/avatars/_DecoratedRoomAvatar.scss b/res/css/views/avatars/_DecoratedRoomAvatar.scss index 2631cbfb40..257b512579 100644 --- a/res/css/views/avatars/_DecoratedRoomAvatar.scss +++ b/res/css/views/avatars/_DecoratedRoomAvatar.scss @@ -16,6 +16,7 @@ limitations under the License. .mx_DecoratedRoomAvatar, .mx_ExtraTile { position: relative; + contain: content; &.mx_DecoratedRoomAvatar_cutout .mx_BaseAvatar { mask-image: url('$(res)/img/element-icons/roomlist/decorated-avatar-mask.svg'); diff --git a/res/css/views/rooms/_RoomSublist.scss b/res/css/views/rooms/_RoomSublist.scss index bae469ab87..146b3edf71 100644 --- a/res/css/views/rooms/_RoomSublist.scss +++ b/res/css/views/rooms/_RoomSublist.scss @@ -198,6 +198,7 @@ limitations under the License. // as the box model should be top aligned. Happens in both FF and Chromium display: flex; flex-direction: column; + align-self: stretch; mask-image: linear-gradient(0deg, transparent, black 4px); } diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 72d29dfd4c..421fbfe39b 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -19,6 +19,10 @@ limitations under the License. margin-bottom: 4px; padding: 4px; + contain: strict; + height: 40px; + box-sizing: border-box; + // The tile is also a flexbox row itself display: flex; From 0c5e8ef381815725656c6619364fb54089a53137 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 1 Jun 2021 16:13:01 +0100 Subject: [PATCH 079/198] Upgrade matrix-js-sdk to 11.2.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 13047b69cf..108a61cdaf 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,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": "11.2.0-rc.1", "matrix-widget-api": "^0.1.0-beta.14", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 0ff235a660..1b07d4502b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5673,9 +5673,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 "11.1.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/acb9bc8cc5234326a7583514a8e120a4ac42eedc" +matrix-js-sdk@11.2.0-rc.1: + version "11.2.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-11.2.0-rc.1.tgz#339259d892ce08195864c1130780dd0b3d996af3" + integrity sha512-ACFONqdRqiPIj7aWf7G4f/2d0S/hfouIH6tdkFGDbizbWJCmqXRJ8EN2gmu0F3A6GGDo0+k9cqaeMf3Y1KIM8w== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From d02f462df9a3cd0a336aea733c2992abcbc07b2d Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 1 Jun 2021 16:18:25 +0100 Subject: [PATCH 080/198] Prepare changelog for v3.23.0-rc.1 --- CHANGELOG.md | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3d9afd51d..a3abd1f32b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,101 @@ +Changes in [3.23.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.23.0-rc.1) (2021-06-01) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.22.0...v3.23.0-rc.1) + + * Upgrade to JS SDK 11.2.0-rc.1 + * Translations update from Weblate + [\#6128](https://github.com/matrix-org/matrix-react-sdk/pull/6128) + * Fix all DMs wrongly appearing in room list when `m.direct` is changed + [\#6122](https://github.com/matrix-org/matrix-react-sdk/pull/6122) + * Update way of checking for registration disabled + [\#6123](https://github.com/matrix-org/matrix-react-sdk/pull/6123) + * Fix the ability to remove avatar from a space via settings + [\#6126](https://github.com/matrix-org/matrix-react-sdk/pull/6126) + * Switch to stable endpoint/fields for MSC2858 + [\#6125](https://github.com/matrix-org/matrix-react-sdk/pull/6125) + * Clear stored editor state when canceling editing using a shortcut + [\#6117](https://github.com/matrix-org/matrix-react-sdk/pull/6117) + * Respect newlines in space topics + [\#6124](https://github.com/matrix-org/matrix-react-sdk/pull/6124) + * Add url param `defaultUsername` to prefill the login username field + [\#5674](https://github.com/matrix-org/matrix-react-sdk/pull/5674) + * Bump ws from 7.4.2 to 7.4.6 + [\#6115](https://github.com/matrix-org/matrix-react-sdk/pull/6115) + * Sticky headers repositioning without layout trashing + [\#6110](https://github.com/matrix-org/matrix-react-sdk/pull/6110) + * Handle user_busy in voip calls + [\#6112](https://github.com/matrix-org/matrix-react-sdk/pull/6112) + * Avoid showing warning modals from the invite dialog after it unmounts + [\#6105](https://github.com/matrix-org/matrix-react-sdk/pull/6105) + * Fix misleading child counts in spaces + [\#6109](https://github.com/matrix-org/matrix-react-sdk/pull/6109) + * Close creation menu when expanding space panel via expand hierarchy + [\#6090](https://github.com/matrix-org/matrix-react-sdk/pull/6090) + * Prevent having duplicates in pending room state + [\#6108](https://github.com/matrix-org/matrix-react-sdk/pull/6108) + * Update reactions row on event decryption + [\#6106](https://github.com/matrix-org/matrix-react-sdk/pull/6106) + * Destroy playback instance on voice message unmount + [\#6101](https://github.com/matrix-org/matrix-react-sdk/pull/6101) + * Fix message preview not up to date + [\#6102](https://github.com/matrix-org/matrix-react-sdk/pull/6102) + * Convert some Flow typed files to TS (round 2) + [\#6076](https://github.com/matrix-org/matrix-react-sdk/pull/6076) + * Remove unused middlePanelResized event listener + [\#6086](https://github.com/matrix-org/matrix-react-sdk/pull/6086) + * Fix accessing currentState on an invalid joinedRoom + [\#6100](https://github.com/matrix-org/matrix-react-sdk/pull/6100) + * Remove Promise allSettled polyfill as js-sdk uses it directly + [\#6097](https://github.com/matrix-org/matrix-react-sdk/pull/6097) + * Prevent DecoratedRoomAvatar to update its state for the same value + [\#6099](https://github.com/matrix-org/matrix-react-sdk/pull/6099) + * Skip generatePreview if event is not part of the live timeline + [\#6098](https://github.com/matrix-org/matrix-react-sdk/pull/6098) + * fix sticky headers when results num get displayed + [\#6095](https://github.com/matrix-org/matrix-react-sdk/pull/6095) + * Improve addEventsToTimeline performance scoping WhoIsTypingTile::setState + [\#6094](https://github.com/matrix-org/matrix-react-sdk/pull/6094) + * Safeguards to prevent layout trashing for window dimensions + [\#6092](https://github.com/matrix-org/matrix-react-sdk/pull/6092) + * Use local room state to render space hierarchy if the room is known + [\#6089](https://github.com/matrix-org/matrix-react-sdk/pull/6089) + * Add spinner in UserMenu to list pending long running actions + [\#6085](https://github.com/matrix-org/matrix-react-sdk/pull/6085) + * Stop overscroll in Firefox Nightly for macOS + [\#6093](https://github.com/matrix-org/matrix-react-sdk/pull/6093) + * Move SettingsStore watchers/monitors over to ES6 maps for performance + [\#6063](https://github.com/matrix-org/matrix-react-sdk/pull/6063) + * Bump libolm version. + [\#6080](https://github.com/matrix-org/matrix-react-sdk/pull/6080) + * Improve styling of the message action bar + [\#6066](https://github.com/matrix-org/matrix-react-sdk/pull/6066) + * Improve explore rooms when no results are found + [\#6070](https://github.com/matrix-org/matrix-react-sdk/pull/6070) + * Remove logo spinner + [\#6078](https://github.com/matrix-org/matrix-react-sdk/pull/6078) + * Fix add reaction prompt showing even when user is not joined to room + [\#6073](https://github.com/matrix-org/matrix-react-sdk/pull/6073) + * Vectorize spinners + [\#5680](https://github.com/matrix-org/matrix-react-sdk/pull/5680) + * Fix handling of via servers for suggested rooms + [\#6077](https://github.com/matrix-org/matrix-react-sdk/pull/6077) + * Upgrade showChatEffects to room-level setting exposure + [\#6075](https://github.com/matrix-org/matrix-react-sdk/pull/6075) + * Delete RoomView dead code + [\#6071](https://github.com/matrix-org/matrix-react-sdk/pull/6071) + * Reduce noise in tests + [\#6074](https://github.com/matrix-org/matrix-react-sdk/pull/6074) + * Fix room name issues in right panel summary card + [\#6069](https://github.com/matrix-org/matrix-react-sdk/pull/6069) + * Cache normalized room name + [\#6072](https://github.com/matrix-org/matrix-react-sdk/pull/6072) + * Update MemberList to reflect changes for invite permission change + [\#6061](https://github.com/matrix-org/matrix-react-sdk/pull/6061) + * Delete RoomView dead code + [\#6065](https://github.com/matrix-org/matrix-react-sdk/pull/6065) + * Show subspace rooms count even if it is 0 for consistency + [\#6067](https://github.com/matrix-org/matrix-react-sdk/pull/6067) + Changes in [3.22.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.22.0) (2021-05-24) ===================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.22.0-rc.1...v3.22.0) From 300b0016d60fd3f3b0527e68f987df3731f2feb0 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 1 Jun 2021 16:18:26 +0100 Subject: [PATCH 081/198] v3.23.0-rc.1 --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 108a61cdaf..4f6a023383 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.22.0", + "version": "3.23.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", @@ -200,5 +200,6 @@ "coverageReporters": [ "text" ] - } + }, + "typings": "./lib/index.d.ts" } From b032422c6a536b856d7d105dd593a804b245c933 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 1 Jun 2021 17:37:31 -0400 Subject: [PATCH 082/198] Fix whitespace lints Signed-off-by: Robin Townsend --- src/components/views/dialogs/ForwardDialog.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 59475b1b51..58759d7d42 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -184,13 +184,13 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr const previewLayout = SettingsStore.getValue("layout"); return -

    { _t("Message preview") }

    +

    {_t("Message preview")}

    = ({ matrixClient: cli, event, permalinkCr
    Date: Tue, 1 Jun 2021 17:56:46 -0400 Subject: [PATCH 083/198] Match forward dialog send failed indicator color with button Signed-off-by: Robin Townsend --- res/css/views/dialogs/_ForwardDialog.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss index 593fe12531..fc59259ca2 100644 --- a/res/css/views/dialogs/_ForwardDialog.scss +++ b/res/css/views/dialogs/_ForwardDialog.scss @@ -124,7 +124,8 @@ limitations under the License. } .mx_NotificationBadge { - opacity: 0.4; + // Match the failed to send indicator's color with the disabled button + background-color: $button-danger-disabled-fg-color; } &.mx_ForwardList_sending .mx_ForwardList_sendIcon { From c78167977a49421156f62d995cf5aab97801a758 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 1 Jun 2021 17:57:26 -0400 Subject: [PATCH 084/198] Remove unused class Signed-off-by: Robin Townsend --- src/components/views/context_menus/MessageContextMenu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 08fc5844ec..442470bb52 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -161,7 +161,7 @@ export default class MessageContextMenu extends React.Component { matrixClient: MatrixClientPeg.get(), event: this.props.mxEvent, permalinkCreator: this.props.permalinkCreator, - }, 'mx_Dialog_forwardMessage'); + }); this.closeMenu(); }; From 4ef69fcbf6cd5bb90261759eff4cd3e720a96f14 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 1 Jun 2021 18:09:51 -0400 Subject: [PATCH 085/198] Use settings hooks in forward dialog ...to dynamically watch for layout changes. Signed-off-by: Robin Townsend --- src/components/views/dialogs/ForwardDialog.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 58759d7d42..2c4920da4b 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -22,7 +22,7 @@ import {MatrixClient} from "matrix-js-sdk/src/client"; import {_t} from "../../../languageHandler"; import dis from "../../../dispatcher/dispatcher"; -import SettingsStore from "../../../settings/SettingsStore"; +import {useSettingValue, useFeatureEnabled} from "../../../hooks/useSettings"; import {UIFeature} from "../../../settings/UIFeature"; import {Layout} from "../../../settings/Layout"; import {IDialogProps} from "./IDialogProps"; @@ -174,14 +174,16 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr const [query, setQuery] = useState(""); const lcQuery = query.toLowerCase(); + const spacesEnabled = useFeatureEnabled("feature_spaces"); + const flairEnabled = useFeatureEnabled(UIFeature.Flair); + const previewLayout = useSettingValue("layout"); + const rooms = useMemo(() => sortRooms( cli.getVisibleRooms().filter( room => room.getMyMembership() === "join" && - !(SettingsStore.getValue("feature_spaces") && room.isSpaceRoom()), + !(spacesEnabled && room.isSpaceRoom()), ), - ), [cli]).filter(room => room.name.toLowerCase().includes(lcQuery)); - - const previewLayout = SettingsStore.getValue("layout"); + ), [cli, spacesEnabled]).filter(room => room.name.toLowerCase().includes(lcQuery)); return = ({ matrixClient: cli, event, permalinkCr
    From 59660df0cbe74dbe39ada90e4296d427e5375bbc Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 1 Jun 2021 20:17:20 -0400 Subject: [PATCH 086/198] Use a QueryMatcher for forward dialog filtering This also allows us to filter by room aliases. Signed-off-by: Robin Townsend --- src/components/views/dialogs/ForwardDialog.tsx | 15 ++++++++++++--- src/hooks/useSettings.ts | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 2c4920da4b..cba292ea25 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -38,6 +38,7 @@ import {StaticNotificationState} from "../../../stores/notifications/StaticNotif import NotificationBadge from "../rooms/NotificationBadge"; import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm"; +import QueryMatcher from "../../../autocomplete/QueryMatcher"; const AVATAR_SIZE = 30; @@ -176,14 +177,22 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr const spacesEnabled = useFeatureEnabled("feature_spaces"); const flairEnabled = useFeatureEnabled(UIFeature.Flair); - const previewLayout = useSettingValue("layout"); + const previewLayout = useSettingValue("layout"); - const rooms = useMemo(() => sortRooms( + let rooms = useMemo(() => sortRooms( cli.getVisibleRooms().filter( room => room.getMyMembership() === "join" && !(spacesEnabled && room.isSpaceRoom()), ), - ), [cli, spacesEnabled]).filter(room => room.name.toLowerCase().includes(lcQuery)); + ), [cli, spacesEnabled]); + + if (lcQuery) { + rooms = new QueryMatcher(rooms, { + keys: ["name"], + funcs: [r => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)], + shouldMatchWordsOnly: false, + }).match(lcQuery); + } return (settingName: string, roomId: string = null, e }; // Hook to fetch whether a feature is enabled and dynamically update when that changes -export const useFeatureEnabled = (featureName: string, roomId: string = null) => { - const [enabled, setEnabled] = useState(SettingsStore.getValue(featureName, roomId)); +export const useFeatureEnabled = (featureName: string, roomId: string = null): boolean => { + const [enabled, setEnabled] = useState(SettingsStore.getValue(featureName, roomId)); useEffect(() => { const ref = SettingsStore.watchSetting(featureName, roomId, () => { From 992861a1cd821c2d7564fd644b4294220f54ff96 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 1 Jun 2021 20:36:28 -0400 Subject: [PATCH 087/198] Fix forward dialog tests Signed-off-by: Robin Townsend --- test/test-utils.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test-utils.js b/test/test-utils.js index c82885b5f0..6053924103 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -265,6 +265,8 @@ export function mkStubRoom(roomId = null, name) { isSpaceRoom: jest.fn(() => false), getUnreadNotificationCount: jest.fn(() => 0), getEventReadUpTo: jest.fn(() => null), + getCanonicalAlias: jest.fn(), + getAltAliases: jest.fn().mockReturnValue([]), timeline: [], }; } From 5b2dacd99ec7dd09a7dc2fbc76c81d1f44f9dfd8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 1 Jun 2021 21:36:28 -0600 Subject: [PATCH 088/198] Adapt for js-sdk MatrixClient conversion to TS For https://github.com/matrix-org/matrix-js-sdk/pull/1718 --- src/Searching.js | 10 +++++----- src/SecurityManager.ts | 2 +- src/components/views/dialogs/DevtoolsDialog.tsx | 2 +- src/components/views/settings/CrossSigningPanel.js | 4 ++-- src/components/views/settings/SecureBackupPanel.js | 4 ++-- src/indexing/EventIndex.js | 2 +- src/rageshake/submit-rageshake.ts | 6 +++--- src/stores/CommunityPrototypeStore.ts | 2 +- src/stores/SetupEncryptionStore.js | 2 +- test/test-utils.js | 2 +- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Searching.js b/src/Searching.js index f65b8920b3..2b17aee054 100644 --- a/src/Searching.js +++ b/src/Searching.js @@ -66,7 +66,7 @@ async function serverSideSearchProcess(term, roomId = undefined) { highlights: [], }; - return client._processRoomEventsSearch(searchResult, result.response); + return client.processRoomEventsSearch(searchResult, result.response); } function compareEvents(a, b) { @@ -131,7 +131,7 @@ async function combinedSearch(searchTerm) { }, }; - const result = client._processRoomEventsSearch(emptyResult, response); + const result = client.processRoomEventsSearch(emptyResult, response); // Restore our encryption info so we can properly re-verify the events. restoreEncryptionInfo(result.results); @@ -185,7 +185,7 @@ async function localSearchProcess(searchTerm, roomId = undefined) { }, }; - const processedResult = MatrixClientPeg.get()._processRoomEventsSearch(emptyResult, response); + const processedResult = MatrixClientPeg.get().processRoomEventsSearch(emptyResult, response); // Restore our encryption info so we can properly re-verify the events. restoreEncryptionInfo(processedResult.results); @@ -210,7 +210,7 @@ async function localPagination(searchResult) { }, }; - const result = MatrixClientPeg.get()._processRoomEventsSearch(searchResult, response); + const result = MatrixClientPeg.get().processRoomEventsSearch(searchResult, response); // Restore our encryption info so we can properly re-verify the events. const newSlice = result.results.slice(Math.max(result.results.length - newResultCount, 0)); @@ -520,7 +520,7 @@ async function combinedPagination(searchResult) { const oldResultCount = searchResult.results ? searchResult.results.length : 0; // Let the client process the combined result. - const result = client._processRoomEventsSearch(searchResult, response); + const result = client.processRoomEventsSearch(searchResult, response); // Restore our encryption info so we can properly re-verify the events. const newResultCount = result.results.length - oldResultCount; diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts index 203830d232..09c8d30614 100644 --- a/src/SecurityManager.ts +++ b/src/SecurityManager.ts @@ -271,7 +271,7 @@ async function onSecretRequested( } return key && encodeBase64(key); } else if (name === "m.megolm_backup.v1") { - const key = await client._crypto.getSessionBackupPrivateKey(); + const key = await client.crypto.getSessionBackupPrivateKey(); if (!key) { console.log( `session backup key requested by ${deviceId}, but not found in cache`, diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 0c37ea9599..fdbf6a36fc 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -766,7 +766,7 @@ class VerificationExplorer extends React.PureComponent { render() { const cli = this.context; const room = this.props.room; - const inRoomChannel = cli._crypto._inRoomVerificationRequests; + const inRoomChannel = cli.crypto._inRoomVerificationRequests; const inRoomRequests = (inRoomChannel._requestsByRoomId || new Map()).get(room.roomId) || new Map(); return (
    diff --git a/src/components/views/settings/CrossSigningPanel.js b/src/components/views/settings/CrossSigningPanel.js index c0f23cb906..0cd1a64ada 100644 --- a/src/components/views/settings/CrossSigningPanel.js +++ b/src/components/views/settings/CrossSigningPanel.js @@ -79,8 +79,8 @@ export default class CrossSigningPanel extends React.PureComponent { async _getUpdatedStatus() { const cli = MatrixClientPeg.get(); const pkCache = cli.getCrossSigningCacheCallbacks(); - const crossSigning = cli._crypto._crossSigningInfo; - const secretStorage = cli._crypto._secretStorage; + const crossSigning = cli.crypto._crossSigningInfo; + const secretStorage = cli.crypto._secretStorage; const crossSigningPublicKeysOnDevice = crossSigning.getId(); const crossSigningPrivateKeysInStorage = await crossSigning.isStoredInSecretStorage(secretStorage); const masterPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("master")); diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js index 310114c8af..4f3eb0bdf6 100644 --- a/src/components/views/settings/SecureBackupPanel.js +++ b/src/components/views/settings/SecureBackupPanel.js @@ -131,10 +131,10 @@ export default class SecureBackupPanel extends React.PureComponent { async _getUpdatedDiagnostics() { const cli = MatrixClientPeg.get(); - const secretStorage = cli._crypto._secretStorage; + const secretStorage = cli.crypto._secretStorage; const backupKeyStored = !!(await cli.isKeyBackupKeyStored()); - const backupKeyFromCache = await cli._crypto.getSessionBackupPrivateKey(); + const backupKeyFromCache = await cli.crypto.getSessionBackupPrivateKey(); const backupKeyCached = !!(backupKeyFromCache); const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array; const secretStorageKeyInAccount = await secretStorage.hasKey(); diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.js index ed4418140b..33f2d594ae 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.js @@ -453,7 +453,7 @@ export default class EventIndex extends EventEmitter { let res; try { - res = await client._createMessagesRequest( + res = await client.createMessagesRequest( checkpoint.roomId, checkpoint.token, this._eventsPerCrawl, checkpoint.direction); } catch (e) { diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts index f46dd88fba..b2ad9fe6f6 100644 --- a/src/rageshake/submit-rageshake.ts +++ b/src/rageshake/submit-rageshake.ts @@ -92,8 +92,8 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true) { body.append('cross_signing_key', client.getCrossSigningId()); // add cross-signing status information - const crossSigning = client._crypto._crossSigningInfo; - const secretStorage = client._crypto._secretStorage; + const crossSigning = client.crypto._crossSigningInfo; + const secretStorage = client.crypto._secretStorage; body.append("cross_signing_ready", String(await client.isCrossSigningReady())); body.append("cross_signing_supported_by_hs", @@ -114,7 +114,7 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true) { body.append("secret_storage_key_in_account", String(!!(await secretStorage.hasKey()))); body.append("session_backup_key_in_secret_storage", String(!!(await client.isKeyBackupKeyStored()))); - const sessionBackupKeyFromCache = await client._crypto.getSessionBackupPrivateKey(); + const sessionBackupKeyFromCache = await client.crypto.getSessionBackupPrivateKey(); body.append("session_backup_key_cached", String(!!sessionBackupKeyFromCache)); body.append("session_backup_key_well_formed", String(sessionBackupKeyFromCache instanceof Uint8Array)); } diff --git a/src/stores/CommunityPrototypeStore.ts b/src/stores/CommunityPrototypeStore.ts index 92e094c83b..023845c9ee 100644 --- a/src/stores/CommunityPrototypeStore.ts +++ b/src/stores/CommunityPrototypeStore.ts @@ -126,7 +126,7 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient { if (membership === EffectiveMembership.Invite) { try { const path = utils.encodeUri("/rooms/$roomId/group_info", {$roomId: room.roomId}); - const profile = await this.matrixClient._http.authedRequest( + const profile = await this.matrixClient.http.authedRequest( undefined, "GET", path, undefined, undefined, {prefix: "/_matrix/client/unstable/im.vector.custom"}); diff --git a/src/stores/SetupEncryptionStore.js b/src/stores/SetupEncryptionStore.js index 5f0054ff24..b768ae69df 100644 --- a/src/stores/SetupEncryptionStore.js +++ b/src/stores/SetupEncryptionStore.js @@ -196,7 +196,7 @@ export class SetupEncryptionStore extends EventEmitter { this.phase = PHASE_FINISHED; this.emit("update"); // async - ask other clients for keys, if necessary - MatrixClientPeg.get()._crypto.cancelAndResendAllOutgoingKeyRequests(); + MatrixClientPeg.get().crypto.cancelAndResendAllOutgoingKeyRequests(); } async _setActiveVerificationRequest(request) { diff --git a/test/test-utils.js b/test/test-utils.js index 4faf948178..b9014f4876 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -90,7 +90,7 @@ export function createTestClient() { }), // Used by various internal bits we aren't concerned with (yet) - _sessionStore: { + sessionStore: { store: { getItem: jest.fn(), }, From c9883f346c32d0c5d670b0efcc5702ec18f5ecad Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 1 Jun 2021 22:21:04 -0600 Subject: [PATCH 089/198] Build pass 1 --- src/ContentMessages.tsx | 26 ++++++++++++------- src/Presence.ts | 2 +- src/Terms.ts | 2 +- src/components/structures/LoggedInView.tsx | 2 +- src/components/structures/MatrixChat.tsx | 2 +- .../structures/auth/Registration.tsx | 2 +- .../tabs/user/HelpUserSettingsTab.tsx | 2 +- 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/ContentMessages.tsx b/src/ContentMessages.tsx index 95b45cce4a..b21829ac63 100644 --- a/src/ContentMessages.tsx +++ b/src/ContentMessages.tsx @@ -40,6 +40,7 @@ import { UploadStartedPayload, } from "./dispatcher/payloads/UploadPayload"; import {IUpload} from "./models/IUpload"; +import { IImageInfo } from "matrix-js-sdk/src/@types/partials"; const MAX_WIDTH = 800; const MAX_HEIGHT = 600; @@ -208,12 +209,12 @@ function infoForImageFile(matrixClient, roomId, imageFile) { } let imageInfo; - return loadImageElement(imageFile).then(function(r) { + return loadImageElement(imageFile).then((r) => { return createThumbnail(r.img, r.width, r.height, thumbnailType); - }).then(function(result) { + }).then((result) => { imageInfo = result.info; return uploadFile(matrixClient, roomId, result.thumbnail); - }).then(function(result) { + }).then((result) => { imageInfo.thumbnail_url = result.url; imageInfo.thumbnail_file = result.file; return imageInfo; @@ -264,12 +265,12 @@ function infoForVideoFile(matrixClient, roomId, videoFile) { const thumbnailType = "image/jpeg"; let videoInfo; - return loadVideoElement(videoFile).then(function(video) { + return loadVideoElement(videoFile).then((video) => { return createThumbnail(video, video.videoWidth, video.videoHeight, thumbnailType); - }).then(function(result) { + }).then((result) => { videoInfo = result.info; return uploadFile(matrixClient, roomId, result.thumbnail); - }).then(function(result) { + }).then((result) => { videoInfo.thumbnail_url = result.url; videoInfo.thumbnail_file = result.file; return videoInfo; @@ -308,7 +309,12 @@ function readFileAsArrayBuffer(file: File | Blob): Promise { * If the file is unencrypted then the object will have a "url" key. * If the file is encrypted then the object will have a "file" key. */ -function uploadFile(matrixClient: MatrixClient, roomId: string, file: File | Blob, progressHandler?: any) { +function uploadFile( + matrixClient: MatrixClient, + roomId: string, + file: File | Blob, + progressHandler?: any, // TODO: Types +): Promise<{url?: string, file?: any}> { // TODO: Types let canceled = false; if (matrixClient.isRoomEncrypted(roomId)) { // If the room is encrypted then encrypt the file before uploading it. @@ -355,7 +361,7 @@ function uploadFile(matrixClient: MatrixClient, roomId: string, file: File | Blo // If the attachment isn't encrypted then include the URL directly. return {"url": url}; }); - promise1.abort = () => { + (promise1 as any).abort = () => { canceled = true; MatrixClientPeg.get().cancelUpload(basePromise); }; @@ -367,7 +373,7 @@ export default class ContentMessages { private inprogress: IUpload[] = []; private mediaConfig: IMediaConfig = null; - sendStickerContentToRoom(url: string, roomId: string, info: string, text: string, matrixClient: MatrixClient) { + sendStickerContentToRoom(url: string, roomId: string, info: IImageInfo, text: string, matrixClient: MatrixClient) { const startTime = CountlyAnalytics.getTimestamp(); const prom = MatrixClientPeg.get().sendStickerMessage(roomId, url, info, text).catch((e) => { console.warn(`Failed to send content with URL ${url} to room ${roomId}`, e); @@ -441,7 +447,7 @@ export default class ContentMessages { let uploadAll = false; // Promise to complete before sending next file into room, used for synchronisation of file-sending // to match the order the files were specified in - let promBefore = Promise.resolve(); + let promBefore: Promise = Promise.resolve(); for (let i = 0; i < okFiles.length; ++i) { const file = okFiles[i]; if (!uploadAll) { diff --git a/src/Presence.ts b/src/Presence.ts index eb56c5714e..8f2e127cb4 100644 --- a/src/Presence.ts +++ b/src/Presence.ts @@ -98,7 +98,7 @@ class Presence { } try { - await MatrixClientPeg.get().setPresence(this.state); + await MatrixClientPeg.get().setPresence({presence: this.state}); console.info("Presence:", newState); } catch (err) { console.error("Failed to set presence:", err); diff --git a/src/Terms.ts b/src/Terms.ts index 1b1c152fdd..a6ea40a6e8 100644 --- a/src/Terms.ts +++ b/src/Terms.ts @@ -103,7 +103,7 @@ export async function startTermsFlow( // fetch the set of agreed policy URLs from account data const currentAcceptedTerms = await MatrixClientPeg.get().getAccountData('m.accepted_terms'); - let agreedUrlSet; + let agreedUrlSet: Set; if (!currentAcceptedTerms || !currentAcceptedTerms.getContent() || !currentAcceptedTerms.getContent().accepted) { agreedUrlSet = new Set(); } else { diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index ad5c759f0d..3091278e3a 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -358,7 +358,7 @@ class LoggedInView extends React.Component { const pinnedEventIds = pinStateEvent.getContent().pinned.slice(0, MAX_PINNED_NOTICES_PER_ROOM); for (const eventId of pinnedEventIds) { - const timeline = await this._matrixClient.getEventTimeline(room.getUnfilteredTimelineSet(), eventId, 0); + const timeline = await this._matrixClient.getEventTimeline(room.getUnfilteredTimelineSet(), eventId); const event = timeline.getEvents().find(ev => ev.getId() === eventId); if (event) events.push(event); } diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index d5e8e6a1f8..16da9321e2 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -378,7 +378,7 @@ export default class MatrixChat extends React.PureComponent { this.onLoggedIn(); } - const promisesList = [this.firstSyncPromise.promise]; + const promisesList: Promise[] = [this.firstSyncPromise.promise]; if (cryptoEnabled) { // wait for the client to finish downloading cross-signing keys for us so we // know whether or not we have keys set up on this account diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index e4515dd627..6feb1e34f7 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -61,7 +61,7 @@ interface IProps { is_url?: string; session_id: string; /* eslint-enable camelcase */ - }): void; + }): string; // registration shouldn't know or care how login is done. onLoginClick(): void; onServerConfigChange(config: ValidatedServerConfig): void; diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx index 3fa0be478c..74c28e35fc 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx @@ -227,7 +227,7 @@ export default class HelpUserSettingsTab extends React.Component const appVersion = this.state.appVersion || 'unknown'; - let olmVersion = MatrixClientPeg.get().olmVersion; + let olmVersion: string = MatrixClientPeg.get().olmVersion?.toString(); olmVersion = olmVersion ? `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}` : ''; let updateButton = null; From 3dc6cfbf341dd40e2500399f5957de53630f0333 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 1 Jun 2021 22:31:08 -0600 Subject: [PATCH 090/198] Undo olmVersion handling --- src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx index 74c28e35fc..3fa0be478c 100644 --- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.tsx @@ -227,7 +227,7 @@ export default class HelpUserSettingsTab extends React.Component const appVersion = this.state.appVersion || 'unknown'; - let olmVersion: string = MatrixClientPeg.get().olmVersion?.toString(); + let olmVersion = MatrixClientPeg.get().olmVersion; olmVersion = olmVersion ? `${olmVersion[0]}.${olmVersion[1]}.${olmVersion[2]}` : ''; let updateButton = null; From bc89cf14dd49b30183625fd78a1b989dbcb26953 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 2 Jun 2021 11:53:47 +0100 Subject: [PATCH 091/198] ignore hash/fragment when de-duplicating links for url previews --- src/components/views/messages/TextualBody.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index dc644f1009..3adfea6ee6 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -278,15 +278,15 @@ export default class TextualBody extends React.Component { // pass only the first child which is the event tile otherwise this recurses on edited events let links = this.findLinks([this._content.current]); if (links.length) { - // de-dup the links (but preserve ordering) - const seen = new Set(); - links = links.filter((link) => { - if (seen.has(link)) return false; - seen.add(link); - return true; - }); + // de-duplicate the links after stripping hashes as they don't affect the preview + // using a set here maintains the order + links = Array.from(new Set(links.map(link => { + const url = new URL(link); + url.hash = ""; + return url.toString(); + }))); - this.setState({ links: links }); + this.setState({ links }); // lazy-load the hidden state of the preview widget from localstorage if (global.localStorage) { From 2c4fa73a4571ff3da3ae55e60ae8facc7748006e Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 2 Jun 2021 17:39:13 +0100 Subject: [PATCH 092/198] Map phone number lookup results to their native rooms When dialing a phone number, also look to see if there's a corresponding native user for the resulting user, and if so, go to the native room for that user. --- src/CallHandler.tsx | 33 +++++++++- src/VoipUserMapper.ts | 6 +- src/components/views/voip/DialPadModal.tsx | 23 ++----- src/dispatcher/actions.ts | 8 +++ test/CallHandler-test.ts | 73 ++++++++++++++-------- 5 files changed, 95 insertions(+), 48 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index a05d3a25c8..ba0178fa59 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -521,7 +521,9 @@ export default class CallHandler extends EventEmitter { let newNativeAssertedIdentity = newAssertedIdentity; if (newAssertedIdentity) { const response = await this.sipNativeLookup(newAssertedIdentity); - if (response.length) newNativeAssertedIdentity = response[0].userid; + if (response.length && response[0].fields.lookup_success) { + newNativeAssertedIdentity = response[0].userid; + } } console.log(`Asserted identity ${newAssertedIdentity} mapped to ${newNativeAssertedIdentity}`); @@ -862,9 +864,38 @@ export default class CallHandler extends EventEmitter { }); break; } + case Action.DialNumber: + this.dialNumber(payload.number); + break; } } + private async dialNumber(number: string) { + const results = await this.pstnLookup(number); + if (!results || results.length === 0 || !results[0].userid) { + Modal.createTrackedDialog('', '', ErrorDialog, { + title: _t("Unable to look up phone number"), + description: _t("There was an error looking up the phone number"), + }); + return; + } + const userId = results[0].userid; + + // Now check to see if this is a virtual user, in which case we should find the + // native user + const nativeLookupResults = await this.sipNativeLookup(userId); + const lookupSuccess = nativeLookupResults.length > 0 && nativeLookupResults[0].fields.lookup_success; + const nativeUserId = lookupSuccess ? nativeLookupResults[0].userid : userId; + console.log("Looked up " + number + " to " + userId + " and mapped to native user " + nativeUserId); + + const roomId = await ensureDMExists(MatrixClientPeg.get(), nativeUserId); + + dis.dispatch({ + action: 'view_room', + room_id: roomId, + }); + } + setActiveCallRoomId(activeCallRoomId: string) { logger.info("Setting call in room " + activeCallRoomId + " active"); diff --git a/src/VoipUserMapper.ts b/src/VoipUserMapper.ts index e5bed2e812..d576a5434c 100644 --- a/src/VoipUserMapper.ts +++ b/src/VoipUserMapper.ts @@ -33,7 +33,7 @@ export default class VoipUserMapper { private async userToVirtualUser(userId: string): Promise { const results = await CallHandler.sharedInstance().sipVirtualLookup(userId); - if (results.length === 0) return null; + if (results.length === 0 || !results[0].fields.lookup_success) return null; return results[0].userid; } @@ -82,14 +82,14 @@ export default class VoipUserMapper { return Boolean(claimedNativeRoomId); } - public async onNewInvitedRoom(invitedRoom: Room) { + public async onNewInvitedRoom(invitedRoom: Room): Promise { if (!CallHandler.sharedInstance().getSupportsVirtualRooms()) return; const inviterId = invitedRoom.getDMInviter(); console.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`); const result = await CallHandler.sharedInstance().sipNativeLookup(inviterId); if (result.length === 0) { - return true; + return; } if (result[0].fields.is_virtual) { diff --git a/src/components/views/voip/DialPadModal.tsx b/src/components/views/voip/DialPadModal.tsx index cdd5bc6641..f3ee49e4ac 100644 --- a/src/components/views/voip/DialPadModal.tsx +++ b/src/components/views/voip/DialPadModal.tsx @@ -15,17 +15,13 @@ limitations under the License. */ import * as React from "react"; -import { ensureDMExists } from "../../../createRoom"; import { _t } from "../../../languageHandler"; -import { MatrixClientPeg } from "../../../MatrixClientPeg"; import AccessibleButton from "../elements/AccessibleButton"; import Field from "../elements/Field"; import DialPad from './DialPad'; import dis from '../../../dispatcher/dispatcher'; -import Modal from "../../../Modal"; -import ErrorDialog from "../../views/dialogs/ErrorDialog"; -import CallHandler from "../../../CallHandler"; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { Action } from "../../../dispatcher/actions"; interface IProps { onFinished: (boolean) => void; @@ -67,21 +63,10 @@ export default class DialpadModal extends React.PureComponent { } onDialPress = async () => { - const results = await CallHandler.sharedInstance().pstnLookup(this.state.value); - if (!results || results.length === 0 || !results[0].userid) { - Modal.createTrackedDialog('', '', ErrorDialog, { - title: _t("Unable to look up phone number"), - description: _t("There was an error looking up the phone number"), - }); - } - const userId = results[0].userid; - - const roomId = await ensureDMExists(MatrixClientPeg.get(), userId); - dis.dispatch({ - action: 'view_room', - room_id: roomId, - }); + action: Action.DialNumber, + number: this.state.value, + }) this.props.onFinished(true); } diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 9fc0b54eea..073e82bef6 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -100,6 +100,14 @@ export enum Action { */ OpenDialPad = "open_dial_pad", + /** + * Dial the phone number in the payload + * payload: { + * number: , + * } + */ + DialNumber = "dial_number", + /** * Fired when CallHandler has checked for PSTN protocol support * payload: none diff --git a/test/CallHandler-test.ts b/test/CallHandler-test.ts index 1e3f92e788..a93a134133 100644 --- a/test/CallHandler-test.ts +++ b/test/CallHandler-test.ts @@ -23,8 +23,8 @@ import dis from '../src/dispatcher/dispatcher'; import { CallEvent, CallState } from 'matrix-js-sdk/src/webrtc/call'; import DMRoomMap from '../src/utils/DMRoomMap'; import EventEmitter from 'events'; -import { Action } from '../src/dispatcher/actions'; import SdkConfig from '../src/SdkConfig'; +import { ActionPayload } from '../src/dispatcher/payloads'; const REAL_ROOM_ID = '$room1:example.org'; const MAPPED_ROOM_ID = '$room2:example.org'; @@ -75,6 +75,18 @@ class FakeCall extends EventEmitter { } } +function untilDispatch(waitForAction: string): Promise { + let dispatchHandle; + return new Promise(resolve => { + dispatchHandle = dis.register(payload => { + if (payload.action === waitForAction) { + dis.unregister(dispatchHandle); + resolve(payload); + } + }); + }); +} + describe('CallHandler', () => { let dmRoomMap; let callHandler; @@ -94,6 +106,21 @@ describe('CallHandler', () => { callHandler = new CallHandler(); callHandler.start(); + const realRoom = mkStubDM(REAL_ROOM_ID, '@user1:example.org'); + const mappedRoom = mkStubDM(MAPPED_ROOM_ID, '@user2:example.org'); + const mappedRoom2 = mkStubDM(MAPPED_ROOM_ID_2, '@user3:example.org'); + + MatrixClientPeg.get().getRoom = roomId => { + switch (roomId) { + case REAL_ROOM_ID: + return realRoom; + case MAPPED_ROOM_ID: + return mappedRoom; + case MAPPED_ROOM_ID_2: + return mappedRoom2; + } + }; + dmRoomMap = { getUserIdForRoomId: roomId => { if (roomId === REAL_ROOM_ID) { @@ -134,38 +161,34 @@ describe('CallHandler', () => { SdkConfig.unset(); }); + it('should look up the correct user and open the room when a phone number is dialled', async () => { + MatrixClientPeg.get().getThirdpartyUser = jest.fn().mockResolvedValue([{ + userid: '@user2:example.org', + protocol: "im.vector.protocol.sip_native", + fields: { + is_native: true, + lookup_success: true, + }, + }]); + + dis.dispatch({ + action: 'dial_number', + number: '01818118181', + }, true); + + const viewRoomPayload = await untilDispatch('view_room'); + expect(viewRoomPayload.room_id).toEqual(MAPPED_ROOM_ID); + }); + it('should move calls between rooms when remote asserted identity changes', async () => { - const realRoom = mkStubDM(REAL_ROOM_ID, '@user1:example.org'); - const mappedRoom = mkStubDM(MAPPED_ROOM_ID, '@user2:example.org'); - const mappedRoom2 = mkStubDM(MAPPED_ROOM_ID_2, '@user3:example.org'); - - MatrixClientPeg.get().getRoom = roomId => { - switch (roomId) { - case REAL_ROOM_ID: - return realRoom; - case MAPPED_ROOM_ID: - return mappedRoom; - case MAPPED_ROOM_ID_2: - return mappedRoom2; - } - }; - dis.dispatch({ action: 'place_call', type: PlaceCallType.Voice, room_id: REAL_ROOM_ID, }, true); - let dispatchHandle; // wait for the call to be set up - await new Promise(resolve => { - dispatchHandle = dis.register(payload => { - if (payload.action === 'call_state') { - resolve(); - } - }); - }); - dis.unregister(dispatchHandle); + await untilDispatch('call_state'); // should start off in the actual room ID it's in at the protocol level expect(callHandler.getCallForRoom(REAL_ROOM_ID)).toBe(fakeCall); From 0aeddea30f2f54d7cb06ae0a1c58be52b654d75a Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 2 Jun 2021 17:47:29 +0100 Subject: [PATCH 093/198] Only do native lookup if it's supported Also fix as bug where we were checking the wrong field to check for native/virtual support: oops. --- src/CallHandler.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index ba0178fa59..0d87451b5f 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -264,7 +264,7 @@ export default class CallHandler extends EventEmitter { } public getSupportsVirtualRooms() { - return this.supportsPstnProtocol; + return this.supportsSipNativeVirtual; } public pstnLookup(phoneNumber: string): Promise { @@ -883,10 +883,15 @@ export default class CallHandler extends EventEmitter { // Now check to see if this is a virtual user, in which case we should find the // native user - const nativeLookupResults = await this.sipNativeLookup(userId); - const lookupSuccess = nativeLookupResults.length > 0 && nativeLookupResults[0].fields.lookup_success; - const nativeUserId = lookupSuccess ? nativeLookupResults[0].userid : userId; - console.log("Looked up " + number + " to " + userId + " and mapped to native user " + nativeUserId); + let nativeUserId; + if (this.getSupportsVirtualRooms()) { + const nativeLookupResults = await this.sipNativeLookup(userId); + const lookupSuccess = nativeLookupResults.length > 0 && nativeLookupResults[0].fields.lookup_success; + nativeUserId = lookupSuccess ? nativeLookupResults[0].userid : userId; + console.log("Looked up " + number + " to " + userId + " and mapped to native user " + nativeUserId); + } else { + nativeUserId = userId; + } const roomId = await ensureDMExists(MatrixClientPeg.get(), nativeUserId); From 58652152c13039c485ccd4a72a92a7915698a6e2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 2 Jun 2021 17:52:26 +0100 Subject: [PATCH 094/198] Strings are now in a different place... --- src/i18n/strings/en_EN.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3d6fcb8643..ada264d110 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -63,6 +63,8 @@ "Already in call": "Already in call", "You're already in a call with this person.": "You're already in a call with this person.", "You cannot place a call with yourself.": "You cannot place a call with yourself.", + "Unable to look up phone number": "Unable to look up phone number", + "There was an error looking up the phone number": "There was an error looking up the phone number", "Call in Progress": "Call in Progress", "A call is currently being placed!": "A call is currently being placed!", "Permission Required": "Permission Required", @@ -898,8 +900,6 @@ "Fill Screen": "Fill Screen", "Return to call": "Return to call", "%(name)s on hold": "%(name)s on hold", - "Unable to look up phone number": "Unable to look up phone number", - "There was an error looking up the phone number": "There was an error looking up the phone number", "Dial pad": "Dial pad", "Unknown caller": "Unknown caller", "Incoming voice call": "Incoming voice call", From 53fc47553906b662d6b27796b3bca6e44a04bfaa Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Jun 2021 08:03:31 +0100 Subject: [PATCH 095/198] Update src/components/views/rooms/PinnedEventTile.tsx Co-authored-by: Travis Ralston --- src/components/views/rooms/PinnedEventTile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/PinnedEventTile.tsx b/src/components/views/rooms/PinnedEventTile.tsx index 83eee7d81d..774dea70c8 100644 --- a/src/components/views/rooms/PinnedEventTile.tsx +++ b/src/components/views/rooms/PinnedEventTile.tsx @@ -40,7 +40,7 @@ const AVATAR_SIZE = 24; @replaceableComponent("views.rooms.PinnedEventTile") export default class PinnedEventTile extends React.Component { - static contextType = MatrixClientContext; + public static contextType = MatrixClientContext; private onTileClicked = () => { dis.dispatch({ From 42a3ace82af9fec69948e2816346c79b9c472f7f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Jun 2021 08:35:12 +0100 Subject: [PATCH 096/198] Iterate PR based on feedback --- src/components/structures/NotificationPanel.tsx | 4 +--- src/components/structures/RightPanel.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/structures/NotificationPanel.tsx b/src/components/structures/NotificationPanel.tsx index 7e1566521d..3bfadf7110 100644 --- a/src/components/structures/NotificationPanel.tsx +++ b/src/components/structures/NotificationPanel.tsx @@ -31,7 +31,7 @@ interface IProps { * Component which shows the global notification list using a TimelinePanel */ @replaceableComponent("structures.NotificationPanel") -class NotificationPanel extends React.Component { +export default class NotificationPanel extends React.PureComponent { render() { const emptyState = (

    {_t('You’re all caught up')}

    @@ -62,5 +62,3 @@ class NotificationPanel extends React.Component { ; } } - -export default NotificationPanel; diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index e3237d98a6..353f50ad34 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -295,13 +295,13 @@ export default class RightPanel extends React.Component { break; case RightPanelPhases.NotificationPanel: - if (SettingsStore.getValue("feature_pinning")) { - panel = ; - } + panel = ; break; case RightPanelPhases.PinnedMessages: - panel = ; + if (SettingsStore.getValue("feature_pinning")) { + panel = ; + } break; case RightPanelPhases.FilePanel: From a34f8a29f4a58ce11ff8bbbf160ae292ac752ed0 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 3 Jun 2021 08:41:12 +0100 Subject: [PATCH 097/198] fix mx_Event containment rules and empty read avatar row --- res/css/structures/_RoomView.scss | 1 - src/components/views/rooms/EventTile.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/res/css/structures/_RoomView.scss b/res/css/structures/_RoomView.scss index b019fe7e01..09e1d58617 100644 --- a/res/css/structures/_RoomView.scss +++ b/res/css/structures/_RoomView.scss @@ -222,7 +222,6 @@ limitations under the License. .mx_RoomView_MessageList li { clear: both; - contain: content; } li.mx_RoomView_myReadMarker_container { diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 67df5a84ba..7b652cbba2 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -631,7 +631,7 @@ export default class EventTile extends React.Component { // return early if there are no read receipts if (!this.props.readReceipts || this.props.readReceipts.length === 0) { - return (); + return null; } const ReadReceiptMarker = sdk.getComponent('rooms.ReadReceiptMarker'); From 83d223475bb98af6e08587c701de14b525f25de9 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Jun 2021 08:41:22 +0100 Subject: [PATCH 098/198] delint imports --- .../structures/NotificationPanel.tsx | 4 +-- src/components/structures/RightPanel.tsx | 16 ++++----- src/components/structures/RoomView.tsx | 4 +-- .../views/context_menus/MessageContextMenu.js | 10 +++--- .../views/right_panel/GroupHeaderButtons.tsx | 12 +++---- .../views/right_panel/HeaderButton.tsx | 2 +- .../views/right_panel/HeaderButtons.tsx | 8 ++--- .../views/right_panel/RoomHeaderButtons.tsx | 14 ++++---- src/components/views/right_panel/UserInfo.tsx | 36 +++++++++---------- src/components/views/rooms/RoomHeader.js | 8 ++--- src/contexts/RoomContext.ts | 4 +-- src/hooks/useAsyncMemo.ts | 2 +- 12 files changed, 60 insertions(+), 60 deletions(-) diff --git a/src/components/structures/NotificationPanel.tsx b/src/components/structures/NotificationPanel.tsx index 3bfadf7110..b4f13f6b37 100644 --- a/src/components/structures/NotificationPanel.tsx +++ b/src/components/structures/NotificationPanel.tsx @@ -17,9 +17,9 @@ limitations under the License. import React from "react"; import { _t } from '../../languageHandler'; -import {MatrixClientPeg} from "../../MatrixClientPeg"; +import { MatrixClientPeg } from "../../MatrixClientPeg"; import BaseCard from "../views/right_panel/BaseCard"; -import {replaceableComponent} from "../../utils/replaceableComponent"; +import { replaceableComponent } from "../../utils/replaceableComponent"; import TimelinePanel from "./TimelinePanel"; import Spinner from "../views/elements/Spinner"; diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index 353f50ad34..294865fe08 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -16,11 +16,11 @@ limitations under the License. */ import React from 'react'; -import {Room} from "matrix-js-sdk/src/models/room"; -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"; +import { Room } from "matrix-js-sdk/src/models/room"; +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"; import dis from '../../dispatcher/dispatcher'; import RateLimitedFunc from '../../ratelimitedfunc'; @@ -32,12 +32,12 @@ import { } from "../../stores/RightPanelStorePhases"; import RightPanelStore from "../../stores/RightPanelStore"; import MatrixClientContext from "../../contexts/MatrixClientContext"; -import {Action} from "../../dispatcher/actions"; +import { Action } from "../../dispatcher/actions"; import RoomSummaryCard from "../views/right_panel/RoomSummaryCard"; import WidgetCard from "../views/right_panel/WidgetCard"; -import {replaceableComponent} from "../../utils/replaceableComponent"; +import { replaceableComponent } from "../../utils/replaceableComponent"; import SettingsStore from "../../settings/SettingsStore"; -import {ActionPayload} from "../../dispatcher/payloads"; +import { ActionPayload } from "../../dispatcher/payloads"; import MemberList from "../views/rooms/MemberList"; import GroupMemberList from "../views/groups/GroupMemberList"; import GroupRoomList from "../views/groups/GroupRoomList"; diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 482f4a45a7..fdfc5c2ada 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -46,7 +46,7 @@ import RoomViewStore from '../../stores/RoomViewStore'; import RoomScrollStateStore from '../../stores/RoomScrollStateStore'; import WidgetEchoStore from '../../stores/WidgetEchoStore'; import SettingsStore from "../../settings/SettingsStore"; -import {Layout} from "../../settings/Layout"; +import { Layout } from "../../settings/Layout"; import AccessibleButton from "../views/elements/AccessibleButton"; import RightPanelStore from "../../stores/RightPanelStore"; import { haveTileForEvent } from "../views/rooms/EventTile"; @@ -80,7 +80,7 @@ import { getKeyBindingsManager, RoomAction } from '../../KeyBindingsManager'; import { objectHasDiff } from "../../utils/objects"; import SpaceRoomView from "./SpaceRoomView"; import { IOpts } from "../../createRoom"; -import {replaceableComponent} from "../../utils/replaceableComponent"; +import { replaceableComponent } from "../../utils/replaceableComponent"; const DEBUG = false; let debuglog = function(msg: string) {}; diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index d9f21906fe..594b98b1f5 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -17,9 +17,9 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; -import {EventStatus} from 'matrix-js-sdk/src/models/event'; +import { EventStatus } from 'matrix-js-sdk/src/models/event'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; import dis from '../../../dispatcher/dispatcher'; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; @@ -28,9 +28,9 @@ import Resend from '../../../Resend'; import SettingsStore from '../../../settings/SettingsStore'; import { isUrlPermitted } from '../../../HtmlUtils'; import { isContentActionable } from '../../../utils/EventUtils'; -import {MenuItem} from "../../structures/ContextMenu"; -import {EventType} from "matrix-js-sdk/src/@types/event"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { MenuItem } from "../../structures/ContextMenu"; +import { EventType } from "matrix-js-sdk/src/@types/event"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard"; export function canCancel(eventStatus) { diff --git a/src/components/views/right_panel/GroupHeaderButtons.tsx b/src/components/views/right_panel/GroupHeaderButtons.tsx index 5ae5709d44..3c93cf6470 100644 --- a/src/components/views/right_panel/GroupHeaderButtons.tsx +++ b/src/components/views/right_panel/GroupHeaderButtons.tsx @@ -21,12 +21,12 @@ limitations under the License. import React from 'react'; import { _t } from '../../../languageHandler'; import HeaderButton from './HeaderButton'; -import HeaderButtons, {HeaderKind} from './HeaderButtons'; -import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; -import {Action} from "../../../dispatcher/actions"; -import {ActionPayload} from "../../../dispatcher/payloads"; -import {ViewUserPayload} from "../../../dispatcher/payloads/ViewUserPayload"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; +import HeaderButtons, { HeaderKind } from './HeaderButtons'; +import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; +import { Action } from "../../../dispatcher/actions"; +import { ActionPayload } from "../../../dispatcher/payloads"; +import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; const GROUP_PHASES = [ RightPanelPhases.GroupMemberInfo, diff --git a/src/components/views/right_panel/HeaderButton.tsx b/src/components/views/right_panel/HeaderButton.tsx index 8451f69fd3..cdf4f44e06 100644 --- a/src/components/views/right_panel/HeaderButton.tsx +++ b/src/components/views/right_panel/HeaderButton.tsx @@ -22,7 +22,7 @@ import React from 'react'; import classNames from 'classnames'; import Analytics from '../../../Analytics'; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; interface IProps { // Whether this button is highlighted diff --git a/src/components/views/right_panel/HeaderButtons.tsx b/src/components/views/right_panel/HeaderButtons.tsx index 0c7d9ee299..6d44a081d9 100644 --- a/src/components/views/right_panel/HeaderButtons.tsx +++ b/src/components/views/right_panel/HeaderButtons.tsx @@ -21,14 +21,14 @@ limitations under the License. import React from 'react'; import dis from '../../../dispatcher/dispatcher'; import RightPanelStore from "../../../stores/RightPanelStore"; -import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; -import {Action} from '../../../dispatcher/actions'; +import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; +import { Action } from '../../../dispatcher/actions'; import { SetRightPanelPhasePayload, SetRightPanelPhaseRefireParams, } from '../../../dispatcher/payloads/SetRightPanelPhasePayload'; -import {EventSubscription} from "fbemitter"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; +import type { EventSubscription } from "fbemitter"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; export enum HeaderKind { Room = "room", diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index 7ff7207fb6..54e18e4529 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -21,15 +21,15 @@ limitations under the License. import React from "react"; import { Room } from "matrix-js-sdk/src/models/room"; -import {_t} from '../../../languageHandler'; +import { _t } from '../../../languageHandler'; import HeaderButton from './HeaderButton'; -import HeaderButtons, {HeaderKind} from './HeaderButtons'; -import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; -import {Action} from "../../../dispatcher/actions"; -import {ActionPayload} from "../../../dispatcher/payloads"; +import HeaderButtons, { HeaderKind } from './HeaderButtons'; +import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; +import { Action } from "../../../dispatcher/actions"; +import { ActionPayload } from "../../../dispatcher/payloads"; import RightPanelStore from "../../../stores/RightPanelStore"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; -import {useSettingValue} from "../../../hooks/useSettings"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { useSettingValue } from "../../../hooks/useSettings"; import { useReadPinnedEvents, usePinnedEvents } from './PinnedMessagesCard'; const ROOM_INFO_PHASES = [ diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index 5a40b60111..cdbca59db9 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -17,19 +17,19 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import classNames from 'classnames'; -import {MatrixClient} from 'matrix-js-sdk/src/client'; -import {RoomMember} from 'matrix-js-sdk/src/models/room-member'; -import {User} from 'matrix-js-sdk/src/models/user'; -import {Room} from 'matrix-js-sdk/src/models/room'; -import {EventTimeline} from 'matrix-js-sdk/src/models/event-timeline'; -import {MatrixEvent} from 'matrix-js-sdk/src/models/event'; -import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; +import { MatrixClient } from 'matrix-js-sdk/src/client'; +import { RoomMember } from 'matrix-js-sdk/src/models/room-member'; +import { User } from 'matrix-js-sdk/src/models/user'; +import { Room } from 'matrix-js-sdk/src/models/room'; +import { EventTimeline } from 'matrix-js-sdk/src/models/event-timeline'; +import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; +import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; import dis from '../../../dispatcher/dispatcher'; import Modal from '../../../Modal'; -import {_t} from '../../../languageHandler'; +import { _t } from '../../../languageHandler'; import createRoom, { findDMForUser, privateShouldBeEncrypted } from '../../../createRoom'; import DMRoomMap from '../../../utils/DMRoomMap'; import AccessibleButton from '../elements/AccessibleButton'; @@ -40,18 +40,18 @@ import MultiInviter from "../../../utils/MultiInviter"; import GroupStore from "../../../stores/GroupStore"; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import E2EIcon from "../rooms/E2EIcon"; -import {useEventEmitter} from "../../../hooks/useEventEmitter"; -import {textualPowerLevel} from '../../../Roles'; +import { useEventEmitter } from "../../../hooks/useEventEmitter"; +import { textualPowerLevel } from '../../../Roles'; import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; +import { RightPanelPhases } from "../../../stores/RightPanelStorePhases"; import EncryptionPanel from "./EncryptionPanel"; -import {useAsyncMemo} from '../../../hooks/useAsyncMemo'; -import {legacyVerifyUser, verifyDevice, verifyUser} from '../../../verification'; -import {Action} from "../../../dispatcher/actions"; +import { useAsyncMemo } from '../../../hooks/useAsyncMemo'; +import { legacyVerifyUser, verifyDevice, verifyUser } from '../../../verification'; +import { Action } from "../../../dispatcher/actions"; import { USER_SECURITY_TAB } from "../dialogs/UserSettingsDialog"; -import {useIsEncrypted} from "../../../hooks/useIsEncrypted"; +import { useIsEncrypted } from "../../../hooks/useIsEncrypted"; import BaseCard from "./BaseCard"; -import {E2EStatus} from "../../../utils/ShieldUtils"; +import { E2EStatus } from "../../../utils/ShieldUtils"; import ImageView from "../elements/ImageView"; import Spinner from "../elements/Spinner"; import PowerSelector from "../elements/PowerSelector"; @@ -66,7 +66,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event"; import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload"; import RoomAvatar from "../avatars/RoomAvatar"; import RoomName from "../elements/RoomName"; -import {mediaFromMxc} from "../../../customisations/Media"; +import { mediaFromMxc } from "../../../customisations/Media"; export interface IDevice { deviceId: string; diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 217b3ea5c2..cb6bb0afca 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -19,10 +19,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { _t } from '../../../languageHandler'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; import RateLimitedFunc from '../../../ratelimitedfunc'; -import {CancelButton} from './SimpleRoomHeader'; +import { CancelButton } from './SimpleRoomHeader'; import SettingsStore from "../../../settings/SettingsStore"; import RoomHeaderButtons from '../right_panel/RoomHeaderButtons'; import E2EIcon from './E2EIcon'; @@ -30,8 +30,8 @@ import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import RoomTopic from "../elements/RoomTopic"; import RoomName from "../elements/RoomName"; -import {PlaceCallType} from "../../../CallHandler"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { PlaceCallType } from "../../../CallHandler"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; @replaceableComponent("views.rooms.RoomHeader") export default class RoomHeader extends React.Component { diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts index 7f2c0bb157..e925f8624b 100644 --- a/src/contexts/RoomContext.ts +++ b/src/contexts/RoomContext.ts @@ -16,8 +16,8 @@ limitations under the License. import { createContext } from "react"; -import {IState} from "../components/structures/RoomView"; -import {Layout} from "../settings/Layout"; +import { IState } from "../components/structures/RoomView"; +import { Layout } from "../settings/Layout"; const RoomContext = createContext({ roomLoading: true, diff --git a/src/hooks/useAsyncMemo.ts b/src/hooks/useAsyncMemo.ts index eda44b1ae4..1776ec1d36 100644 --- a/src/hooks/useAsyncMemo.ts +++ b/src/hooks/useAsyncMemo.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {useState, useEffect, DependencyList} from 'react'; +import { useState, useEffect, DependencyList } from 'react'; type Fn = () => Promise; From 8ef95a62378a8fca563aa094ee897da06e05c438 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 3 Jun 2021 14:38:13 +0100 Subject: [PATCH 099/198] Interface dispatcher payload & use constant in test --- src/components/views/voip/DialPadModal.tsx | 6 +++-- src/dispatcher/actions.ts | 4 +--- src/dispatcher/payloads/DialNumberPayload.ts | 23 ++++++++++++++++++++ test/CallHandler-test.ts | 4 +++- 4 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 src/dispatcher/payloads/DialNumberPayload.ts diff --git a/src/components/views/voip/DialPadModal.tsx b/src/components/views/voip/DialPadModal.tsx index f3ee49e4ac..8c0af5e81a 100644 --- a/src/components/views/voip/DialPadModal.tsx +++ b/src/components/views/voip/DialPadModal.tsx @@ -21,6 +21,7 @@ import Field from "../elements/Field"; import DialPad from './DialPad'; import dis from '../../../dispatcher/dispatcher'; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { DialNumberPayload } from "../../../dispatcher/payloads/DialNumberPayload"; import { Action } from "../../../dispatcher/actions"; interface IProps { @@ -63,10 +64,11 @@ export default class DialpadModal extends React.PureComponent { } onDialPress = async () => { - dis.dispatch({ + const payload: DialNumberPayload = { action: Action.DialNumber, number: this.state.value, - }) + }; + dis.dispatch(payload); this.props.onFinished(true); } diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 073e82bef6..300eed2b98 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -102,9 +102,7 @@ export enum Action { /** * Dial the phone number in the payload - * payload: { - * number: , - * } + * payload: DialNumberPayload */ DialNumber = "dial_number", diff --git a/src/dispatcher/payloads/DialNumberPayload.ts b/src/dispatcher/payloads/DialNumberPayload.ts new file mode 100644 index 0000000000..1b591b9f6b --- /dev/null +++ b/src/dispatcher/payloads/DialNumberPayload.ts @@ -0,0 +1,23 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { ActionPayload } from "../payloads"; +import { Action } from "../actions"; + +export interface DialNumberPayload extends ActionPayload { + action: Action.DialNumber; + number: string; +} diff --git a/test/CallHandler-test.ts b/test/CallHandler-test.ts index a93a134133..12316ac01c 100644 --- a/test/CallHandler-test.ts +++ b/test/CallHandler-test.ts @@ -25,6 +25,8 @@ import DMRoomMap from '../src/utils/DMRoomMap'; import EventEmitter from 'events'; import SdkConfig from '../src/SdkConfig'; import { ActionPayload } from '../src/dispatcher/payloads'; +import { Actions } from '../src/notifications/types'; +import { Action } from '../src/dispatcher/actions'; const REAL_ROOM_ID = '$room1:example.org'; const MAPPED_ROOM_ID = '$room2:example.org'; @@ -172,7 +174,7 @@ describe('CallHandler', () => { }]); dis.dispatch({ - action: 'dial_number', + action: Action.DialNumber, number: '01818118181', }, true); From 5ef78f43a42005ed09f3f1bc02eb55170d8c0487 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 3 Jun 2021 16:26:20 +0100 Subject: [PATCH 100/198] fix containment rule to keep height when resizing vertically --- res/css/views/rooms/_RoomTile.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss index 421fbfe39b..03146e0325 100644 --- a/res/css/views/rooms/_RoomTile.scss +++ b/res/css/views/rooms/_RoomTile.scss @@ -19,7 +19,7 @@ limitations under the License. margin-bottom: 4px; padding: 4px; - contain: strict; + contain: content; // Not strict as it will break when resizing a sublist vertically height: 40px; box-sizing: border-box; From 5d0d81e79acd06ba9b024e312bfb888844f99a9e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 3 Jun 2021 19:37:26 +0100 Subject: [PATCH 101/198] not sure how I butchered this merge conflict resolution this much. --- src/components/structures/RoomView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 4390d21783..5ffc2fd0da 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -79,8 +79,8 @@ import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutS import { getKeyBindingsManager, RoomAction } from '../../KeyBindingsManager'; import { objectHasDiff } from "../../utils/objects"; import SpaceRoomView from "./SpaceRoomView"; -import { IOpts } from "../../createRoom -import { replaceableComponent } from "../../utils/replaceableComponent +import { IOpts } from "../../createRoom"; +import { replaceableComponent } from "../../utils/replaceableComponent"; import { omit } from 'lodash'; import UIStore from "../../stores/UIStore"; From 48bd6583ed3e1431a995074dcb50ca47146f61a0 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 4 Jun 2021 11:34:54 +0100 Subject: [PATCH 102/198] Fix unpinning of pinned messages --- src/components/views/right_panel/PinnedMessagesCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index a3f1f2d9df..55809ee02b 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -155,7 +155,7 @@ const PinnedMessagesCard = ({ room, onClose }: IProps) => { // show them in reverse, with latest pinned at the top content = pinnedEvents.filter(Boolean).reverse().map(ev => ( - + onUnpinClicked(ev)} /> )); } else { content =
    From 93f41ce0ab484172a76f0e07e4036eace2fe9bce Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 4 Jun 2021 11:35:17 +0100 Subject: [PATCH 103/198] Actually finish the empty state for the pinned messages card --- .../right_panel/_PinnedMessagesCard.scss | 55 +++++++++++++++++++ .../views/right_panel/PinnedMessagesCard.tsx | 17 +++++- src/i18n/strings/en_EN.json | 6 +- 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/res/css/views/right_panel/_PinnedMessagesCard.scss b/res/css/views/right_panel/_PinnedMessagesCard.scss index b6b8238bed..785aee09ca 100644 --- a/res/css/views/right_panel/_PinnedMessagesCard.scss +++ b/res/css/views/right_panel/_PinnedMessagesCard.scss @@ -32,4 +32,59 @@ limitations under the License. margin-right: 6px; } } + + .mx_PinnedMessagesCard_empty { + display: flex; + height: 100%; + + > div { + height: max-content; + text-align: center; + margin: auto 40px; + + .mx_PinnedMessagesCard_MessageActionBar { + pointer-events: none; + display: flex; + height: 32px; + line-height: $font-24px; + border-radius: 8px; + background: $primary-bg-color; + border: 1px solid $input-border-color; + padding: 1px; + width: max-content; + margin: 0 auto; + box-sizing: border-box; + + .mx_MessageActionBar_maskButton { + display: inline-block; + position: relative; + } + + .mx_MessageActionBar_optionsButton { + background: $roomlist-button-bg-color; + border-radius: 6px; + z-index: 1; + + &::after { + background-color: $primary-fg-color; + } + } + } + + > h2 { + font-weight: $font-semi-bold; + font-size: $font-15px; + line-height: $font-24px; + color: $primary-fg-color; + margin-top: 24px; + margin-bottom: 20px; + } + + > span { + font-size: $font-12px; + line-height: $font-15px; + color: $secondary-fg-color; + } + } + } } diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 55809ee02b..3a4dc9cc92 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -158,9 +158,20 @@ const PinnedMessagesCard = ({ room, onClose }: IProps) => { onUnpinClicked(ev)} /> )); } else { - content =
    -

    {_t("You’re all caught up")}

    -

    {_t("You have no visible notifications.")}

    + content =
    +
    +
    +
    +
    +
    +
    + +

    { _t("Nothing pinned, yet") }

    + { _t("If you have permissions, open the menu on any message and select " + + "Pin to stick them here.", {}, { + b: sub => { sub }, + }) } +
    ; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9e85ea28c8..302c8709fa 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1717,8 +1717,8 @@ "The homeserver the user you’re verifying is connected to": "The homeserver the user you’re verifying is connected to", "Yours, or the other users’ internet connection": "Yours, or the other users’ internet connection", "Yours, or the other users’ session": "Yours, or the other users’ session", - "You’re all caught up": "You’re all caught up", - "You have no visible notifications.": "You have no visible notifications.", + "Nothing pinned, yet": "Nothing pinned, yet", + "If you have permissions, open the menu on any message and select Pin to stick them here.": "If you have permissions, open the menu on any message and select Pin to stick them here.", "Pinned messages": "Pinned messages", "Room Info": "Room Info", "You can only pin up to %(count)s widgets|other": "You can only pin up to %(count)s widgets", @@ -2628,6 +2628,8 @@ "Create a new community": "Create a new community", "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.", "Communities are changing to Spaces": "Communities are changing to Spaces", + "You’re all caught up": "You’re all caught up", + "You have no visible notifications.": "You have no visible notifications.", "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.": "%(brand)s failed to get the protocol list from the homeserver. The homeserver may be too old to support third party networks.", "%(brand)s failed to get the public room list.": "%(brand)s failed to get the public room list.", "The homeserver may be unavailable or overloaded.": "The homeserver may be unavailable or overloaded.", From ab96d5f8af9d4b5afa01f355d4ec7e68d41239bb Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 4 Jun 2021 11:54:58 +0100 Subject: [PATCH 104/198] Repair event status position in timeline https://github.com/matrix-org/matrix-react-sdk/pull/6079 caused a regression in the event status indicator. The `mx_EventTile_msgOption` container was folded into the avatars code path, but the event status is a special case of this, so it now needs to also have this container to preserve its positioning. Fixes https://github.com/vector-im/element-web/issues/17552 --- src/components/views/rooms/EventTile.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index d3057fed62..8cec067c39 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -1340,11 +1340,15 @@ class SentReceipt extends React.PureComponent; } - return - - {nonCssBadge} - {tooltip} - - ; + return ( +
    + + + {nonCssBadge} + {tooltip} + + +
    + ); } } From 31604c13c04f1a0738038725718889ca9d47ca0a Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 4 Jun 2021 16:52:50 +0100 Subject: [PATCH 105/198] Lint the typescript tests Turns out we hadn't told eslint to lint .ts in tests/ Also fix all the lint errors, including removing a use of assert that had randomly crept in. --- .eslintrc.js | 2 +- test/CallHandler-test.ts | 1 - test/KeyBindingsManager-test.ts | 98 ++++++++++++++++----------------- test/stores/SpaceStore-test.ts | 2 +- 4 files changed, 50 insertions(+), 53 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 9ae51f9bc5..c759eae9e3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -18,7 +18,7 @@ module.exports = { }, overrides: [{ - "files": ["src/**/*.{ts,tsx}"], + "files": ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx}"], "extends": ["matrix-org/ts"], "rules": { // We're okay being explicit at the moment diff --git a/test/CallHandler-test.ts b/test/CallHandler-test.ts index 12316ac01c..4c3dd25fb4 100644 --- a/test/CallHandler-test.ts +++ b/test/CallHandler-test.ts @@ -25,7 +25,6 @@ import DMRoomMap from '../src/utils/DMRoomMap'; import EventEmitter from 'events'; import SdkConfig from '../src/SdkConfig'; import { ActionPayload } from '../src/dispatcher/payloads'; -import { Actions } from '../src/notifications/types'; import { Action } from '../src/dispatcher/actions'; const REAL_ROOM_ID = '$room1:example.org'; diff --git a/test/KeyBindingsManager-test.ts b/test/KeyBindingsManager-test.ts index 41614b61fa..694efac7b5 100644 --- a/test/KeyBindingsManager-test.ts +++ b/test/KeyBindingsManager-test.ts @@ -15,7 +15,6 @@ limitations under the License. */ import { isKeyComboMatch, KeyCombo } from '../src/KeyBindingsManager'; -const assert = require('assert'); function mockKeyEvent(key: string, modifiers?: { ctrlKey?: boolean, @@ -28,7 +27,7 @@ function mockKeyEvent(key: string, modifiers?: { ctrlKey: modifiers?.ctrlKey ?? false, altKey: modifiers?.altKey ?? false, shiftKey: modifiers?.shiftKey ?? false, - metaKey: modifiers?.metaKey ?? false + metaKey: modifiers?.metaKey ?? false, } as KeyboardEvent; } @@ -37,9 +36,8 @@ describe('KeyBindingsManager', () => { const combo1: KeyCombo = { key: 'k', }; - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo1, false), true); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('n'), combo1, false), false); - + expect(isKeyComboMatch(mockKeyEvent('k'), combo1, false)).toBe(true); + expect(isKeyComboMatch(mockKeyEvent('n'), combo1, false)).toBe(false); }); it('should match key + modifier key combo', () => { @@ -47,38 +45,38 @@ describe('KeyBindingsManager', () => { key: 'k', ctrlKey: true, }; - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false), true); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo, false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true }), combo, false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true, metaKey: true }), combo, false), false); + expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false)).toBe(true); + expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('k'), combo, false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true }), combo, false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true, metaKey: true }), combo, false)).toBe(false); const combo2: KeyCombo = { key: 'k', metaKey: true, }; - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo2, false), true); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { metaKey: true }), combo2, false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo2, false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { altKey: true, metaKey: true }), combo2, false), false); + expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo2, false)).toBe(true); + expect(isKeyComboMatch(mockKeyEvent('n', { metaKey: true }), combo2, false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('k'), combo2, false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('k', { altKey: true, metaKey: true }), combo2, false)).toBe(false); const combo3: KeyCombo = { key: 'k', altKey: true, }; - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { altKey: true }), combo3, false), true); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { altKey: true }), combo3, false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo3, false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo3, false), false); + expect(isKeyComboMatch(mockKeyEvent('k', { altKey: true }), combo3, false)).toBe(true); + expect(isKeyComboMatch(mockKeyEvent('n', { altKey: true }), combo3, false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('k'), combo3, false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo3, false)).toBe(false); const combo4: KeyCombo = { key: 'k', shiftKey: true, }; - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true }), combo4, false), true); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { shiftKey: true }), combo4, false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k'), combo4, false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true, ctrlKey: true }), combo4, false), false); + expect(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true }), combo4, false)).toBe(true); + expect(isKeyComboMatch(mockKeyEvent('n', { shiftKey: true }), combo4, false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('k'), combo4, false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('k', { shiftKey: true, ctrlKey: true }), combo4, false)).toBe(false); }); it('should match key + multiple modifiers key combo', () => { @@ -87,11 +85,11 @@ describe('KeyBindingsManager', () => { ctrlKey: true, altKey: true, }; - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, false), true); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true, altKey: true }), combo, false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo, false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true, shiftKey: true }), combo, - false), false); + expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, false)).toBe(true); + expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true, altKey: true }), combo, false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo, false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true, shiftKey: true }), combo, + false)).toBe(false); const combo2: KeyCombo = { key: 'k', @@ -99,13 +97,13 @@ describe('KeyBindingsManager', () => { shiftKey: true, altKey: true, }; - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, shiftKey: true, altKey: true }), combo2, - false), true); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true, shiftKey: true, altKey: true }), combo2, - false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo2, false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', - { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo2, false), false); + expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, shiftKey: true, altKey: true }), combo2, + false)).toBe(true); + expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true, shiftKey: true, altKey: true }), combo2, + false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, metaKey: true }), combo2, false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('k', + { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo2, false)).toBe(false); const combo3: KeyCombo = { key: 'k', @@ -114,12 +112,12 @@ describe('KeyBindingsManager', () => { altKey: true, metaKey: true, }; - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', - { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo3, false), true); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', - { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo3, false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', - { ctrlKey: true, shiftKey: true, altKey: true }), combo3, false), false); + expect(isKeyComboMatch(mockKeyEvent('k', + { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo3, false)).toBe(true); + expect(isKeyComboMatch(mockKeyEvent('n', + { ctrlKey: true, shiftKey: true, altKey: true, metaKey: true }), combo3, false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('k', + { ctrlKey: true, shiftKey: true, altKey: true }), combo3, false)).toBe(false); }); it('should match ctrlOrMeta key combo', () => { @@ -128,13 +126,13 @@ describe('KeyBindingsManager', () => { ctrlOrCmd: true, }; // PC: - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false), true); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo, false), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, false), false); + expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, false)).toBe(true); + expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo, false)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, false)).toBe(false); // MAC: - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo, true), true); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, true), false); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, true), false); + expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true }), combo, true)).toBe(true); + expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true }), combo, true)).toBe(false); + expect(isKeyComboMatch(mockKeyEvent('n', { ctrlKey: true }), combo, true)).toBe(false); }); it('should match advanced ctrlOrMeta key combo', () => { @@ -144,10 +142,10 @@ describe('KeyBindingsManager', () => { altKey: true, }; // PC: - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, false), true); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true, altKey: true }), combo, false), false); + expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, false)).toBe(true); + expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true, altKey: true }), combo, false)).toBe(false); // MAC: - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { metaKey: true, altKey: true }), combo, true), true); - assert.strictEqual(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, true), false); + expect(isKeyComboMatch(mockKeyEvent('k', { metaKey: true, altKey: true }), combo, true)).toBe(true); + expect(isKeyComboMatch(mockKeyEvent('k', { ctrlKey: true, altKey: true }), combo, true)).toBe(false); }); }); diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts index 20c48c29db..01bd528b87 100644 --- a/test/stores/SpaceStore-test.ts +++ b/test/stores/SpaceStore-test.ts @@ -21,7 +21,7 @@ import "../skinned-sdk"; // Must be first for skinning to work import SpaceStore, { UPDATE_INVITED_SPACES, UPDATE_SELECTED_SPACE, - UPDATE_TOP_LEVEL_SPACES + UPDATE_TOP_LEVEL_SPACES, } from "../../src/stores/SpaceStore"; import { resetAsyncStoreWithClient, setupAsyncStoreWithClient } from "../utils/test-utils"; import { mkEvent, mkStubRoom, stubClient } from "../test-utils"; From 40d2f8228f4d2a3f7f386d320c42ae1970932ee4 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sat, 5 Jun 2021 01:16:52 -0400 Subject: [PATCH 106/198] Fix watching settings An accidental variable shadowing was preventing setting watcher callbacks from being fired. Signed-off-by: Robin Townsend --- src/settings/WatchManager.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/settings/WatchManager.ts b/src/settings/WatchManager.ts index 56f911f180..744d75b136 100644 --- a/src/settings/WatchManager.ts +++ b/src/settings/WatchManager.ts @@ -63,8 +63,7 @@ export class WatchManager { if (!inRoomId) { // Fire updates to all the individual room watchers too, as they probably care about the change higher up. - const callbacks = Array.from(roomWatchers.values()).flat(1); - callbacks.push(...callbacks); + callbacks.push(...Array.from(roomWatchers.values()).flat(1)); } else if (roomWatchers.has(IRRELEVANT_ROOM)) { callbacks.push(...roomWatchers.get(IRRELEVANT_ROOM)); } From 48d3e41351bd869c232176d956d8254d17f5dc77 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sat, 5 Jun 2021 01:23:51 -0400 Subject: [PATCH 107/198] Cache frequently used settings values in RoomContext Signed-off-by: Robin Townsend --- src/components/structures/RoomView.tsx | 64 +++++++++++++++++++++----- src/contexts/RoomContext.ts | 6 ++- 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 5ffc2fd0da..fa67013ccb 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -155,7 +155,6 @@ export interface IState { canPeek: boolean; showApps: boolean; isPeeking: boolean; - showReadReceipts: boolean; showRightPanel: boolean; // error object, as from the matrix client/server API // If we failed to load information about the room, @@ -183,6 +182,11 @@ export interface IState { canReact: boolean; canReply: boolean; layout: Layout; + showReadReceipts: boolean; + showRedactions: boolean; + showJoinLeaves: boolean; + showAvatarChanges: boolean; + showDisplaynameChanges: boolean; matrixClientIsReady: boolean; showUrlPreview?: boolean; e2eStatus?: E2EStatus; @@ -200,8 +204,7 @@ export default class RoomView extends React.Component { private readonly dispatcherRef: string; private readonly roomStoreToken: EventSubscription; private readonly rightPanelStoreToken: EventSubscription; - private readonly showReadReceiptsWatchRef: string; - private readonly layoutWatcherRef: string; + private settingWatchers: string[]; private unmounted = false; private permalinkCreators: Record = {}; @@ -232,7 +235,6 @@ export default class RoomView extends React.Component { canPeek: false, showApps: false, isPeeking: false, - showReadReceipts: true, showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom, joining: false, atEndOfLiveTimeline: true, @@ -242,6 +244,11 @@ export default class RoomView extends React.Component { canReact: false, canReply: false, layout: SettingsStore.getValue("layout"), + showReadReceipts: true, + showRedactions: true, + showJoinLeaves: true, + showAvatarChanges: true, + showDisplaynameChanges: true, matrixClientIsReady: this.context && this.context.isInitialSyncComplete(), dragCounter: 0, }; @@ -268,9 +275,11 @@ export default class RoomView extends React.Component { WidgetEchoStore.on(UPDATE_EVENT, this.onWidgetEchoStoreUpdate); WidgetStore.instance.on(UPDATE_EVENT, this.onWidgetStoreUpdate); - this.showReadReceiptsWatchRef = SettingsStore.watchSetting("showReadReceipts", null, - this.onReadReceiptsChange); - this.layoutWatcherRef = SettingsStore.watchSetting("layout", null, this.onLayoutChange); + this.settingWatchers = [ + SettingsStore.watchSetting("layout", null, () => + this.setState({ layout: SettingsStore.getValue("layout") }), + ), + ]; } private onWidgetStoreUpdate = () => { @@ -327,9 +336,42 @@ export default class RoomView extends React.Component { // we should only peek once we have a ready client shouldPeek: this.state.matrixClientIsReady && RoomViewStore.shouldPeek(), showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId), + showRedactions: SettingsStore.getValue("showRedactions", roomId), + showJoinLeaves: SettingsStore.getValue("showJoinLeaves", roomId), + showAvatarChanges: SettingsStore.getValue("showAvatarChanges", roomId), + showDisplaynameChanges: SettingsStore.getValue("showDisplaynameChanges", roomId), wasContextSwitch: RoomViewStore.getWasContextSwitch(), }; + // Add watchers for each of the settings we just looked up + this.settingWatchers = this.settingWatchers.concat([ + SettingsStore.watchSetting("showReadReceipts", null, () => + this.setState({ + showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId), + }), + ), + SettingsStore.watchSetting("showRedactions", null, () => + this.setState({ + showRedactions: SettingsStore.getValue("showRedactions", roomId), + }), + ), + SettingsStore.watchSetting("showJoinLeaves", null, () => + this.setState({ + showJoinLeaves: SettingsStore.getValue("showJoinLeaves", roomId), + }), + ), + SettingsStore.watchSetting("showAvatarChanges", null, () => + this.setState({ + showAvatarChanges: SettingsStore.getValue("showAvatarChanges", roomId), + }), + ), + SettingsStore.watchSetting("showDisplaynameChanges", null, () => + this.setState({ + showDisplaynameChanges: SettingsStore.getValue("showDisplaynameChanges", roomId), + }), + ), + ]); + if (!initial && this.state.shouldPeek && !newState.shouldPeek) { // Stop peeking because we have joined this room now this.context.stopPeeking(); @@ -638,10 +680,6 @@ export default class RoomView extends React.Component { ); } - if (this.showReadReceiptsWatchRef) { - SettingsStore.unwatchSetting(this.showReadReceiptsWatchRef); - } - // cancel any pending calls to the rate_limited_funcs this.updateRoomMembers.cancelPendingCall(); @@ -649,7 +687,9 @@ export default class RoomView extends React.Component { // console.log("Tinter.tint from RoomView.unmount"); // Tinter.tint(); // reset colourscheme - SettingsStore.unwatchSetting(this.layoutWatcherRef); + for (const watcher of this.settingWatchers) { + SettingsStore.unwatchSetting(watcher); + } } private onUserScroll = () => { diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts index e925f8624b..1efa1c03e7 100644 --- a/src/contexts/RoomContext.ts +++ b/src/contexts/RoomContext.ts @@ -31,7 +31,6 @@ const RoomContext = createContext({ canPeek: false, showApps: false, isPeeking: false, - showReadReceipts: true, showRightPanel: true, joining: false, atEndOfLiveTimeline: true, @@ -41,6 +40,11 @@ const RoomContext = createContext({ canReact: false, canReply: false, layout: Layout.Group, + showReadReceipts: true, + showRedactions: true, + showJoinLeaves: true, + showAvatarChanges: true, + showDisplaynameChanges: true, matrixClientIsReady: false, dragCounter: 0, }); From 13196b8146919f8ef781904d02c1edfd5f376dfe Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sat, 5 Jun 2021 01:25:01 -0400 Subject: [PATCH 108/198] Prefer cached settings values in shouldHideEvent Signed-off-by: Robin Townsend --- src/shouldHideEvent.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/shouldHideEvent.ts b/src/shouldHideEvent.ts index 2a47b9c417..31d610b28b 100644 --- a/src/shouldHideEvent.ts +++ b/src/shouldHideEvent.ts @@ -17,6 +17,7 @@ import {MatrixEvent} from "matrix-js-sdk/src/models/event"; import SettingsStore from "./settings/SettingsStore"; +import {IState} from "./components/structures/RoomView"; interface IDiff { isMemberEvent: boolean; @@ -47,11 +48,18 @@ function memberEventDiff(ev: MatrixEvent): IDiff { return diff; } -export default function shouldHideEvent(ev: MatrixEvent): boolean { - // Wrap getValue() for readability. Calling the SettingsStore can be - // fairly resource heavy, so the checks below should avoid hitting it - // where possible. - const isEnabled = (name) => SettingsStore.getValue(name, ev.getRoomId()); +/** + * Determines whether the given event should be hidden from timelines. + * @param ev The event + * @param ctx An optional RoomContext to pull cached settings values from to avoid + * hitting the settings store + */ +export default function shouldHideEvent(ev: MatrixEvent, ctx?: IState): boolean { + // Accessing the settings store directly can be expensive if done frequently, + // so we should prefer using cached values if a RoomContext is available + const isEnabled = ctx ? + name => ctx[name] : + name => SettingsStore.getValue(name, ev.getRoomId()); // Hide redacted events if (ev.isRedacted() && !isEnabled('showRedactions')) return true; From 3bf8e54d7f37477868c1fa229449749bd02f2894 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sat, 5 Jun 2021 01:25:43 -0400 Subject: [PATCH 109/198] Use cached RoomContext settings values throughout rooms Signed-off-by: Robin Townsend --- src/components/structures/MessagePanel.js | 5 ++++- src/components/structures/RoomView.tsx | 2 +- src/components/structures/TimelinePanel.js | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 6709fef814..bca62e7b2f 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -26,6 +26,7 @@ import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import SettingsStore from '../../settings/SettingsStore'; +import RoomContext from "../../contexts/RoomContext"; import {Layout, LayoutPropType} from "../../settings/Layout"; import {_t} from "../../languageHandler"; import {haveTileForEvent} from "../views/rooms/EventTile"; @@ -152,6 +153,8 @@ export default class MessagePanel extends React.Component { enableFlair: PropTypes.bool, }; + static contextType = RoomContext; + constructor(props) { super(props); @@ -381,7 +384,7 @@ export default class MessagePanel extends React.Component { // Always show highlighted event if (this.props.highlightedEventId === mxEv.getId()) return true; - return !shouldHideEvent(mxEv); + return !shouldHideEvent(mxEv, this.context); } _readMarkerForEvent(eventId, isLastEvent) { diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index fa67013ccb..b80d909a94 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -859,7 +859,7 @@ export default class RoomView extends React.Component { // update unread count when scrolled up if (!this.state.searchResults && this.state.atEndOfLiveTimeline) { // no change - } else if (!shouldHideEvent(ev)) { + } else if (!shouldHideEvent(ev, this.state)) { this.setState((state, props) => { return {numUnreadMessages: state.numUnreadMessages + 1}; }); diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 6300c7532e..c5ae2e87ba 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -26,6 +26,7 @@ import {EventTimeline} from "matrix-js-sdk/src/models/event-timeline"; import {TimelineWindow} from "matrix-js-sdk/src/timeline-window"; import { _t } from '../../languageHandler'; import {MatrixClientPeg} from "../../MatrixClientPeg"; +import RoomContext from "../../contexts/RoomContext"; import UserActivity from "../../UserActivity"; import Modal from "../../Modal"; import dis from "../../dispatcher/dispatcher"; @@ -122,6 +123,8 @@ class TimelinePanel extends React.Component { layout: LayoutPropType, } + static contextType = RoomContext; + // a map from room id to read marker event timestamp static roomReadMarkerTsMap = {}; @@ -1285,7 +1288,7 @@ class TimelinePanel extends React.Component { const shouldIgnore = !!ev.status || // local echo (ignoreOwn && ev.sender && ev.sender.userId == myUserId); // own message - const isWithoutTile = !haveTileForEvent(ev) || shouldHideEvent(ev); + const isWithoutTile = !haveTileForEvent(ev) || shouldHideEvent(ev, this.context); if (isWithoutTile || !node) { // don't start counting if the event should be ignored, From c24b239478aef6e4e19e3651a9f8376753f17f12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 5 Jun 2021 13:36:25 +0000 Subject: [PATCH 110/198] Bump ws from 6.2.1 to 6.2.2 in /test/end-to-end-tests Bumps [ws](https://github.com/websockets/ws) from 6.2.1 to 6.2.2. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/commits) --- updated-dependencies: - dependency-name: ws dependency-type: indirect ... Signed-off-by: dependabot[bot] --- test/end-to-end-tests/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/end-to-end-tests/yarn.lock b/test/end-to-end-tests/yarn.lock index 97b348fe50..bc942c4f51 100644 --- a/test/end-to-end-tests/yarn.lock +++ b/test/end-to-end-tests/yarn.lock @@ -760,9 +760,9 @@ wrappy@1: integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= ws@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" - integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + version "6.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.2.tgz#dd5cdbd57a9979916097652d78f1cc5faea0c32e" + integrity sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw== dependencies: async-limiter "~1.0.0" From 0f64f4d692f36287ace222eb13e07244fa6e1c63 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sat, 5 Jun 2021 10:49:44 -0400 Subject: [PATCH 111/198] Fix MessagePanel tests Signed-off-by: Robin Townsend --- test/components/structures/MessagePanel-test.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/components/structures/MessagePanel-test.js b/test/components/structures/MessagePanel-test.js index 5b466b4bb0..4f7fca1759 100644 --- a/test/components/structures/MessagePanel-test.js +++ b/test/components/structures/MessagePanel-test.js @@ -51,8 +51,20 @@ class WrappedMessagePanel extends React.Component { }; render() { + const roomContext = { + room, + roomId: room.roomId, + canReact: true, + canReply: true, + showReadReceipts: true, + showRedactions: false, + showJoinLeaves: false, + showAvatarChanges: false, + showDisplaynameChanges: true, + }; + return - + ; From bbd5fab7b5345f3147f8392907fb114129b6c74e Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sat, 5 Jun 2021 11:15:06 -0400 Subject: [PATCH 112/198] Fix type check As TypeScript points out, you can only set an id on HTML elements, not arbitrary components. Signed-off-by: Robin Townsend --- src/components/views/dialogs/ForwardDialog.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index cba292ea25..355c92240e 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -214,7 +214,7 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr />

    -
    +
    = ({ matrixClient: cli, event, permalinkCr autoComplete={true} autoFocus={true} /> - + { rooms.length > 0 ? (
    { rooms.map(room => From b06da16a8567e74b90ef74b860e23f6c04399d89 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sat, 5 Jun 2021 13:20:27 -0400 Subject: [PATCH 113/198] Fix jumping to bottom without a highlighted event Signed-off-by: Robin Townsend --- src/components/structures/RoomView.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 5ffc2fd0da..135057dbb1 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1526,10 +1526,19 @@ export default class RoomView extends React.Component { // jump down to the bottom of this room, where new events are arriving private jumpToLiveTimeline = () => { - dis.dispatch({ - action: 'view_room', - room_id: this.state.room.roomId, - }); + if (this.state.initialEventId && this.state.isInitialEventHighlighted) { + // If we were viewing a highlighted event, firing view_room without + // an event will take care of both clearing the URL fragment and + // jumping to the bottom + dis.dispatch({ + action: 'view_room', + room_id: this.state.room.roomId, + }); + } else { + // Otherwise we have to jump manually + this.messagePanel.jumpToLiveTimeline(); + dis.fire(Action.FocusComposer); + } }; // jump up to wherever our read marker is From 17edfec3aa5adf594dec54672c3d8c4332c91a8c Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sat, 5 Jun 2021 21:08:44 -0400 Subject: [PATCH 114/198] Make it easier to pan images in the lightbox Previously, if you were dragging an image and your cursor outpaced the edge of the image as it was moving, panning would abruptly stop. This moves a few of the lightbox event listeners one level up to the image wrapper to ensure that all drag movements are detected, even if they don't end over the image's current position. Signed-off-by: Robin Townsend --- res/css/views/elements/_ImageView.scss | 6 +++--- src/components/views/elements/ImageView.tsx | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index 71035dadc3..da23957b36 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -22,6 +22,7 @@ limitations under the License. } .mx_ImageView_image_wrapper { + pointer-events: initial; display: flex; justify-content: center; align-items: center; @@ -30,7 +31,6 @@ limitations under the License. } .mx_ImageView_image { - pointer-events: all; flex-shrink: 0; } @@ -43,7 +43,7 @@ limitations under the License. } .mx_ImageView_info_wrapper { - pointer-events: all; + pointer-events: initial; padding-left: 32px; display: flex; flex-direction: row; @@ -63,7 +63,7 @@ limitations under the License. .mx_ImageView_toolbar { padding-right: 16px; - pointer-events: all; + pointer-events: initial; display: flex; align-items: center; } diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index df73e1a8cb..d2f09917ad 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -471,7 +471,12 @@ export default class ImageView extends React.Component {
    + ref={this.imageWrapper} + onMouseDown={this.props.onFinished} + onMouseMove={this.onMoving} + onMouseUp={this.onEndMoving} + onMouseLeave={this.onEndMoving} + > { className="mx_ImageView_image" draggable={true} onMouseDown={this.onStartMoving} - onMouseMove={this.onMoving} - onMouseUp={this.onEndMoving} - onMouseLeave={this.onEndMoving} />
    From e891d18fa264491fe81fbbcd893fa5c9fdc1d51a Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sat, 5 Jun 2021 21:41:28 -0400 Subject: [PATCH 115/198] Add my email to my copyright notices Signed-off-by: Robin Townsend --- res/css/views/dialogs/_ForwardDialog.scss | 2 +- src/components/views/dialogs/ForwardDialog.tsx | 2 +- test/components/views/dialogs/ForwardDialog-test.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss index fc59259ca2..7422d39626 100644 --- a/res/css/views/dialogs/_ForwardDialog.scss +++ b/res/css/views/dialogs/_ForwardDialog.scss @@ -1,5 +1,5 @@ /* -Copyright 2021 Robin Townsend. +Copyright 2021 Robin Townsend Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index 355c92240e..e97958973b 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -1,5 +1,5 @@ /* -Copyright 2021 Robin Townsend. +Copyright 2021 Robin Townsend Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/test/components/views/dialogs/ForwardDialog-test.js b/test/components/views/dialogs/ForwardDialog-test.js index 004954c713..c50fb073bf 100644 --- a/test/components/views/dialogs/ForwardDialog-test.js +++ b/test/components/views/dialogs/ForwardDialog-test.js @@ -1,5 +1,5 @@ /* -Copyright 2021 Robin Townsend. +Copyright 2021 Robin Townsend Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From ea2120bdfdb6b75b25973fb89e63df5dc2a9b72b Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sun, 6 Jun 2021 01:55:01 -0400 Subject: [PATCH 116/198] Fix timestamps 7f835908468dc10848e3e9dcb071f08c7f8132b9 changed timestamps to be hidden at the DOM level, not the CSS level. We can keep that approach, we just need to ensure they still get shown at the right times. Signed-off-by: Robin Townsend --- res/css/views/rooms/_EventTile.scss | 21 +------------------ src/components/structures/MessagePanel.js | 10 +-------- .../views/dialogs/MessageEditHistoryDialog.js | 2 +- src/components/views/elements/ReplyThread.js | 6 +++++- src/components/views/rooms/EventTile.tsx | 6 +++++- src/components/views/rooms/ReplyPreview.js | 2 +- .../views/rooms/SearchResultTile.js | 2 ++ 7 files changed, 16 insertions(+), 33 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 51d9e1cc9d..c8b4138f27 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -85,12 +85,11 @@ $left-gutter: 64px; } .mx_EventTile_isEditing .mx_MessageTimestamp { - visibility: hidden !important; + visibility: hidden; } .mx_EventTile .mx_MessageTimestamp { display: block; - visibility: hidden; white-space: nowrap; left: 0px; text-align: center; @@ -142,29 +141,11 @@ $left-gutter: 64px; line-height: 57px !important; } -.mx_MessagePanel_alwaysShowTimestamps .mx_MessageTimestamp { - visibility: visible; -} - .mx_EventTile_selected > div > a > .mx_MessageTimestamp { left: 3px; width: auto; } -// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies) -// The first set is to handle the 'group layout' (default) and the second for the IRC layout -.mx_EventTile_last > div > a > .mx_MessageTimestamp, -.mx_EventTile:hover > div > a > .mx_MessageTimestamp, -.mx_EventTile.mx_EventTile_actionBarFocused > div > a > .mx_MessageTimestamp, -.mx_EventTile.focus-visible:focus-within > div > a > .mx_MessageTimestamp, -.mx_IRCLayout .mx_EventTile_last > a > .mx_MessageTimestamp, -.mx_IRCLayout .mx_EventTile:hover > a > .mx_MessageTimestamp, -.mx_IRCLayout .mx_ReplyThread .mx_EventTile > a > .mx_MessageTimestamp, -.mx_IRCLayout .mx_EventTile.mx_EventTile_actionBarFocused > a > .mx_MessageTimestamp, -.mx_IRCLayout .mx_EventTile.focus-visible:focus-within > a > .mx_MessageTimestamp { - visibility: visible; -} - .mx_EventTile:hover .mx_MessageActionBar, .mx_EventTile.mx_EventTile_actionBarFocused .mx_MessageActionBar, [data-whatinput='keyboard'] .mx_EventTile:focus-within .mx_MessageActionBar, diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 6709fef814..e64d3770d4 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -19,7 +19,6 @@ limitations under the License. import React, {createRef} from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; -import classNames from 'classnames'; import shouldHideEvent from '../../shouldHideEvent'; import {wantsDateSeparator} from '../../DateUtils'; import * as sdk from '../../index'; @@ -854,13 +853,6 @@ export default class MessagePanel extends React.Component { const style = this.props.hidden ? { display: 'none' } : {}; - const className = classNames( - this.props.className, - { - "mx_MessagePanel_alwaysShowTimestamps": this.props.alwaysShowTimestamps, - }, - ); - let whoIsTyping; if (this.props.room && !this.props.tileShape && this.state.showTypingNotifications) { whoIsTyping = ( -
      {this._renderEdits()}
    +
      {this._renderEdits()}
    ); } const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); diff --git a/src/components/views/elements/ReplyThread.js b/src/components/views/elements/ReplyThread.js index bbced5328f..81ed360b17 100644 --- a/src/components/views/elements/ReplyThread.js +++ b/src/components/views/elements/ReplyThread.js @@ -46,6 +46,8 @@ export default class ReplyThread extends React.Component { permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired, // Specifies which layout to use. layout: LayoutPropType, + // Whether to always show a timestamp + alwaysShowTimestamps: PropTypes.bool, }; static contextType = MatrixClientContext; @@ -212,7 +214,7 @@ export default class ReplyThread extends React.Component { }; } - static makeThread(parentEv, onHeightChanged, permalinkCreator, ref, layout) { + static makeThread(parentEv, onHeightChanged, permalinkCreator, ref, layout, alwaysShowTimestamps) { if (!ReplyThread.getParentEventId(parentEv)) { return null; } @@ -222,6 +224,7 @@ export default class ReplyThread extends React.Component { ref={ref} permalinkCreator={permalinkCreator} layout={layout} + alwaysShowTimestamps={alwaysShowTimestamps} />; } @@ -386,6 +389,7 @@ export default class ReplyThread extends React.Component { isRedacted={ev.isRedacted()} isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")} layout={this.props.layout} + alwaysShowTimestamps={this.props.alwaysShowTimestamps} enableFlair={SettingsStore.getValue(UIFeature.Flair)} replacingEventId={ev.replacingEventId()} /> diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 8cec067c39..e1e3b04f36 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -975,7 +975,8 @@ export default class EventTile extends React.Component { onFocusChange={this.onActionBarFocusChange} /> : undefined; - const showTimestamp = this.props.mxEvent.getTs() && (this.props.alwaysShowTimestamps || this.state.hover); + const showTimestamp = this.props.mxEvent.getTs() && + (this.props.alwaysShowTimestamps || this.props.last || this.state.hover || this.state.actionBarFocused); const timestamp = showTimestamp ? : null; @@ -1108,6 +1109,8 @@ export default class EventTile extends React.Component { this.props.onHeightChanged, this.props.permalinkCreator, this.replyThread, + null, + this.props.alwaysShowTimestamps || this.state.hover, ); } return ( @@ -1139,6 +1142,7 @@ export default class EventTile extends React.Component { this.props.permalinkCreator, this.replyThread, this.props.layout, + this.props.alwaysShowTimestamps || this.state.hover, ); // 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/ReplyPreview.js b/src/components/views/rooms/ReplyPreview.js index d7f8c74d73..5835eb9f36 100644 --- a/src/components/views/rooms/ReplyPreview.js +++ b/src/components/views/rooms/ReplyPreview.js @@ -89,7 +89,7 @@ export default class ReplyPreview extends React.Component {
    ]; + const alwaysShowTimestamps = SettingsStore.getValue("alwaysShowTimestamps"); const timeline = result.context.getTimeline(); for (let j = 0; j < timeline.length; j++) { @@ -67,6 +68,7 @@ export default class SearchResultTile extends React.Component { highlightLink={this.props.resultLink} onHeightChanged={this.props.onHeightChanged} isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")} + alwaysShowTimestamps={alwaysShowTimestamps} enableFlair={SettingsStore.getValue(UIFeature.Flair)} /> )); From 1c8e34114318e472d7deee6ecc8dd99e2e6baa7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 6 Jun 2021 08:09:41 +0200 Subject: [PATCH 117/198] Reset all translation vars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not doing this would result in jumps because everything would get out of sync Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index df73e1a8cb..1b1decd6d5 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -283,6 +283,10 @@ export default class ImageView extends React.Component { translationX: 0, translationY: 0, }); + this.lastX = 0; + this.lastY = 0; + this.initX = 0; + this.initY = 0; } this.setState({moving: false}); }; From c0964b69b73af9de4759b2f45d97caf0d6c98b59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 6 Jun 2021 08:14:43 +0200 Subject: [PATCH 118/198] Remove redundant vars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 1b1decd6d5..d6256f7dd2 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -95,8 +95,6 @@ export default class ImageView extends React.Component { private initX = 0; private initY = 0; - private lastX = 0; - private lastY = 0; private previousX = 0; private previousY = 0; @@ -253,8 +251,8 @@ export default class ImageView extends React.Component { this.setState({moving: true}); this.previousX = this.state.translationX; this.previousY = this.state.translationY; - this.initX = ev.pageX - this.lastX; - this.initY = ev.pageY - this.lastY; + this.initX = ev.pageX - this.state.translationX; + this.initY = ev.pageY - this.state.translationY; }; private onMoving = (ev: React.MouseEvent) => { @@ -263,11 +261,9 @@ export default class ImageView extends React.Component { if (!this.state.moving) return; - this.lastX = ev.pageX - this.initX; - this.lastY = ev.pageY - this.initY; this.setState({ - translationX: this.lastX, - translationY: this.lastY, + translationX: ev.pageX - this.initX, + translationY: ev.pageY - this.initY, }); }; @@ -283,8 +279,6 @@ export default class ImageView extends React.Component { translationX: 0, translationY: 0, }); - this.lastX = 0; - this.lastY = 0; this.initX = 0; this.initY = 0; } From 28be581af18615ec24c4a664744ccae3a110696f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 6 Jun 2021 08:51:07 +0200 Subject: [PATCH 119/198] Take image rotation into account when zooming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 31 +++++++++++++-------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index d6256f7dd2..d4870a569b 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -97,29 +97,36 @@ export default class ImageView extends React.Component { private initY = 0; private previousX = 0; private previousY = 0; + private rotation = 0; componentDidMount() { // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); // We want to recalculate zoom whenever the window's size changes - window.addEventListener("resize", this.calculateZoom); + window.addEventListener("resize", this.calculateZoomAndRotate); // After the image loads for the first time we want to calculate the zoom - this.image.current.addEventListener("load", this.calculateZoom); + this.image.current.addEventListener("load", this.calculateZoomAndRotate); } componentWillUnmount() { this.focusLock.current.removeEventListener('wheel', this.onWheel); - window.removeEventListener("resize", this.calculateZoom); - this.image.current.removeEventListener("load", this.calculateZoom); + window.removeEventListener("resize", this.calculateZoomAndRotate); + this.image.current.removeEventListener("load", this.calculateZoomAndRotate); } - private calculateZoom = () => { + private calculateZoomAndRotate = () => { const image = this.image.current; const imageWrapper = this.imageWrapper.current; - const zoomX = imageWrapper.clientWidth / image.naturalWidth; - const zoomY = imageWrapper.clientHeight / image.naturalHeight; + const landscape = this.rotation % 180 === 0; + + // If the image is rotated take it into account + const width = landscape ? image.naturalWidth : image.naturalHeight; + const height = landscape ? image.naturalHeight : image.naturalWidth; + + const zoomX = imageWrapper.clientWidth / width; + const zoomY = imageWrapper.clientHeight / height; // If the image is smaller in both dimensions set its the zoom to 1 to // display it in its original size @@ -128,6 +135,7 @@ export default class ImageView extends React.Component { zoom: 1, minZoom: 1, maxZoom: 1, + rotation: this.rotation, }); return; } @@ -140,6 +148,7 @@ export default class ImageView extends React.Component { this.setState({ minZoom: minZoom, maxZoom: 1, + rotation: this.rotation, }); } @@ -190,14 +199,14 @@ export default class ImageView extends React.Component { private onRotateCounterClockwiseClick = () => { const cur = this.state.rotation; - const rotationDegrees = cur - 90; - this.setState({ rotation: rotationDegrees }); + this.rotation = cur - 90; + this.calculateZoomAndRotate(); }; private onRotateClockwiseClick = () => { const cur = this.state.rotation; - const rotationDegrees = cur + 90; - this.setState({ rotation: rotationDegrees }); + this.rotation = cur + 90; + this.calculateZoomAndRotate(); }; private onDownloadClick = () => { From c3fdd733570550fcd48473834009bf6bafafeaac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 6 Jun 2021 08:56:12 +0200 Subject: [PATCH 120/198] Avoid multiple setState() calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index d4870a569b..c4ec3497e4 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -144,11 +144,14 @@ export default class ImageView extends React.Component { // image by default const minZoom = Math.min(zoomX, zoomY) * MAX_SCALE; - if (this.state.zoom <= this.state.minZoom) this.setState({zoom: minZoom}); + // If zoom is smaller than minZoom don't go beneath that value + const zoom = (this.state.zoom <= this.state.minZoom) ? minZoom : this.state.zoom; + this.setState({ minZoom: minZoom, maxZoom: 1, rotation: this.rotation, + zoom: zoom, }); } From ba0f6766caefb700324a0a4441e0427689b8faf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 6 Jun 2021 09:02:30 +0200 Subject: [PATCH 121/198] Update curly braces styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 131 ++++++++++---------- 1 file changed, 66 insertions(+), 65 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index c4ec3497e4..948e005978 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -19,20 +19,20 @@ limitations under the License. import React, { createRef } from 'react'; import { _t } from '../../../languageHandler'; import AccessibleTooltipButton from "./AccessibleTooltipButton"; -import {Key} from "../../../Keyboard"; +import { Key } from "../../../Keyboard"; import FocusLock from "react-focus-lock"; import MemberAvatar from "../avatars/MemberAvatar"; -import {ContextMenuTooltipButton} from "../../../accessibility/context_menu/ContextMenuTooltipButton"; +import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton"; import MessageContextMenu from "../context_menus/MessageContextMenu"; -import {aboveLeftOf, ContextMenu} from '../../structures/ContextMenu'; +import { aboveLeftOf, ContextMenu } from '../../structures/ContextMenu'; import MessageTimestamp from "../messages/MessageTimestamp"; import SettingsStore from "../../../settings/SettingsStore"; -import {formatFullDate} from "../../../DateUtils"; +import { formatFullDate } from "../../../DateUtils"; import dis from '../../../dispatcher/dispatcher'; -import {replaceableComponent} from "../../../utils/replaceableComponent"; -import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks" -import {MatrixEvent} from "matrix-js-sdk/src/models/event"; -import {normalizeWheelEvent} from "../../../utils/Mouse"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks" +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { normalizeWheelEvent } from "../../../utils/Mouse"; // Max scale to keep gaps around the image const MAX_SCALE = 0.95; @@ -167,7 +167,7 @@ export default class ImageView extends React.Component { return; } if (newZoom >= this.state.maxZoom) { - this.setState({zoom: this.state.maxZoom}); + this.setState({ zoom: this.state.maxZoom }); return; } @@ -256,11 +256,11 @@ export default class ImageView extends React.Component { // Zoom in if we are completely zoomed out if (this.state.zoom === this.state.minZoom) { - this.setState({zoom: this.state.maxZoom}); + this.setState({ zoom: this.state.maxZoom }); return; } - this.setState({moving: true}); + this.setState({ moving: true }); this.previousX = this.state.translationX; this.previousY = this.state.translationY; this.initX = ev.pageX - this.state.translationX; @@ -294,7 +294,7 @@ export default class ImageView extends React.Component { this.initX = 0; this.initY = 0; } - this.setState({moving: false}); + this.setState({ moving: false }); }; private renderContextMenu() { @@ -302,14 +302,14 @@ export default class ImageView extends React.Component { if (this.state.contextMenuDisplayed) { contextMenu = ( ); @@ -347,10 +347,10 @@ export default class ImageView extends React.Component { const style = { cursor: cursor, transition: this.state.moving ? null : "transform 200ms ease 0s", - transform: `translateX(${translatePixelsX}) - translateY(${translatePixelsY}) - scale(${zoom}) - rotate(${rotationDegrees})`, + transform: `translateX(${ translatePixelsX }) + translateY(${ translatePixelsY }) + scale(${ zoom }) + rotate(${ rotationDegrees })`, }; let info; @@ -365,37 +365,38 @@ export default class ImageView extends React.Component { const senderName = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); const sender = (
    - {senderName} + { senderName }
    ); const messageTimestamp = ( ); const avatar = ( ); info = (
    - {avatar} + { avatar }
    - {sender} - {messageTimestamp} + { sender } + { messageTimestamp }
    ); @@ -413,10 +414,10 @@ export default class ImageView extends React.Component { contextMenuButton = ( ); } @@ -427,14 +428,14 @@ export default class ImageView extends React.Component { zoomOutButton = ( + title={ _t("Zoom out") } + onClick={ this.onZoomOutClick }> ); zoomInButton = ( ); @@ -442,57 +443,57 @@ export default class ImageView extends React.Component { return (
    - {info} + { info }
    + title={ _t("Rotate Right") } + onClick={ this.onRotateClockwiseClick }> - {zoomOutButton} - {zoomInButton} + { zoomOutButton } + { zoomInButton } - {contextMenuButton} + { contextMenuButton } - {this.renderContextMenu()} + { this.renderContextMenu() }
    + ref={ this.imageWrapper }>
    From 903d4d252a0c2ac6043ec24251d4c446f33d7990 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sun, 6 Jun 2021 23:06:56 -0400 Subject: [PATCH 122/198] Add optimized function to determine whether event has text to display Signed-off-by: Robin Townsend --- src/TextForEvent.js | 274 ++++++++++++---------- src/components/structures/MessagePanel.js | 9 +- src/components/views/rooms/EventTile.tsx | 4 +- 3 files changed, 155 insertions(+), 132 deletions(-) diff --git a/src/TextForEvent.js b/src/TextForEvent.js index 86f9ff20f4..22ce0dd9cf 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -21,6 +21,10 @@ import SettingsStore from "./settings/SettingsStore"; import {ALL_RULE_TYPES, ROOM_RULE_TYPES, SERVER_RULE_TYPES, USER_RULE_TYPES} from "./mjolnir/BanList"; import {WIDGET_LAYOUT_EVENT_TYPE} from "./stores/widgets/WidgetLayoutStore"; +// These functions are frequently used just to check whether an event has +// any text to display at all. For this reason they return deferred values +// to avoid the expense of looking up translations when they're not needed. + function textForMemberEvent(ev) { // XXX: SYJS-16 "sender is sometimes null for join messages" const senderName = ev.sender ? ev.sender.name : ev.getSender(); @@ -28,84 +32,84 @@ function textForMemberEvent(ev) { const prevContent = ev.getPrevContent(); const content = ev.getContent(); - const reason = content.reason ? (_t('Reason') + ': ' + content.reason) : ''; + const getReason = () => content.reason ? (_t('Reason') + ': ' + content.reason) : ''; switch (content.membership) { case 'invite': { const threePidContent = content.third_party_invite; if (threePidContent) { if (threePidContent.display_name) { - return _t('%(targetName)s accepted the invitation for %(displayName)s.', { + return () => _t('%(targetName)s accepted the invitation for %(displayName)s.', { targetName, displayName: threePidContent.display_name, }); } else { - return _t('%(targetName)s accepted an invitation.', {targetName}); + return () => _t('%(targetName)s accepted an invitation.', {targetName}); } } else { - return _t('%(senderName)s invited %(targetName)s.', {senderName, targetName}); + return () => _t('%(senderName)s invited %(targetName)s.', {senderName, targetName}); } } case 'ban': - return _t('%(senderName)s banned %(targetName)s.', {senderName, targetName}) + ' ' + reason; + return () => _t('%(senderName)s banned %(targetName)s.', {senderName, targetName}) + ' ' + getReason(); case 'join': if (prevContent && prevContent.membership === 'join') { if (prevContent.displayname && content.displayname && prevContent.displayname !== content.displayname) { - return _t('%(oldDisplayName)s changed their display name to %(displayName)s.', { + return () => _t('%(oldDisplayName)s changed their display name to %(displayName)s.', { oldDisplayName: prevContent.displayname, displayName: content.displayname, }); } else if (!prevContent.displayname && content.displayname) { - return _t('%(senderName)s set their display name to %(displayName)s.', { + return () => _t('%(senderName)s set their display name to %(displayName)s.', { senderName: ev.getSender(), displayName: content.displayname, }); } else if (prevContent.displayname && !content.displayname) { - return _t('%(senderName)s removed their display name (%(oldDisplayName)s).', { + return () => _t('%(senderName)s removed their display name (%(oldDisplayName)s).', { senderName, oldDisplayName: prevContent.displayname, }); } else if (prevContent.avatar_url && !content.avatar_url) { - return _t('%(senderName)s removed their profile picture.', {senderName}); + return () => _t('%(senderName)s removed their profile picture.', {senderName}); } else if (prevContent.avatar_url && content.avatar_url && prevContent.avatar_url !== content.avatar_url) { - return _t('%(senderName)s changed their profile picture.', {senderName}); + return () => _t('%(senderName)s changed their profile picture.', {senderName}); } else if (!prevContent.avatar_url && content.avatar_url) { - return _t('%(senderName)s set a profile picture.', {senderName}); + return () => _t('%(senderName)s set a profile picture.', {senderName}); } else if (SettingsStore.getValue("showHiddenEventsInTimeline")) { // This is a null rejoin, it will only be visible if the Labs option is enabled - return _t("%(senderName)s made no change.", {senderName}); + return () => _t("%(senderName)s made no change.", {senderName}); } else { - return ""; + return null; } } else { if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key); - return _t('%(targetName)s joined the room.', {targetName}); + return () => _t('%(targetName)s joined the room.', {targetName}); } case 'leave': if (ev.getSender() === ev.getStateKey()) { if (prevContent.membership === "invite") { - return _t('%(targetName)s rejected the invitation.', {targetName}); + return () => _t('%(targetName)s rejected the invitation.', {targetName}); } else { - return _t('%(targetName)s left the room.', {targetName}); + return () => _t('%(targetName)s left the room.', {targetName}); } } else if (prevContent.membership === "ban") { - return _t('%(senderName)s unbanned %(targetName)s.', {senderName, targetName}); + return () => _t('%(senderName)s unbanned %(targetName)s.', {senderName, targetName}); } else if (prevContent.membership === "invite") { - return _t('%(senderName)s withdrew %(targetName)s\'s invitation.', { + return () => _t('%(senderName)s withdrew %(targetName)s\'s invitation.', { senderName, targetName, - }) + ' ' + reason; + }) + ' ' + getReason(); } else if (prevContent.membership === "join") { - return _t('%(senderName)s kicked %(targetName)s.', {senderName, targetName}) + ' ' + reason; + return () => _t('%(senderName)s kicked %(targetName)s.', {senderName, targetName}) + ' ' + getReason(); } else { - return ""; + return null; } } } function textForTopicEvent(ev) { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); - return _t('%(senderDisplayName)s changed the topic to "%(topic)s".', { + return () => _t('%(senderDisplayName)s changed the topic to "%(topic)s".', { senderDisplayName, topic: ev.getContent().topic, }); @@ -115,16 +119,16 @@ function textForRoomNameEvent(ev) { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); if (!ev.getContent().name || ev.getContent().name.trim().length === 0) { - return _t('%(senderDisplayName)s removed the room name.', {senderDisplayName}); + return () => _t('%(senderDisplayName)s removed the room name.', {senderDisplayName}); } if (ev.getPrevContent().name) { - return _t('%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.', { + return () => _t('%(senderDisplayName)s changed the room name from %(oldRoomName)s to %(newRoomName)s.', { senderDisplayName, oldRoomName: ev.getPrevContent().name, newRoomName: ev.getContent().name, }); } - return _t('%(senderDisplayName)s changed the room name to %(roomName)s.', { + return () => _t('%(senderDisplayName)s changed the room name to %(roomName)s.', { senderDisplayName, roomName: ev.getContent().name, }); @@ -132,19 +136,23 @@ function textForRoomNameEvent(ev) { function textForTombstoneEvent(ev) { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); - return _t('%(senderDisplayName)s upgraded this room.', {senderDisplayName}); + return () => _t('%(senderDisplayName)s upgraded this room.', {senderDisplayName}); } function textForJoinRulesEvent(ev) { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); switch (ev.getContent().join_rule) { case "public": - return _t('%(senderDisplayName)s made the room public to whoever knows the link.', {senderDisplayName}); + return () => _t('%(senderDisplayName)s made the room public to whoever knows the link.', { + senderDisplayName, + }); case "invite": - return _t('%(senderDisplayName)s made the room invite only.', {senderDisplayName}); + return () => _t('%(senderDisplayName)s made the room invite only.', { + senderDisplayName, + }); default: // The spec supports "knock" and "private", however nothing implements these. - return _t('%(senderDisplayName)s changed the join rule to %(rule)s', { + return () => _t('%(senderDisplayName)s changed the join rule to %(rule)s', { senderDisplayName, rule: ev.getContent().join_rule, }); @@ -155,12 +163,12 @@ function textForGuestAccessEvent(ev) { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); switch (ev.getContent().guest_access) { case "can_join": - return _t('%(senderDisplayName)s has allowed guests to join the room.', {senderDisplayName}); + return () => _t('%(senderDisplayName)s has allowed guests to join the room.', {senderDisplayName}); case "forbidden": - return _t('%(senderDisplayName)s has prevented guests from joining the room.', {senderDisplayName}); + return () => _t('%(senderDisplayName)s has prevented guests from joining the room.', {senderDisplayName}); default: // There's no other options we can expect, however just for safety's sake we'll do this. - return _t('%(senderDisplayName)s changed guest access to %(rule)s', { + return () => _t('%(senderDisplayName)s changed guest access to %(rule)s', { senderDisplayName, rule: ev.getContent().guest_access, }); @@ -175,17 +183,17 @@ function textForRelatedGroupsEvent(ev) { const removed = prevGroups.filter((g) => !groups.includes(g)); if (added.length && !removed.length) { - return _t('%(senderDisplayName)s enabled flair for %(groups)s in this room.', { + return () => _t('%(senderDisplayName)s enabled flair for %(groups)s in this room.', { senderDisplayName, groups: added.join(', '), }); } else if (!added.length && removed.length) { - return _t('%(senderDisplayName)s disabled flair for %(groups)s in this room.', { + return () => _t('%(senderDisplayName)s disabled flair for %(groups)s in this room.', { senderDisplayName, groups: removed.join(', '), }); } else if (added.length && removed.length) { - return _t('%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for ' + + return () => _t('%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for ' + '%(oldGroups)s in this room.', { senderDisplayName, newGroups: added.join(', '), @@ -193,7 +201,7 @@ function textForRelatedGroupsEvent(ev) { }); } else { // Don't bother rendering this change (because there were no changes) - return ''; + return null; } } @@ -207,11 +215,11 @@ function textForServerACLEvent(ev) { allow_ip_literals: !(prevContent.allow_ip_literals === false), }; - let text = ""; + let getText = null; if (prev.deny.length === 0 && prev.allow.length === 0) { - text = _t("%(senderDisplayName)s set the server ACLs for this room.", {senderDisplayName}); + getText = () => _t("%(senderDisplayName)s set the server ACLs for this room.", {senderDisplayName}); } else { - text = _t("%(senderDisplayName)s changed the server ACLs for this room.", {senderDisplayName}); + getText = () => _t("%(senderDisplayName)s changed the server ACLs for this room.", {senderDisplayName}); } if (!Array.isArray(current.allow)) { @@ -220,21 +228,24 @@ function textForServerACLEvent(ev) { // If we know for sure everyone is banned, mark the room as obliterated if (current.allow.length === 0) { - return text + " " + _t("🎉 All servers are banned from participating! This room can no longer be used."); + return () => getText() + " " + + _t("🎉 All servers are banned from participating! This room can no longer be used."); } - return text; + return getText; } function textForMessageEvent(ev) { - const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); - let message = senderDisplayName + ': ' + ev.getContent().body; - if (ev.getContent().msgtype === "m.emote") { - message = "* " + senderDisplayName + " " + message; - } else if (ev.getContent().msgtype === "m.image") { - message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName}); - } - return message; + return () => { + const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); + let message = senderDisplayName + ': ' + ev.getContent().body; + if (ev.getContent().msgtype === "m.emote") { + message = "* " + senderDisplayName + " " + message; + } else if (ev.getContent().msgtype === "m.image") { + message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName}); + } + return message; + }; } function textForCanonicalAliasEvent(ev) { @@ -248,96 +259,100 @@ function textForCanonicalAliasEvent(ev) { if (!removedAltAliases.length && !addedAltAliases.length) { if (newAlias) { - return _t('%(senderName)s set the main address for this room to %(address)s.', { + return () => _t('%(senderName)s set the main address for this room to %(address)s.', { senderName: senderName, address: ev.getContent().alias, }); } else if (oldAlias) { - return _t('%(senderName)s removed the main address for this room.', { + return () => _t('%(senderName)s removed the main address for this room.', { senderName: senderName, }); } } else if (newAlias === oldAlias) { if (addedAltAliases.length && !removedAltAliases.length) { - return _t('%(senderName)s added the alternative addresses %(addresses)s for this room.', { + return () => _t('%(senderName)s added the alternative addresses %(addresses)s for this room.', { senderName: senderName, addresses: addedAltAliases.join(", "), count: addedAltAliases.length, }); } if (removedAltAliases.length && !addedAltAliases.length) { - return _t('%(senderName)s removed the alternative addresses %(addresses)s for this room.', { + return () => _t('%(senderName)s removed the alternative addresses %(addresses)s for this room.', { senderName: senderName, addresses: removedAltAliases.join(", "), count: removedAltAliases.length, }); } if (removedAltAliases.length && addedAltAliases.length) { - return _t('%(senderName)s changed the alternative addresses for this room.', { + return () => _t('%(senderName)s changed the alternative addresses for this room.', { senderName: senderName, }); } } else { // both alias and alt_aliases where modified - return _t('%(senderName)s changed the main and alternative addresses for this room.', { + return () => _t('%(senderName)s changed the main and alternative addresses for this room.', { senderName: senderName, }); } // in case there is no difference between the two events, // say something as we can't simply hide the tile from here - return _t('%(senderName)s changed the addresses for this room.', { + return () => _t('%(senderName)s changed the addresses for this room.', { senderName: senderName, }); } function textForCallAnswerEvent(event) { - const senderName = event.sender ? event.sender.name : _t('Someone'); - const supported = MatrixClientPeg.get().supportsVoip() ? '' : _t('(not supported by this browser)'); - return _t('%(senderName)s answered the call.', {senderName}) + ' ' + supported; + return () => { + const senderName = event.sender ? event.sender.name : _t('Someone'); + const supported = MatrixClientPeg.get().supportsVoip() ? '' : _t('(not supported by this browser)'); + return _t('%(senderName)s answered the call.', {senderName}) + ' ' + supported; + }; } function textForCallHangupEvent(event) { - const senderName = event.sender ? event.sender.name : _t('Someone'); + const getSenderName = () => event.sender ? event.sender.name : _t('Someone'); const eventContent = event.getContent(); - let reason = ""; + let getReason = () => ""; if (!MatrixClientPeg.get().supportsVoip()) { - reason = _t('(not supported by this browser)'); + getReason = () => _t('(not supported by this browser)'); } else if (eventContent.reason) { if (eventContent.reason === "ice_failed") { // We couldn't establish a connection at all - reason = _t('(could not connect media)'); + getReason = () => _t('(could not connect media)'); } else if (eventContent.reason === "ice_timeout") { // We established a connection but it died - reason = _t('(connection failed)'); + getReason = () => _t('(connection failed)'); } else if (eventContent.reason === "user_media_failed") { // The other side couldn't open capture devices - reason = _t("(their device couldn't start the camera / microphone)"); + getReason = () => _t("(their device couldn't start the camera / microphone)"); } else if (eventContent.reason === "unknown_error") { // An error code the other side doesn't have a way to express // (as opposed to an error code they gave but we don't know about, // in which case we show the error code) - reason = _t("(an error occurred)"); + getReason = () => _t("(an error occurred)"); } else if (eventContent.reason === "invite_timeout") { - reason = _t('(no answer)'); + getReason = () => _t('(no answer)'); } else if (eventContent.reason === "user hangup" || eventContent.reason === "user_hangup") { // workaround for https://github.com/vector-im/element-web/issues/5178 // it seems Android randomly sets a reason of "user hangup" which is // interpreted as an error code :( // https://github.com/vector-im/riot-android/issues/2623 // Also the correct hangup code as of VoIP v1 (with underscore) - reason = ''; + getReason = () => ''; } else { - reason = _t('(unknown failure: %(reason)s)', {reason: eventContent.reason}); + getReason = () => _t('(unknown failure: %(reason)s)', {reason: eventContent.reason}); } } - return _t('%(senderName)s ended the call.', {senderName}) + ' ' + reason; + return () => _t('%(senderName)s ended the call.', {senderName: getSenderName()}) + ' ' + getReason(); } function textForCallRejectEvent(event) { - const senderName = event.sender ? event.sender.name : _t('Someone'); - return _t('%(senderName)s declined the call.', {senderName}); + return () => { + const senderName = event.sender ? event.sender.name : _t('Someone'); + return _t('%(senderName)s declined the call.', {senderName}); + }; } function textForCallInviteEvent(event) { - const senderName = event.sender ? event.sender.name : _t('Someone'); + const getSenderName = () => event.sender ? event.sender.name : _t('Someone'); // FIXME: Find a better way to determine this from the event? let isVoice = true; if (event.getContent().offer && event.getContent().offer.sdp && @@ -350,13 +365,21 @@ function textForCallInviteEvent(event) { // can have a hard time translating those strings. In an effort to make translations easier // and more accurate, we break out the string-based variables to a couple booleans. if (isVoice && isSupported) { - return _t("%(senderName)s placed a voice call.", {senderName}); + return () => _t("%(senderName)s placed a voice call.", { + senderName: getSenderName(), + }); } else if (isVoice && !isSupported) { - return _t("%(senderName)s placed a voice call. (not supported by this browser)", {senderName}); + return () => _t("%(senderName)s placed a voice call. (not supported by this browser)", { + senderName: getSenderName(), + }); } else if (!isVoice && isSupported) { - return _t("%(senderName)s placed a video call.", {senderName}); + return () => _t("%(senderName)s placed a video call.", { + senderName: getSenderName(), + }); } else if (!isVoice && !isSupported) { - return _t("%(senderName)s placed a video call. (not supported by this browser)", {senderName}); + return () => _t("%(senderName)s placed a video call. (not supported by this browser)", { + senderName: getSenderName(), + }); } } @@ -364,14 +387,13 @@ function textForThreePidInviteEvent(event) { const senderName = event.sender ? event.sender.name : event.getSender(); if (!isValid3pidInvite(event)) { - const targetDisplayName = event.getPrevContent().display_name || _t("Someone"); - return _t('%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.', { + return () => _t('%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.', { senderName, - targetDisplayName, + targetDisplayName: event.getPrevContent().display_name || _t("Someone"), }); } - return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', { + return () => _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', { senderName, targetDisplayName: event.getContent().display_name, }); @@ -381,17 +403,17 @@ function textForHistoryVisibilityEvent(event) { const senderName = event.sender ? event.sender.name : event.getSender(); switch (event.getContent().history_visibility) { case 'invited': - return _t('%(senderName)s made future room history visible to all room members, ' + return () => _t('%(senderName)s made future room history visible to all room members, ' + 'from the point they are invited.', {senderName}); case 'joined': - return _t('%(senderName)s made future room history visible to all room members, ' + return () => _t('%(senderName)s made future room history visible to all room members, ' + 'from the point they joined.', {senderName}); case 'shared': - return _t('%(senderName)s made future room history visible to all room members.', {senderName}); + return () => _t('%(senderName)s made future room history visible to all room members.', {senderName}); case 'world_readable': - return _t('%(senderName)s made future room history visible to anyone.', {senderName}); + return () => _t('%(senderName)s made future room history visible to anyone.', {senderName}); default: - return _t('%(senderName)s made future room history visible to unknown (%(visibility)s).', { + return () => _t('%(senderName)s made future room history visible to unknown (%(visibility)s).', { senderName, visibility: event.getContent().history_visibility, }); @@ -403,7 +425,7 @@ function textForPowerEvent(event) { const senderName = event.sender ? event.sender.name : event.getSender(); if (!event.getPrevContent() || !event.getPrevContent().users || !event.getContent() || !event.getContent().users) { - return ''; + return null; } const userDefault = event.getContent().users_default || 0; // Construct set of userIds @@ -418,35 +440,35 @@ function textForPowerEvent(event) { if (users.indexOf(userId) === -1) users.push(userId); }, ); - const diff = []; - // XXX: This is also surely broken for i18n + const diffs = []; users.forEach((userId) => { // Previous power level const from = event.getPrevContent().users[userId]; // Current power level const to = event.getContent().users[userId]; if (to !== from) { - diff.push( - _t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', { - userId, - fromPowerLevel: Roles.textualPowerLevel(from, userDefault), - toPowerLevel: Roles.textualPowerLevel(to, userDefault), - }), - ); + diffs.push({ userId, from, to }); } }); - if (!diff.length) { - return ''; + if (!diffs.length) { + return null; } - return _t('%(senderName)s changed the power level of %(powerLevelDiffText)s.', { + // XXX: This is also surely broken for i18n + return () => _t('%(senderName)s changed the power level of %(powerLevelDiffText)s.', { senderName, - powerLevelDiffText: diff.join(", "), + powerLevelDiffText: diffs.map(diff => + _t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', { + userId: diff.userId, + fromPowerLevel: Roles.textualPowerLevel(diff.from, userDefault), + toPowerLevel: Roles.textualPowerLevel(diff.to, userDefault), + }), + ).join(", "), }); } function textForPinnedEvent(event) { const senderName = event.sender ? event.sender.name : event.getSender(); - return _t("%(senderName)s changed the pinned messages for the room.", {senderName}); + return () => _t("%(senderName)s changed the pinned messages for the room.", {senderName}); } function textForWidgetEvent(event) { @@ -464,16 +486,16 @@ function textForWidgetEvent(event) { // equivalent to that condition. if (url) { if (prevUrl) { - return _t('%(widgetName)s widget modified by %(senderName)s', { + return () => _t('%(widgetName)s widget modified by %(senderName)s', { widgetName, senderName, }); } else { - return _t('%(widgetName)s widget added by %(senderName)s', { + return () => _t('%(widgetName)s widget added by %(senderName)s', { widgetName, senderName, }); } } else { - return _t('%(widgetName)s widget removed by %(senderName)s', { + return () => _t('%(widgetName)s widget removed by %(senderName)s', { widgetName, senderName, }); } @@ -481,7 +503,7 @@ function textForWidgetEvent(event) { function textForWidgetLayoutEvent(event) { const senderName = event.sender?.name || event.getSender(); - return _t("%(senderName)s has updated the widget layout", {senderName}); + return () => _t("%(senderName)s has updated the widget layout", {senderName}); } function textForMjolnirEvent(event) { @@ -492,74 +514,74 @@ function textForMjolnirEvent(event) { // Rule removed if (!entity) { if (USER_RULE_TYPES.includes(event.getType())) { - return _t("%(senderName)s removed the rule banning users matching %(glob)s", + return () => _t("%(senderName)s removed the rule banning users matching %(glob)s", {senderName, glob: prevEntity}); } else if (ROOM_RULE_TYPES.includes(event.getType())) { - return _t("%(senderName)s removed the rule banning rooms matching %(glob)s", + return () => _t("%(senderName)s removed the rule banning rooms matching %(glob)s", {senderName, glob: prevEntity}); } else if (SERVER_RULE_TYPES.includes(event.getType())) { - return _t("%(senderName)s removed the rule banning servers matching %(glob)s", + return () => _t("%(senderName)s removed the rule banning servers matching %(glob)s", {senderName, glob: prevEntity}); } // Unknown type. We'll say something, but we shouldn't end up here. - return _t("%(senderName)s removed a ban rule matching %(glob)s", {senderName, glob: prevEntity}); + return () => _t("%(senderName)s removed a ban rule matching %(glob)s", {senderName, glob: prevEntity}); } // Invalid rule - if (!recommendation || !reason) return _t(`%(senderName)s updated an invalid ban rule`, {senderName}); + if (!recommendation || !reason) return () => _t(`%(senderName)s updated an invalid ban rule`, {senderName}); // Rule updated if (entity === prevEntity) { if (USER_RULE_TYPES.includes(event.getType())) { - return _t("%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s", + return () => _t("%(senderName)s updated the rule banning users matching %(glob)s for %(reason)s", {senderName, glob: entity, reason}); } else if (ROOM_RULE_TYPES.includes(event.getType())) { - return _t("%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s", + return () => _t("%(senderName)s updated the rule banning rooms matching %(glob)s for %(reason)s", {senderName, glob: entity, reason}); } else if (SERVER_RULE_TYPES.includes(event.getType())) { - return _t("%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s", + return () => _t("%(senderName)s updated the rule banning servers matching %(glob)s for %(reason)s", {senderName, glob: entity, reason}); } // Unknown type. We'll say something but we shouldn't end up here. - return _t("%(senderName)s updated a ban rule matching %(glob)s for %(reason)s", + return () => _t("%(senderName)s updated a ban rule matching %(glob)s for %(reason)s", {senderName, glob: entity, reason}); } // New rule if (!prevEntity) { if (USER_RULE_TYPES.includes(event.getType())) { - return _t("%(senderName)s created a rule banning users matching %(glob)s for %(reason)s", + return () => _t("%(senderName)s created a rule banning users matching %(glob)s for %(reason)s", {senderName, glob: entity, reason}); } else if (ROOM_RULE_TYPES.includes(event.getType())) { - return _t("%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s", + return () => _t("%(senderName)s created a rule banning rooms matching %(glob)s for %(reason)s", {senderName, glob: entity, reason}); } else if (SERVER_RULE_TYPES.includes(event.getType())) { - return _t("%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s", + return () => _t("%(senderName)s created a rule banning servers matching %(glob)s for %(reason)s", {senderName, glob: entity, reason}); } // Unknown type. We'll say something but we shouldn't end up here. - return _t("%(senderName)s created a ban rule matching %(glob)s for %(reason)s", + return () => _t("%(senderName)s created a ban rule matching %(glob)s for %(reason)s", {senderName, glob: entity, reason}); } // else the entity !== prevEntity - count as a removal & add if (USER_RULE_TYPES.includes(event.getType())) { - return _t( + return () => _t( "%(senderName)s changed a rule that was banning users matching %(oldGlob)s to matching " + "%(newGlob)s for %(reason)s", {senderName, oldGlob: prevEntity, newGlob: entity, reason}, ); } else if (ROOM_RULE_TYPES.includes(event.getType())) { - return _t( + return () => _t( "%(senderName)s changed a rule that was banning rooms matching %(oldGlob)s to matching " + "%(newGlob)s for %(reason)s", {senderName, oldGlob: prevEntity, newGlob: entity, reason}, ); } else if (SERVER_RULE_TYPES.includes(event.getType())) { - return _t( + return () => _t( "%(senderName)s changed a rule that was banning servers matching %(oldGlob)s to matching " + "%(newGlob)s for %(reason)s", {senderName, oldGlob: prevEntity, newGlob: entity, reason}, @@ -567,7 +589,7 @@ function textForMjolnirEvent(event) { } // Unknown type. We'll say something but we shouldn't end up here. - return _t("%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s " + + return () => _t("%(senderName)s updated a ban rule that was matching %(oldGlob)s to matching %(newGlob)s " + "for %(reason)s", {senderName, oldGlob: prevEntity, newGlob: entity, reason}); } @@ -604,8 +626,12 @@ for (const evType of ALL_RULE_TYPES) { stateHandlers[evType] = textForMjolnirEvent; } +export function hasText(ev) { + const handler = (ev.isState() ? stateHandlers : handlers)[ev.getType()]; + return Boolean(handler?.(ev)); +} + export function textForEvent(ev) { const handler = (ev.isState() ? stateHandlers : handlers)[ev.getType()]; - if (handler) return handler(ev); - return ''; + return handler?.(ev)?.() || ''; } diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index bca62e7b2f..d040944833 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -30,7 +30,7 @@ import RoomContext from "../../contexts/RoomContext"; import {Layout, LayoutPropType} from "../../settings/Layout"; import {_t} from "../../languageHandler"; import {haveTileForEvent} from "../views/rooms/EventTile"; -import {textForEvent} from "../../TextForEvent"; +import {hasText} from "../../TextForEvent"; import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer"; import DMRoomMap from "../../utils/DMRoomMap"; import NewRoomIntro from "../views/rooms/NewRoomIntro"; @@ -1180,11 +1180,8 @@ class MemberGrouper { add(ev) { if (ev.getType() === 'm.room.member') { - // We'll just double check that it's worth our time to do so, through an - // ugly hack. If textForEvent returns something, we should group it for - // rendering but if it doesn't then we'll exclude it. - const renderText = textForEvent(ev); - if (!renderText || renderText.trim().length === 0) return; // quietly ignore + // We can ignore any events that don't actually have a message to display + if (!hasText(ev)) return; } this.readMarker = this.readMarker || this.panel._readMarkerForEvent( ev.getId(), diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 8cec067c39..fda2906f07 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -25,7 +25,7 @@ import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import ReplyThread from "../elements/ReplyThread"; import { _t } from '../../../languageHandler'; -import * as TextForEvent from "../../../TextForEvent"; +import { hasText } from "../../../TextForEvent"; import * as sdk from "../../../index"; import dis from '../../../dispatcher/dispatcher'; import SettingsStore from "../../../settings/SettingsStore"; @@ -1200,7 +1200,7 @@ export function haveTileForEvent(e) { const handler = getHandlerTile(e); if (handler === undefined) return false; if (handler === 'messages.TextualEvent') { - return TextForEvent.textForEvent(e) !== ''; + return hasText(e); } else if (handler === 'messages.RoomCreate') { return Boolean(e.getContent()['predecessor']); } else { From 1e574307d0a74c498429c6e2a22f07c61369550b Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sun, 6 Jun 2021 23:08:47 -0400 Subject: [PATCH 123/198] Cache lowBandwidth setting to speed up BaseAvatar Signed-off-by: Robin Townsend --- src/components/structures/RoomView.tsx | 5 +++++ src/components/views/avatars/BaseAvatar.tsx | 15 +++++++++++---- src/contexts/RoomContext.ts | 1 + 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index b80d909a94..e4dc90f141 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -182,6 +182,7 @@ export interface IState { canReact: boolean; canReply: boolean; layout: Layout; + lowBandwidth: boolean; showReadReceipts: boolean; showRedactions: boolean; showJoinLeaves: boolean; @@ -244,6 +245,7 @@ export default class RoomView extends React.Component { canReact: false, canReply: false, layout: SettingsStore.getValue("layout"), + lowBandwidth: SettingsStore.getValue("lowBandwidth"), showReadReceipts: true, showRedactions: true, showJoinLeaves: true, @@ -279,6 +281,9 @@ export default class RoomView extends React.Component { SettingsStore.watchSetting("layout", null, () => this.setState({ layout: SettingsStore.getValue("layout") }), ), + SettingsStore.watchSetting("lowBandwidth", null, () => + this.setState({ lowBandwidth: SettingsStore.getValue("lowBandwidth") }), + ), ]; } diff --git a/src/components/views/avatars/BaseAvatar.tsx b/src/components/views/avatars/BaseAvatar.tsx index 8ce05e0a55..6949c14636 100644 --- a/src/components/views/avatars/BaseAvatar.tsx +++ b/src/components/views/avatars/BaseAvatar.tsx @@ -22,6 +22,7 @@ import classNames from 'classnames'; import * as AvatarLogic from '../../../Avatar'; import SettingsStore from "../../../settings/SettingsStore"; import AccessibleButton from '../elements/AccessibleButton'; +import RoomContext from "../../../contexts/RoomContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {useEventEmitter} from "../../../hooks/useEventEmitter"; import {toPx} from "../../../utils/units"; @@ -44,12 +45,12 @@ interface IProps { className?: string; } -const calculateUrls = (url, urls) => { +const calculateUrls = (url, urls, lowBandwidth) => { // work out the full set of urls to try to load. This is formed like so: // imageUrls: [ props.url, ...props.urls ] let _urls = []; - if (!SettingsStore.getValue("lowBandwidth")) { + if (!lowBandwidth) { _urls = urls || []; if (url) { @@ -63,7 +64,13 @@ const calculateUrls = (url, urls) => { }; const useImageUrl = ({url, urls}): [string, () => void] => { - const [imageUrls, setUrls] = useState(calculateUrls(url, urls)); + // Since this is a hot code path and the settings store can be slow, we + // use the cached lowBandwidth value from the room context if it exists + const roomContext = useContext(RoomContext); + const lowBandwidth = roomContext ? + roomContext.lowBandwidth : SettingsStore.getValue("lowBandwidth"); + + const [imageUrls, setUrls] = useState(calculateUrls(url, urls, lowBandwidth)); const [urlsIndex, setIndex] = useState(0); const onError = useCallback(() => { @@ -71,7 +78,7 @@ const useImageUrl = ({url, urls}): [string, () => void] => { }, []); useEffect(() => { - setUrls(calculateUrls(url, urls)); + setUrls(calculateUrls(url, urls, lowBandwidth)); setIndex(0); }, [url, JSON.stringify(urls)]); // eslint-disable-line react-hooks/exhaustive-deps diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts index 1efa1c03e7..3464f952a6 100644 --- a/src/contexts/RoomContext.ts +++ b/src/contexts/RoomContext.ts @@ -40,6 +40,7 @@ const RoomContext = createContext({ canReact: false, canReply: false, layout: Layout.Group, + lowBandwidth: false, showReadReceipts: true, showRedactions: true, showJoinLeaves: true, From 21ff1f521fa191308852429214d2697ee35adda8 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Sun, 6 Jun 2021 23:14:26 -0400 Subject: [PATCH 124/198] Fix i18n strings Signed-off-by: Robin Townsend --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9e85ea28c8..5108ea7fe2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -556,8 +556,8 @@ "%(senderName)s made future room history visible to all room members.": "%(senderName)s made future room history visible to all room members.", "%(senderName)s made future room history visible to anyone.": "%(senderName)s made future room history visible to anyone.", "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s made future room history visible to unknown (%(visibility)s).", - "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s", "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s changed the power level of %(powerLevelDiffText)s.", + "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s", "%(senderName)s changed the pinned messages for the room.": "%(senderName)s changed the pinned messages for the room.", "%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s", "%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s", From 31d308a1fbdd94176a8a3ea43e1e20f5b41590db Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Jun 2021 09:22:47 +0100 Subject: [PATCH 125/198] Fix Stickerpicker context menu --- .../views/context_menus/WidgetContextMenu.tsx | 46 ++++++++++++------- src/components/views/elements/AppTile.js | 2 + src/components/views/rooms/Stickerpicker.js | 6 +-- src/stores/widgets/WidgetLayoutStore.ts | 2 +- 4 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/components/views/context_menus/WidgetContextMenu.tsx b/src/components/views/context_menus/WidgetContextMenu.tsx index 623fe04f2f..068bfd6497 100644 --- a/src/components/views/context_menus/WidgetContextMenu.tsx +++ b/src/components/views/context_menus/WidgetContextMenu.tsx @@ -40,6 +40,8 @@ interface IProps extends React.ComponentProps { showUnpin?: boolean; // override delete handler onDeleteClick?(): void; + // override edit handler + onEditClick?(): void; } const WidgetContextMenu: React.FC = ({ @@ -47,6 +49,7 @@ const WidgetContextMenu: React.FC = ({ app, userWidget, onDeleteClick, + onEditClick, showUnpin, ...props }) => { @@ -89,12 +92,16 @@ const WidgetContextMenu: React.FC = ({ let editButton; if (canModify && WidgetUtils.isManagedByManager(app)) { - const onEditClick = () => { - WidgetUtils.editWidget(room, app); + const _onEditClick = () => { + if (onEditClick) { + onEditClick(); + } else { + WidgetUtils.editWidget(room, app); + } onFinished(); }; - editButton = ; + editButton = ; } let snapshotButton; @@ -116,24 +123,29 @@ const WidgetContextMenu: React.FC = ({ let deleteButton; if (onDeleteClick || canModify) { - const onDeleteClickDefault = () => { - // Show delete confirmation dialog - Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, { - title: _t("Delete Widget"), - description: _t( - "Deleting a widget removes it for all users in this room." + - " Are you sure you want to delete this widget?"), - button: _t("Delete widget"), - onFinished: (confirmed) => { - if (!confirmed) return; - WidgetUtils.setRoomWidget(roomId, app.id); - }, - }); + const _onDeleteClick = () => { + if (onDeleteClick) { + onDeleteClick(); + } else { + // Show delete confirmation dialog + Modal.createTrackedDialog('Delete Widget', '', QuestionDialog, { + title: _t("Delete Widget"), + description: _t( + "Deleting a widget removes it for all users in this room." + + " Are you sure you want to delete this widget?"), + button: _t("Delete widget"), + onFinished: (confirmed) => { + if (!confirmed) return; + WidgetUtils.setRoomWidget(roomId, app.id); + }, + }); + } + onFinished(); }; deleteButton = ; } diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index b898ad2ebc..ad5cbfa6b3 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -417,6 +417,8 @@ export default class AppTile extends React.Component { onFinished={this._closeContextMenu} showUnpin={!this.props.userWidget} userWidget={this.props.userWidget} + onEditClick={this.props.onEditClick} + onDeleteClick={this.props.onDeleteClick} /> ); } diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index 3d2300b83c..b9aaee7a57 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -367,7 +367,7 @@ export default class Stickerpicker extends React.PureComponent { /** * Launch the integration manager on the stickers integration page */ - _launchManageIntegrations() { + _launchManageIntegrations = () => { // TODO: Open the right integration manager for the widget if (SettingsStore.getValue("feature_many_integration_managers")) { IntegrationManagers.sharedInstance().openAll( @@ -382,7 +382,7 @@ export default class Stickerpicker extends React.PureComponent { this.state.widgetId, ); } - } + }; render() { let stickerPicker; @@ -401,7 +401,7 @@ export default class Stickerpicker extends React.PureComponent { key="controls_hide_stickers" className={className} onClick={this._onHideStickersClick} - active={this.state.showStickers} + active={this.state.showStickers.toString()} title={_t("Hide Stickers")} > ; diff --git a/src/stores/widgets/WidgetLayoutStore.ts b/src/stores/widgets/WidgetLayoutStore.ts index f5734d74c5..b74da98c9c 100644 --- a/src/stores/widgets/WidgetLayoutStore.ts +++ b/src/stores/widgets/WidgetLayoutStore.ts @@ -332,7 +332,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore { } public getContainerWidgets(room: Room, container: Container): IApp[] { - return this.byRoom[room.roomId]?.[container]?.ordered || []; + return this.byRoom[room?.roomId]?.[container]?.ordered || []; } public isInContainer(room: Room, widget: IApp, container: Container): boolean { From a6ddffe74c8f081e4a99ea087cfa51622a5c9769 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 7 Jun 2021 09:26:42 +0100 Subject: [PATCH 126/198] Add scroll token to file and notif event tiles --- src/components/structures/MessagePanel.js | 5 ----- src/components/views/rooms/EventTile.tsx | 22 ++++++++++++++-------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 6709fef814..19bf27b1d2 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -616,10 +616,6 @@ export default class MessagePanel extends React.Component { const eventId = mxEv.getId(); const highlight = (eventId === this.props.highlightedEventId); - // we can't use local echoes as scroll tokens, because their event IDs change. - // Local echos have a send "status". - const scrollToken = mxEv.status ? undefined : eventId; - const readReceipts = this._readReceiptsByEvent[eventId]; let isLastSuccessful = false; @@ -651,7 +647,6 @@ export default class MessagePanel extends React.Component { { permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId()); } + const scrollToken = this.props.mxEvent.status + ? undefined + : this.props.mxEvent.getId(); + let avatar; let sender; let avatarSize; @@ -1046,7 +1050,7 @@ export default class EventTile extends React.Component { case 'notif': { const room = this.context.getRoom(this.props.mxEvent.getRoomId()); return ( -
    +
  • -
  • + ); } case 'file_grid': { return ( - + ); } @@ -1111,7 +1115,7 @@ export default class EventTile extends React.Component { ); } return ( -
    +
  • { ircTimestamp } { avatar } { sender } @@ -1129,7 +1133,7 @@ export default class EventTile extends React.Component { showUrlPreview={false} />
  • -
    + ); } default: { @@ -1143,13 +1147,15 @@ export default class EventTile extends React.Component { // tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers return ( - React.createElement(this.props.as || "div", { + React.createElement(this.props.as || "li", { "ref": this.ref, "className": classes, "tabIndex": -1, "aria-live": ariaLive, "aria-atomic": "true", - "data-scroll-tokens": this.props["data-scroll-tokens"], + // we can't use local echoes as scroll tokens, because their event IDs change. + // Local echos have a send "status". + "data-scroll-tokens": scrollToken, "onMouseEnter": () => this.setState({ hover: true }), "onMouseLeave": () => this.setState({ hover: false }), }, [ From d2ce670d3ec2c4c01653b3150d9b1d610bdec9e7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Jun 2021 10:56:10 +0100 Subject: [PATCH 127/198] Remove unused code --- src/utils/Accessibility.js | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 src/utils/Accessibility.js diff --git a/src/utils/Accessibility.js b/src/utils/Accessibility.js deleted file mode 100644 index f4909f971b..0000000000 --- a/src/utils/Accessibility.js +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/** - * Automatically focuses the captured reference when receiving a non-null - * object. Useful in scenarios where componentDidMount does not have a - * useful reference to an element, but one needs to focus the element on - * first render. Example usage: ref={focusCapturedRef} - * @param {function} ref The React reference to focus on, if not null - */ -export function focusCapturedRef(ref) { - if (ref) { - ref.focus(); - } -} From 9315a87ebfa9adacb0cf638fca65a687378c9b43 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Jun 2021 10:57:11 +0100 Subject: [PATCH 128/198] Convert EventIndex to Typescript --- src/indexing/BaseEventIndexManager.ts | 10 +- src/indexing/{EventIndex.js => EventIndex.ts} | 177 ++++++++++-------- 2 files changed, 106 insertions(+), 81 deletions(-) rename src/indexing/{EventIndex.js => EventIndex.ts} (87%) diff --git a/src/indexing/BaseEventIndexManager.ts b/src/indexing/BaseEventIndexManager.ts index 6349f31524..debcb213ca 100644 --- a/src/indexing/BaseEventIndexManager.ts +++ b/src/indexing/BaseEventIndexManager.ts @@ -35,7 +35,7 @@ export interface MatrixProfile { export interface CrawlerCheckpoint { roomId: string; token: string; - fullCrawl: boolean; + fullCrawl?: boolean; direction: string; } @@ -73,14 +73,14 @@ export interface EventAndProfile { export interface LoadArgs { roomId: string; limit: number; - fromEvent: string; - direction: string; + fromEvent?: string; + direction?: string; } export interface IndexStats { size: number; - event_count: number; - room_count: number; + eventCount: number; + roomCount: number; } /** diff --git a/src/indexing/EventIndex.js b/src/indexing/EventIndex.ts similarity index 87% rename from src/indexing/EventIndex.js rename to src/indexing/EventIndex.ts index 33f2d594ae..b6289969bd 100644 --- a/src/indexing/EventIndex.js +++ b/src/indexing/EventIndex.ts @@ -1,5 +1,5 @@ /* -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,33 +14,42 @@ See the License for the specific language governing permissions and limitations under the License. */ +import { EventEmitter } from "events"; +import { RoomMember } from 'matrix-js-sdk/src/models/room-member'; +import { EventTimeline } from 'matrix-js-sdk/src/models/event-timeline'; +import { Room } from 'matrix-js-sdk/src/models/room'; +import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; +import { EventTimelineSet } from 'matrix-js-sdk/src/models/event-timeline-set'; +import { RoomState } from 'matrix-js-sdk/src/models/room-state'; +import { TimelineWindow } from 'matrix-js-sdk/src/timeline-window'; + import PlatformPeg from "../PlatformPeg"; -import {MatrixClientPeg} from "../MatrixClientPeg"; -import {RoomMember} from 'matrix-js-sdk/src/models/room-member'; -import {EventTimeline} from 'matrix-js-sdk/src/models/event-timeline'; -import {sleep} from "../utils/promise"; +import { MatrixClientPeg } from "../MatrixClientPeg"; +import { sleep } from "../utils/promise"; import SettingsStore from "../settings/SettingsStore"; -import {EventEmitter} from "events"; -import {SettingLevel} from "../settings/SettingLevel"; +import { SettingLevel } from "../settings/SettingLevel"; +import {CrawlerCheckpoint, LoadArgs, SearchArgs} from "./BaseEventIndexManager"; + +// The time in ms that the crawler will wait loop iterations if there +// have not been any checkpoints to consume in the last iteration. +const CRAWLER_IDLE_TIME = 5000; + +// The maximum number of events our crawler should fetch in a single crawl. +const EVENTS_PER_CRAWL = 100; + +interface ICrawler { + cancel(): void; +} /* * Event indexing class that wraps the platform specific event indexing. */ export default class EventIndex extends EventEmitter { - constructor() { - super(); - this.crawlerCheckpoints = []; - // The time in ms that the crawler will wait loop iterations if there - // have not been any checkpoints to consume in the last iteration. - this._crawlerIdleTime = 5000; - // The maximum number of events our crawler should fetch in a single - // crawl. - this._eventsPerCrawl = 100; - this._crawler = null; - this._currentCheckpoint = null; - } + private crawlerCheckpoints: CrawlerCheckpoint[] = []; + private crawler: ICrawler = null; + private currentCheckpoint: CrawlerCheckpoint = null; - async init() { + public async init() { const indexManager = PlatformPeg.get().getEventIndexingManager(); this.crawlerCheckpoints = await indexManager.loadCheckpoints(); @@ -52,7 +61,7 @@ export default class EventIndex extends EventEmitter { /** * Register event listeners that are necessary for the event index to work. */ - registerListeners() { + public registerListeners() { const client = MatrixClientPeg.get(); client.on('sync', this.onSync); @@ -66,7 +75,7 @@ export default class EventIndex extends EventEmitter { /** * Remove the event index specific event listeners. */ - removeListeners() { + public removeListeners() { const client = MatrixClientPeg.get(); if (client === null) return; @@ -81,7 +90,7 @@ export default class EventIndex extends EventEmitter { /** * Get crawler checkpoints for the encrypted rooms and store them in the index. */ - async addInitialCheckpoints() { + public async addInitialCheckpoints() { const indexManager = PlatformPeg.get().getEventIndexingManager(); const client = MatrixClientPeg.get(); const rooms = client.getRooms(); @@ -102,14 +111,14 @@ export default class EventIndex extends EventEmitter { const timeline = room.getLiveTimeline(); const token = timeline.getPaginationToken("b"); - const backCheckpoint = { + const backCheckpoint: CrawlerCheckpoint = { roomId: room.roomId, token: token, direction: "b", fullCrawl: true, }; - const forwardCheckpoint = { + const forwardCheckpoint: CrawlerCheckpoint = { roomId: room.roomId, token: token, direction: "f", @@ -146,7 +155,7 @@ export default class EventIndex extends EventEmitter { * - Every other sync, tell the event index to commit all the queued up * live events */ - onSync = async (state, prevState, data) => { + private onSync = async (state: string, prevState: string, data: object) => { const indexManager = PlatformPeg.get().getEventIndexingManager(); if (prevState === "PREPARED" && state === "SYNCING") { @@ -176,7 +185,15 @@ export default class EventIndex extends EventEmitter { * otherwise we save their event id and wait for them in the Event.decrypted * listener. */ - onRoomTimeline = async (ev, room, toStartOfTimeline, removed, data) => { + private onRoomTimeline = async ( + ev: MatrixEvent, + room: Room, + toStartOfTimeline: boolean, + removed: boolean, + data: { + liveEvent: boolean; + }, + ) => { const client = MatrixClientPeg.get(); // We only index encrypted rooms locally. @@ -194,7 +211,7 @@ export default class EventIndex extends EventEmitter { await this.addLiveEventToIndex(ev); } - onRoomStateEvent = async (ev, state) => { + private onRoomStateEvent = async (ev: MatrixEvent, state: RoomState) => { if (!MatrixClientPeg.get().isRoomEncrypted(state.roomId)) return; if (ev.getType() === "m.room.encryption" && !await this.isRoomIndexed(state.roomId)) { @@ -209,7 +226,7 @@ export default class EventIndex extends EventEmitter { * Checks if the event was marked for addition in the Room.timeline * listener, if so queues it up to be added to the index. */ - onEventDecrypted = async (ev, err) => { + private onEventDecrypted = async (ev: MatrixEvent, err: Error) => { // If the event isn't in our live event set, ignore it. if (err) return; await this.addLiveEventToIndex(ev); @@ -220,7 +237,7 @@ export default class EventIndex extends EventEmitter { * * Removes a redacted event from our event index. */ - onRedaction = async (ev, room) => { + private onRedaction = async (ev: MatrixEvent, room: Room) => { // We only index encrypted rooms locally. if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return; const indexManager = PlatformPeg.get().getEventIndexingManager(); @@ -238,7 +255,7 @@ export default class EventIndex extends EventEmitter { * Listens for timeline resets that are caused by a limited timeline to * re-add checkpoints for rooms that need to be crawled again. */ - onTimelineReset = async (room, timelineSet, resetAllTimelines) => { + private onTimelineReset = async (room: Room, timelineSet: EventTimelineSet, resetAllTimelines: boolean) => { if (room === null) return; if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return; @@ -258,7 +275,7 @@ export default class EventIndex extends EventEmitter { * @returns {bool} Returns true if the event can be indexed, false * otherwise. */ - isValidEvent(ev) { + private isValidEvent(ev: MatrixEvent) { const isUsefulType = ["m.room.message", "m.room.name", "m.room.topic"].includes(ev.getType()); const validEventType = isUsefulType && !ev.isRedacted() && !ev.isDecryptionFailure(); @@ -282,7 +299,7 @@ export default class EventIndex extends EventEmitter { return validEventType && validMsgType && hasContentValue; } - eventToJson(ev) { + private eventToJson(ev: MatrixEvent) { const jsonEvent = ev.toJSON(); const e = ev.isEncrypted() ? jsonEvent.decrypted : jsonEvent; @@ -314,7 +331,7 @@ export default class EventIndex extends EventEmitter { * * @param {MatrixEvent} ev The event that should be added to the index. */ - async addLiveEventToIndex(ev) { + private async addLiveEventToIndex(ev: MatrixEvent) { const indexManager = PlatformPeg.get().getEventIndexingManager(); if (!this.isValidEvent(ev)) return; @@ -333,11 +350,11 @@ export default class EventIndex extends EventEmitter { * Emmit that the crawler has changed the checkpoint that it's currently * handling. */ - emitNewCheckpoint() { + private emitNewCheckpoint() { this.emit("changedCheckpoint", this.currentRoom()); } - async addEventsFromLiveTimeline(timeline) { + private async addEventsFromLiveTimeline(timeline: EventTimeline) { const events = timeline.getEvents(); for (let i = 0; i < events.length; i++) { @@ -346,7 +363,7 @@ export default class EventIndex extends EventEmitter { } } - async addRoomCheckpoint(roomId, fullCrawl = false) { + private async addRoomCheckpoint(roomId: string, fullCrawl = false) { const indexManager = PlatformPeg.get().getEventIndexingManager(); const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); @@ -396,16 +413,16 @@ export default class EventIndex extends EventEmitter { * crawl, otherwise create a new checkpoint and push it to the * crawlerCheckpoints queue so we go through them in a round-robin way. */ - async crawlerFunc() { + private async crawlerFunc() { let cancelled = false; const client = MatrixClientPeg.get(); const indexManager = PlatformPeg.get().getEventIndexingManager(); - this._crawler = {}; - - this._crawler.cancel = () => { - cancelled = true; + this.crawler = { + cancel: () => { + cancelled = true; + }, }; let idle = false; @@ -417,11 +434,11 @@ export default class EventIndex extends EventEmitter { sleepTime = Math.max(sleepTime, 100); if (idle) { - sleepTime = this._crawlerIdleTime; + sleepTime = CRAWLER_IDLE_TIME; } - if (this._currentCheckpoint !== null) { - this._currentCheckpoint = null; + if (this.currentCheckpoint !== null) { + this.currentCheckpoint = null; this.emitNewCheckpoint(); } @@ -440,7 +457,7 @@ export default class EventIndex extends EventEmitter { continue; } - this._currentCheckpoint = checkpoint; + this.currentCheckpoint = checkpoint; this.emitNewCheckpoint(); idle = false; @@ -454,8 +471,11 @@ export default class EventIndex extends EventEmitter { try { res = await client.createMessagesRequest( - checkpoint.roomId, checkpoint.token, this._eventsPerCrawl, - checkpoint.direction); + checkpoint.roomId, + checkpoint.token, + EVENTS_PER_CRAWL, + checkpoint.direction, + ); } catch (e) { if (e.httpStatus === 403) { console.log("EventIndex: Removing checkpoint as we don't have ", @@ -612,23 +632,23 @@ export default class EventIndex extends EventEmitter { } } - this._crawler = null; + this.crawler = null; } /** * Start the crawler background task. */ - startCrawler() { - if (this._crawler !== null) return; + public startCrawler() { + if (this.crawler !== null) return; this.crawlerFunc(); } /** * Stop the crawler background task. */ - stopCrawler() { - if (this._crawler === null) return; - this._crawler.cancel(); + public stopCrawler() { + if (this.crawler === null) return; + this.crawler.cancel(); } /** @@ -637,7 +657,7 @@ export default class EventIndex extends EventEmitter { * This removes all the MatrixClient event listeners, stops the crawler * task, and closes the index. */ - async close() { + public async close() { const indexManager = PlatformPeg.get().getEventIndexingManager(); this.removeListeners(); this.stopCrawler(); @@ -654,7 +674,7 @@ export default class EventIndex extends EventEmitter { * @return {Promise<[SearchResult]>} A promise that will resolve to an array * of search results once the search is done. */ - async search(searchArgs) { + public async search(searchArgs: SearchArgs) { const indexManager = PlatformPeg.get().getEventIndexingManager(); return indexManager.searchEventIndex(searchArgs); } @@ -680,11 +700,16 @@ export default class EventIndex extends EventEmitter { * @returns {Promise} Resolves to an array of events that * contain URLs. */ - async loadFileEvents(room, limit = 10, fromEvent = null, direction = EventTimeline.BACKWARDS) { + public async loadFileEvents( + room: Room, + limit = 10, + fromEvent: string = null, + direction: string = EventTimeline.BACKWARDS, + ) { const client = MatrixClientPeg.get(); const indexManager = PlatformPeg.get().getEventIndexingManager(); - const loadArgs = { + const loadArgs: LoadArgs = { roomId: room.roomId, limit: limit, }; @@ -772,13 +797,13 @@ export default class EventIndex extends EventEmitter { * @returns {Promise} Resolves to true if events were added to the * timeline, false otherwise. */ - async populateFileTimeline( - timelineSet, - timeline, - room, + public async populateFileTimeline( + timelineSet: EventTimelineSet, + timeline: EventTimeline, + room: Room, limit = 10, - fromEvent = null, - direction = EventTimeline.BACKWARDS, + fromEvent: string = null, + direction: string = EventTimeline.BACKWARDS, ) { const matrixEvents = await this.loadFileEvents(room, limit, fromEvent, direction); @@ -837,7 +862,7 @@ export default class EventIndex extends EventEmitter { * @returns {Promise} Resolves to a boolean which is true if more * events were successfully retrieved. */ - paginateTimelineWindow(room, timelineWindow, direction, limit) { + public paginateTimelineWindow(room: Room, timelineWindow: TimelineWindow, direction: string, limit: number) { const tl = timelineWindow.getTimelineIndex(direction); if (!tl) return Promise.resolve(false); @@ -871,7 +896,7 @@ export default class EventIndex extends EventEmitter { * @return {Promise} A promise that will resolve to the index * statistics. */ - async getStats() { + public async getStats() { const indexManager = PlatformPeg.get().getEventIndexingManager(); return indexManager.getStats(); } @@ -885,7 +910,7 @@ export default class EventIndex extends EventEmitter { * @return {Promise} Returns true if the index contains events for * the given room, false otherwise. */ - async isRoomIndexed(roomId) { + public async isRoomIndexed(roomId) { const indexManager = PlatformPeg.get().getEventIndexingManager(); return indexManager.isRoomIndexed(roomId); } @@ -896,21 +921,21 @@ export default class EventIndex extends EventEmitter { * @returns {Room} A MatrixRoom that is being currently crawled, null * if no room is currently being crawled. */ - currentRoom() { - if (this._currentCheckpoint === null && this.crawlerCheckpoints.length === 0) { + public currentRoom() { + if (this.currentCheckpoint === null && this.crawlerCheckpoints.length === 0) { return null; } const client = MatrixClientPeg.get(); - if (this._currentCheckpoint !== null) { - return client.getRoom(this._currentCheckpoint.roomId); + if (this.currentCheckpoint !== null) { + return client.getRoom(this.currentCheckpoint.roomId); } else { return client.getRoom(this.crawlerCheckpoints[0].roomId); } } - crawlingRooms() { + public crawlingRooms() { const totalRooms = new Set(); const crawlingRooms = new Set(); @@ -918,14 +943,14 @@ export default class EventIndex extends EventEmitter { crawlingRooms.add(checkpoint.roomId); }); - if (this._currentCheckpoint !== null) { - crawlingRooms.add(this._currentCheckpoint.roomId); + if (this.currentCheckpoint !== null) { + crawlingRooms.add(this.currentCheckpoint.roomId); } const client = MatrixClientPeg.get(); const rooms = client.getRooms(); - const isRoomEncrypted = (room) => { + const isRoomEncrypted = (room: Room) => { return client.isRoomEncrypted(room.roomId); }; @@ -934,6 +959,6 @@ export default class EventIndex extends EventEmitter { totalRooms.add(room.roomId); }); - return {crawlingRooms, totalRooms}; + return { crawlingRooms, totalRooms }; } } From 3431ebb9957cb8424eb2ee32b4f87d445a6daf6b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Jun 2021 10:57:25 +0100 Subject: [PATCH 129/198] Convert AsyncWrapper to Typescript --- src/{AsyncWrapper.js => AsyncWrapper.tsx} | 63 ++++++++++++----------- 1 file changed, 34 insertions(+), 29 deletions(-) rename src/{AsyncWrapper.js => AsyncWrapper.tsx} (59%) diff --git a/src/AsyncWrapper.js b/src/AsyncWrapper.tsx similarity index 59% rename from src/AsyncWrapper.js rename to src/AsyncWrapper.tsx index 359828b312..1d3c42e60b 100644 --- a/src/AsyncWrapper.js +++ b/src/AsyncWrapper.tsx @@ -1,6 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2015-2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,52 +14,63 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; -import * as sdk from './index'; -import PropTypes from 'prop-types'; +import React, { ComponentType } from "react"; + import { _t } from './languageHandler'; +import { IDialogProps } from "./components/views/dialogs/IDialogProps"; +import BaseDialog from "./components/views/dialogs/BaseDialog"; +import DialogButtons from "./components/views/elements/DialogButtons"; +import Spinner from "./components/views/elements/Spinner"; + +type AsyncImport = { default: T }; + +interface IProps extends IDialogProps { + // A promise which resolves with the real component + prom: Promise>; +} + +interface IState { + component?: ComponentType; + error?: Error; +} /** * Wrap an asynchronous loader function with a react component which shows a * spinner until the real component loads. */ -export default class AsyncWrapper extends React.Component { - static propTypes = { - /** A promise which resolves with the real component - */ - prom: PropTypes.object.isRequired, - }; +export default class AsyncWrapper extends React.Component { + private unmounted = false; - state = { + public state = { component: null, error: null, }; componentDidMount() { - this._unmounted = false; // XXX: temporary logging to try to diagnose // https://github.com/vector-im/element-web/issues/3148 console.log('Starting load of AsyncWrapper for modal'); this.props.prom.then((result) => { - if (this._unmounted) { - return; - } + if (this.unmounted) return; + // Take the 'default' member if it's there, then we support // passing in just an import()ed module, since ES6 async import // always returns a module *namespace*. - const component = result.default ? result.default : result; - this.setState({component}); + const component = (result as AsyncImport).default + ? (result as AsyncImport).default + : result as ComponentType; + this.setState({ component }); }).catch((e) => { console.warn('AsyncWrapper promise failed', e); - this.setState({error: e}); + this.setState({ error: e }); }); } componentWillUnmount() { - this._unmounted = true; + this.unmounted = true; } - _onWrapperCancelClick = () => { + private onWrapperCancelClick = () => { this.props.onFinished(false); }; @@ -69,20 +79,15 @@ export default class AsyncWrapper extends React.Component { const Component = this.state.component; return ; } else if (this.state.error) { - const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); - const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); - return - {_t("Unable to load! Check your network connectivity and try again.")} + return + { _t("Unable to load! Check your network connectivity and try again.") } ; } else { // show a spinner until the component is loaded. - const Spinner = sdk.getComponent("elements.Spinner"); return ; } } From b2d9dd7214c5f72add24d79c32b763b7c916187c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Jun 2021 10:57:39 +0100 Subject: [PATCH 130/198] Convert LifecycleStore to Typescript --- .../{LifecycleStore.js => LifecycleStore.ts} | 50 ++++++++++--------- 1 file changed, 27 insertions(+), 23 deletions(-) rename src/stores/{LifecycleStore.js => LifecycleStore.ts} (62%) diff --git a/src/stores/LifecycleStore.js b/src/stores/LifecycleStore.ts similarity index 62% rename from src/stores/LifecycleStore.js rename to src/stores/LifecycleStore.ts index a12bac7dd6..5381fc58ed 100644 --- a/src/stores/LifecycleStore.js +++ b/src/stores/LifecycleStore.ts @@ -1,7 +1,5 @@ /* -Copyright 2017 Vector Creations Ltd -Copyright 2018 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2017-2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,11 +13,18 @@ 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 { Store } from 'flux/utils'; + import dis from '../dispatcher/dispatcher'; -import {Store} from 'flux/utils'; +import { ActionPayload } from "../dispatcher/payloads"; + +interface IState { + deferredAction: any; +} const INITIAL_STATE = { - deferred_action: null, + deferredAction: null, }; /** @@ -27,39 +32,38 @@ const INITIAL_STATE = { * store that listens for actions and updates its state accordingly, informing any * listeners (views) of state changes. */ -class LifecycleStore extends Store { +class LifecycleStore extends Store { + private state: IState = INITIAL_STATE; + constructor() { super(dis); - - // Initialise state - this._state = INITIAL_STATE; } - _setState(newState) { - this._state = Object.assign(this._state, newState); + private setState(newState: Partial) { + this.state = Object.assign(this.state, newState); this.__emitChange(); } - __onDispatch(payload) { + protected __onDispatch(payload: ActionPayload) { switch (payload.action) { case 'do_after_sync_prepared': - this._setState({ - deferred_action: payload.deferred_action, + this.setState({ + deferredAction: payload.deferred_action, }); break; case 'cancel_after_sync_prepared': - this._setState({ - deferred_action: null, + this.setState({ + deferredAction: null, }); break; - case 'sync_state': { + case 'syncstate': { if (payload.state !== 'PREPARED') { break; } - if (!this._state.deferred_action) break; - const deferredAction = Object.assign({}, this._state.deferred_action); - this._setState({ - deferred_action: null, + if (!this.state.deferredAction) break; + const deferredAction = Object.assign({}, this.state.deferredAction); + this.setState({ + deferredAction: null, }); dis.dispatch(deferredAction); break; @@ -71,8 +75,8 @@ class LifecycleStore extends Store { } } - reset() { - this._state = Object.assign({}, INITIAL_STATE); + private reset() { + this.state = Object.assign({}, INITIAL_STATE); } } From c8b3acb7e4a23d5cd654a2d7234d9f3a5b62405d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Jun 2021 10:57:46 +0100 Subject: [PATCH 131/198] Convert utils to Typescript --- src/utils/{UrlUtils.js => UrlUtils.ts} | 6 +++--- src/utils/{humanize.js => humanize.ts} | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) rename src/utils/{UrlUtils.js => UrlUtils.ts} (88%) rename src/utils/{humanize.js => humanize.ts} (83%) diff --git a/src/utils/UrlUtils.js b/src/utils/UrlUtils.ts similarity index 88% rename from src/utils/UrlUtils.js rename to src/utils/UrlUtils.ts index 7b207c128e..6f441ff98e 100644 --- a/src/utils/UrlUtils.js +++ b/src/utils/UrlUtils.ts @@ -1,5 +1,5 @@ /* -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import url from "url"; * @param {string} u The url to be abbreviated * @returns {string} The abbreviated url */ -export function abbreviateUrl(u) { +export function abbreviateUrl(u: string): string { if (!u) return ''; const parsedUrl = url.parse(u); @@ -37,7 +37,7 @@ export function abbreviateUrl(u) { return u; } -export function unabbreviateUrl(u) { +export function unabbreviateUrl(u: string): string { if (!u) return ''; let longUrl = u; diff --git a/src/utils/humanize.js b/src/utils/humanize.ts similarity index 83% rename from src/utils/humanize.js rename to src/utils/humanize.ts index 6e6f17f8f4..55bdbf760b 100644 --- a/src/utils/humanize.js +++ b/src/utils/humanize.ts @@ -1,5 +1,5 @@ /* -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2020, 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,22 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {_t} from "../languageHandler"; +import { _t } from "../languageHandler"; + +// These are the constants we use for when to break the text +const MILLISECONDS_RECENT = 15000; +const MILLISECONDS_1_MIN = 75000; +const MINUTES_UNDER_1_HOUR = 45; +const MINUTES_1_HOUR = 75; +const HOURS_UNDER_1_DAY = 23; +const HOURS_1_DAY = 26; /** * Converts a timestamp into human-readable, translated, text. * @param {number} timeMillis The time in millis to compare against. * @returns {string} The humanized time. */ -export function humanizeTime(timeMillis) { - // These are the constants we use for when to break the text - const MILLISECONDS_RECENT = 15000; - const MILLISECONDS_1_MIN = 75000; - const MINUTES_UNDER_1_HOUR = 45; - const MINUTES_1_HOUR = 75; - const HOURS_UNDER_1_DAY = 23; - const HOURS_1_DAY = 26; - +export function humanizeTime(timeMillis: number): string { const now = (new Date()).getTime(); let msAgo = now - timeMillis; const minutes = Math.abs(Math.ceil(msAgo / 60000)); From 50d3e04bdabc2cfd6d23381f18de11d4c7ef1287 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Jun 2021 10:58:22 +0100 Subject: [PATCH 132/198] Convert MatrixActionCreators to Typescript --- ...ionCreators.js => MatrixActionCreators.ts} | 125 +++++++++++------- 1 file changed, 77 insertions(+), 48 deletions(-) rename src/actions/{MatrixActionCreators.js => MatrixActionCreators.ts} (68%) diff --git a/src/actions/MatrixActionCreators.js b/src/actions/MatrixActionCreators.ts similarity index 68% rename from src/actions/MatrixActionCreators.js rename to src/actions/MatrixActionCreators.ts index 93a4fcf07c..33e72becd2 100644 --- a/src/actions/MatrixActionCreators.js +++ b/src/actions/MatrixActionCreators.ts @@ -1,5 +1,5 @@ /* -Copyright 2017 New Vector Ltd +Copyright 2017, 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,7 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import dis from '../dispatcher/dispatcher'; +import { MatrixClient } from "matrix-js-sdk/src/client"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { Room } from "matrix-js-sdk/src/models/room"; +import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline"; + +import dis from "../dispatcher/dispatcher"; +import {ActionPayload} from "../dispatcher/payloads"; // TODO: migrate from sync_state to MatrixActions.sync so that more js-sdk events // become dispatches in the same place. @@ -27,7 +33,7 @@ import dis from '../dispatcher/dispatcher'; * @param {string} prevState the previous sync state. * @returns {Object} an action of type MatrixActions.sync. */ -function createSyncAction(matrixClient, state, prevState) { +function createSyncAction(matrixClient: MatrixClient, state: string, prevState: string): ActionPayload { return { action: 'MatrixActions.sync', state, @@ -53,7 +59,7 @@ function createSyncAction(matrixClient, state, prevState) { * @param {MatrixEvent} accountDataEvent the account data event. * @returns {AccountDataAction} an action of type MatrixActions.accountData. */ -function createAccountDataAction(matrixClient, accountDataEvent) { +function createAccountDataAction(matrixClient: MatrixClient, accountDataEvent: MatrixEvent): ActionPayload { return { action: 'MatrixActions.accountData', event: accountDataEvent, @@ -81,7 +87,11 @@ function createAccountDataAction(matrixClient, accountDataEvent) { * @param {Room} room the room where account data was changed * @returns {RoomAccountDataAction} an action of type MatrixActions.Room.accountData. */ -function createRoomAccountDataAction(matrixClient, accountDataEvent, room) { +function createRoomAccountDataAction( + matrixClient: MatrixClient, + accountDataEvent: MatrixEvent, + room: Room, +): ActionPayload { return { action: 'MatrixActions.Room.accountData', event: accountDataEvent, @@ -106,7 +116,7 @@ function createRoomAccountDataAction(matrixClient, accountDataEvent, room) { * @param {Room} room the Room that was stored. * @returns {RoomAction} an action of type `MatrixActions.Room`. */ -function createRoomAction(matrixClient, room) { +function createRoomAction(matrixClient: MatrixClient, room: Room): ActionPayload { return { action: 'MatrixActions.Room', room }; } @@ -127,7 +137,7 @@ function createRoomAction(matrixClient, room) { * @param {Room} room the Room whose tags were changed. * @returns {RoomTagsAction} an action of type `MatrixActions.Room.tags`. */ -function createRoomTagsAction(matrixClient, roomTagsEvent, room) { +function createRoomTagsAction(matrixClient: MatrixClient, roomTagsEvent: MatrixEvent, room: Room): ActionPayload { return { action: 'MatrixActions.Room.tags', room }; } @@ -140,7 +150,7 @@ function createRoomTagsAction(matrixClient, roomTagsEvent, room) { * @param {Room} room the room the receipt happened in. * @returns {Object} an action of type MatrixActions.Room.receipt. */ -function createRoomReceiptAction(matrixClient, event, room) { +function createRoomReceiptAction(matrixClient: MatrixClient, event: MatrixEvent, room: Room): ActionPayload { return { action: 'MatrixActions.Room.receipt', event, @@ -178,7 +188,17 @@ function createRoomReceiptAction(matrixClient, event, room) { * @param {EventTimeline} data.timeline the timeline being altered. * @returns {RoomTimelineAction} an action of type `MatrixActions.Room.timeline`. */ -function createRoomTimelineAction(matrixClient, timelineEvent, room, toStartOfTimeline, removed, data) { +function createRoomTimelineAction( + matrixClient: MatrixClient, + timelineEvent: MatrixEvent, + room: Room, + toStartOfTimeline: boolean, + removed: boolean, + data: { + liveEvent: boolean; + timeline: EventTimeline; + }, +): ActionPayload { return { action: 'MatrixActions.Room.timeline', event: timelineEvent, @@ -208,8 +228,13 @@ function createRoomTimelineAction(matrixClient, timelineEvent, room, toStartOfTi * @param {string} oldMembership the previous membership, can be null. * @returns {RoomMembershipAction} an action of type `MatrixActions.Room.myMembership`. */ -function createSelfMembershipAction(matrixClient, room, membership, oldMembership) { - return { action: 'MatrixActions.Room.myMembership', room, membership, oldMembership}; +function createSelfMembershipAction( + matrixClient: MatrixClient, + room: Room, + membership: string, + oldMembership: string, +): ActionPayload { + return { action: 'MatrixActions.Room.myMembership', room, membership, oldMembership }; } /** @@ -228,61 +253,65 @@ function createSelfMembershipAction(matrixClient, room, membership, oldMembershi * @param {MatrixEvent} event the matrix event that was decrypted. * @returns {EventDecryptedAction} an action of type `MatrixActions.Event.decrypted`. */ -function createEventDecryptedAction(matrixClient, event) { +function createEventDecryptedAction(matrixClient: MatrixClient, event: MatrixEvent): ActionPayload { return { action: 'MatrixActions.Event.decrypted', event }; } +type Listener = () => void; +type ActionCreator = (matrixClient: MatrixClient, ...args: any) => ActionPayload; + +// A list of callbacks to call to unregister all listeners added +let matrixClientListenersStop: Listener[] = []; + +/** + * Start listening to events of type eventName on matrixClient and when they are emitted, + * dispatch an action created by the actionCreator function. + * @param {MatrixClient} matrixClient a MatrixClient to register a listener with. + * @param {string} eventName the event to listen to on MatrixClient. + * @param {function} actionCreator a function that should return an action to dispatch + * when given the MatrixClient as an argument as well as + * arguments emitted in the MatrixClient event. + */ +function addMatrixClientListener(matrixClient: MatrixClient, eventName: string, actionCreator: ActionCreator): void { + const listener: Listener = (...args) => { + const payload = actionCreator(matrixClient, ...args); + if (payload) { + dis.dispatch(payload, true); + } + }; + matrixClient.on(eventName, listener); + matrixClientListenersStop.push(() => { + matrixClient.removeListener(eventName, listener); + }); +} + /** * This object is responsible for dispatching actions when certain events are emitted by * the given MatrixClient. */ export default { - // A list of callbacks to call to unregister all listeners added - _matrixClientListenersStop: [], - /** * Start listening to certain events from the MatrixClient and dispatch actions when * they are emitted. * @param {MatrixClient} matrixClient the MatrixClient to listen to events from */ - start(matrixClient) { - this._addMatrixClientListener(matrixClient, 'sync', createSyncAction); - this._addMatrixClientListener(matrixClient, 'accountData', createAccountDataAction); - this._addMatrixClientListener(matrixClient, 'Room.accountData', createRoomAccountDataAction); - this._addMatrixClientListener(matrixClient, 'Room', createRoomAction); - this._addMatrixClientListener(matrixClient, 'Room.tags', createRoomTagsAction); - this._addMatrixClientListener(matrixClient, 'Room.receipt', createRoomReceiptAction); - this._addMatrixClientListener(matrixClient, 'Room.timeline', createRoomTimelineAction); - this._addMatrixClientListener(matrixClient, 'Room.myMembership', createSelfMembershipAction); - this._addMatrixClientListener(matrixClient, 'Event.decrypted', createEventDecryptedAction); - }, - - /** - * Start listening to events of type eventName on matrixClient and when they are emitted, - * dispatch an action created by the actionCreator function. - * @param {MatrixClient} matrixClient a MatrixClient to register a listener with. - * @param {string} eventName the event to listen to on MatrixClient. - * @param {function} actionCreator a function that should return an action to dispatch - * when given the MatrixClient as an argument as well as - * arguments emitted in the MatrixClient event. - */ - _addMatrixClientListener(matrixClient, eventName, actionCreator) { - const listener = (...args) => { - const payload = actionCreator(matrixClient, ...args); - if (payload) { - dis.dispatch(payload, true); - } - }; - matrixClient.on(eventName, listener); - this._matrixClientListenersStop.push(() => { - matrixClient.removeListener(eventName, listener); - }); + start(matrixClient: MatrixClient) { + addMatrixClientListener(matrixClient, 'sync', createSyncAction); + addMatrixClientListener(matrixClient, 'accountData', createAccountDataAction); + addMatrixClientListener(matrixClient, 'Room.accountData', createRoomAccountDataAction); + addMatrixClientListener(matrixClient, 'Room', createRoomAction); + addMatrixClientListener(matrixClient, 'Room.tags', createRoomTagsAction); + addMatrixClientListener(matrixClient, 'Room.receipt', createRoomReceiptAction); + addMatrixClientListener(matrixClient, 'Room.timeline', createRoomTimelineAction); + addMatrixClientListener(matrixClient, 'Room.myMembership', createSelfMembershipAction); + addMatrixClientListener(matrixClient, 'Event.decrypted', createEventDecryptedAction); }, /** * Stop listening to events. */ stop() { - this._matrixClientListenersStop.forEach((stopListener) => stopListener()); + matrixClientListenersStop.forEach((stopListener) => stopListener()); + matrixClientListenersStop = []; }, }; From f6e4943bef403230b0d99d4d0c7a07b1b138978f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Jun 2021 11:01:38 +0100 Subject: [PATCH 133/198] Convert RoomAliasCache to Typescript --- src/{RoomAliasCache.js => RoomAliasCache.ts} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename src/{RoomAliasCache.js => RoomAliasCache.ts} (81%) diff --git a/src/RoomAliasCache.js b/src/RoomAliasCache.ts similarity index 81% rename from src/RoomAliasCache.js rename to src/RoomAliasCache.ts index bb511ba4d7..c318db2d3f 100644 --- a/src/RoomAliasCache.js +++ b/src/RoomAliasCache.ts @@ -1,5 +1,5 @@ /* -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2019, 2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,12 +24,12 @@ limitations under the License. * A similar thing could also be achieved via `pushState` with a state object, * but keeping it separate like this seems easier in case we do want to extend. */ -const aliasToIDMap = new Map(); +const aliasToIDMap = new Map(); -export function storeRoomAliasInCache(alias, id) { +export function storeRoomAliasInCache(alias: string, id: string): void { aliasToIDMap.set(alias, id); } -export function getCachedRoomIDForAlias(alias) { +export function getCachedRoomIDForAlias(alias: string): string { return aliasToIDMap.get(alias); } From 2525db6fb131ce760673463000895ed73f9d1ef8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Jun 2021 11:03:39 +0100 Subject: [PATCH 134/198] Convert Resend to Typescript --- src/{Resend.js => Resend.ts} | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) rename src/{Resend.js => Resend.ts} (70%) diff --git a/src/Resend.js b/src/Resend.ts similarity index 70% rename from src/Resend.js rename to src/Resend.ts index f1e5fb38f5..38b84a28e0 100644 --- a/src/Resend.js +++ b/src/Resend.ts @@ -1,6 +1,5 @@ /* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. +Copyright 2015-2021 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -15,35 +14,37 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {MatrixClientPeg} from './MatrixClientPeg'; +import { MatrixEvent, EventStatus } from 'matrix-js-sdk/src/models/event'; +import { Room } from 'matrix-js-sdk/src/models/room'; + +import { MatrixClientPeg } from './MatrixClientPeg'; import dis from './dispatcher/dispatcher'; -import { EventStatus } from 'matrix-js-sdk/src/models/event'; export default class Resend { - static resendUnsentEvents(room) { - return Promise.all(room.getPendingEvents().filter(function(ev) { + static resendUnsentEvents(room: Room): Promise { + return Promise.all(room.getPendingEvents().filter(function(ev: MatrixEvent) { return ev.status === EventStatus.NOT_SENT; - }).map(function(event) { + }).map(function(event: MatrixEvent) { return Resend.resend(event); })); } - static cancelUnsentEvents(room) { - room.getPendingEvents().filter(function(ev) { + static cancelUnsentEvents(room: Room): void { + room.getPendingEvents().filter(function(ev: MatrixEvent) { return ev.status === EventStatus.NOT_SENT; - }).forEach(function(event) { + }).forEach(function(event: MatrixEvent) { Resend.removeFromQueue(event); }); } - static resend(event) { + static resend(event: MatrixEvent): Promise { const room = MatrixClientPeg.get().getRoom(event.getRoomId()); return MatrixClientPeg.get().resendEvent(event, room).then(function(res) { dis.dispatch({ action: 'message_sent', event: event, }); - }, function(err) { + }, function(err: Error) { // XXX: temporary logging to try to diagnose // https://github.com/vector-im/element-web/issues/3148 console.log('Resend got send failure: ' + err.name + '(' + err + ')'); @@ -55,7 +56,7 @@ export default class Resend { }); } - static removeFromQueue(event) { + static removeFromQueue(event: MatrixEvent): void { MatrixClientPeg.get().cancelPendingEvent(event); } } From ae04b2ce4ac0ea4ee425d848903aa5b5ec9db82f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Jun 2021 11:06:30 +0100 Subject: [PATCH 135/198] Fix Dialog/Modal types defn --- src/components/views/dialogs/IDialogProps.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/IDialogProps.ts b/src/components/views/dialogs/IDialogProps.ts index 1027ca7607..b294fdafe1 100644 --- a/src/components/views/dialogs/IDialogProps.ts +++ b/src/components/views/dialogs/IDialogProps.ts @@ -15,5 +15,5 @@ limitations under the License. */ export interface IDialogProps { - onFinished: (bool) => void; + onFinished(...args: any): void; } From 4cc95dd1841111e93a18d1a7a8630abed681449b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Jun 2021 11:20:35 +0100 Subject: [PATCH 136/198] Make skinning less upset --- src/AsyncWrapper.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/AsyncWrapper.tsx b/src/AsyncWrapper.tsx index 1d3c42e60b..3bbef71093 100644 --- a/src/AsyncWrapper.tsx +++ b/src/AsyncWrapper.tsx @@ -16,11 +16,9 @@ limitations under the License. import React, { ComponentType } from "react"; +import * as sdk from './index'; import { _t } from './languageHandler'; import { IDialogProps } from "./components/views/dialogs/IDialogProps"; -import BaseDialog from "./components/views/dialogs/BaseDialog"; -import DialogButtons from "./components/views/elements/DialogButtons"; -import Spinner from "./components/views/elements/Spinner"; type AsyncImport = { default: T }; @@ -79,6 +77,8 @@ export default class AsyncWrapper extends React.Component { const Component = this.state.component; return ; } else if (this.state.error) { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); return { _t("Unable to load! Check your network connectivity and try again.") } { ; } else { // show a spinner until the component is loaded. + const Spinner = sdk.getComponent("elements.Spinner"); return ; } } From 6693c39709fb5fae664ce27e20343dfca347d278 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 7 Jun 2021 12:17:29 +0100 Subject: [PATCH 137/198] make event list summary a list item --- src/components/views/elements/EventListSummary.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/EventListSummary.tsx b/src/components/views/elements/EventListSummary.tsx index 1d3b6e8764..86d3e082ad 100644 --- a/src/components/views/elements/EventListSummary.tsx +++ b/src/components/views/elements/EventListSummary.tsx @@ -63,9 +63,9 @@ const EventListSummary: React.FC = ({ // If we are only given few events then just pass them through if (events.length < threshold) { return ( -
    +
  • { children } -
  • + ); } From ea6904ce2a10e9aa368db43a900ea95fbb134911 Mon Sep 17 00:00:00 2001 From: Germain Date: Mon, 7 Jun 2021 12:37:11 +0100 Subject: [PATCH 138/198] Fix local echo comment for scroll tokens Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/views/rooms/EventTile.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 0796475bf8..ff310e1c43 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -906,6 +906,8 @@ export default class EventTile extends React.Component { permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId()); } + // we can't use local echoes as scroll tokens, because their event IDs change. + // Local echos have a send "status". const scrollToken = this.props.mxEvent.status ? undefined : this.props.mxEvent.getId(); @@ -1153,8 +1155,6 @@ export default class EventTile extends React.Component { "tabIndex": -1, "aria-live": ariaLive, "aria-atomic": "true", - // we can't use local echoes as scroll tokens, because their event IDs change. - // Local echos have a send "status". "data-scroll-tokens": scrollToken, "onMouseEnter": () => this.setState({ hover: true }), "onMouseLeave": () => this.setState({ hover: false }), From f3aa5056730c1bc94ca4988f0abacba30d4348ab Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 7 Jun 2021 14:14:31 +0100 Subject: [PATCH 139/198] Add warning to private space creation flow --- res/css/structures/_SpaceRoomView.scss | 39 +++++++++++++++++++++ src/components/structures/SpaceRoomView.tsx | 4 +++ src/i18n/strings/en_EN.json | 2 ++ 3 files changed, 45 insertions(+) diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index 4bc4af467c..12762cbc9d 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -365,6 +365,45 @@ $SpaceRoomViewInnerWidth: 428px; } } + .mx_SpaceRoomView_betaWarning { + padding: 12px 12px 12px 54px; + position: relative; + font-size: $font-15px; + line-height: $font-24px; + width: 432px; + border-radius: 8px; + background-color: $info-plinth-bg-color; + color: $secondary-fg-color; + box-sizing: border-box; + + > h3 { + font-weight: $font-semi-bold; + font-size: inherit; + line-height: inherit; + margin: 0; + } + + > p { + font-size: inherit; + line-height: inherit; + margin: 0; + } + + &::before { + mask-image: url('$(res)/img/element-icons/room/room-summary.svg'); + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; + content: ''; + width: 20px; + height: 20px; + position: absolute; + top: 14px; + left: 14px; + background-color: $secondary-fg-color; + } + } + .mx_SpaceRoomView_inviteTeammates { // XXX remove this when spaces leaves Beta .mx_SpaceRoomView_inviteTeammates_betaDisclaimer { diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 06d2bda16e..276f4ae6ca 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -587,6 +587,10 @@ const SpaceSetupPrivateScope = ({ space, justCreatedOpts, onFinished }) => {

    { _t("Me and my teammates") }

    { _t("A private space for you and your teammates") }
    +
    +

    { _t("Teammates might not be able to view or join any private rooms you make.") }

    +

    { _t("We're working on this as part of the beta, but just want to let you know.") }

    +
    ; }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9e85ea28c8..16cfcccd11 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2723,6 +2723,8 @@ "A private space to organise your rooms": "A private space to organise your rooms", "Me and my teammates": "Me and my teammates", "A private space for you and your teammates": "A private space for you and your teammates", + "Teammates might not be able to view or join any private rooms you make.": "Teammates might not be able to view or join any private rooms you make.", + "We're working on this as part of the beta, but just want to let you know.": "We're working on this as part of the beta, but just want to let you know.", "Failed to invite the following users to your space: %(csvUsers)s": "Failed to invite the following users to your space: %(csvUsers)s", "Inviting...": "Inviting...", "Invite your teammates": "Invite your teammates", From 67c9fe1e070d41192d9ff8553523c5834ff9f38c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 7 Jun 2021 15:25:57 +0200 Subject: [PATCH 140/198] Revert "Update curly braces styling" This reverts commit ba0f6766caefb700324a0a4441e0427689b8faf7. --- src/components/views/elements/ImageView.tsx | 131 ++++++++++---------- 1 file changed, 65 insertions(+), 66 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 948e005978..c4ec3497e4 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -19,20 +19,20 @@ limitations under the License. import React, { createRef } from 'react'; import { _t } from '../../../languageHandler'; import AccessibleTooltipButton from "./AccessibleTooltipButton"; -import { Key } from "../../../Keyboard"; +import {Key} from "../../../Keyboard"; import FocusLock from "react-focus-lock"; import MemberAvatar from "../avatars/MemberAvatar"; -import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton"; +import {ContextMenuTooltipButton} from "../../../accessibility/context_menu/ContextMenuTooltipButton"; import MessageContextMenu from "../context_menus/MessageContextMenu"; -import { aboveLeftOf, ContextMenu } from '../../structures/ContextMenu'; +import {aboveLeftOf, ContextMenu} from '../../structures/ContextMenu'; import MessageTimestamp from "../messages/MessageTimestamp"; import SettingsStore from "../../../settings/SettingsStore"; -import { formatFullDate } from "../../../DateUtils"; +import {formatFullDate} from "../../../DateUtils"; import dis from '../../../dispatcher/dispatcher'; -import { replaceableComponent } from "../../../utils/replaceableComponent"; -import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks" -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; -import { normalizeWheelEvent } from "../../../utils/Mouse"; +import {replaceableComponent} from "../../../utils/replaceableComponent"; +import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks" +import {MatrixEvent} from "matrix-js-sdk/src/models/event"; +import {normalizeWheelEvent} from "../../../utils/Mouse"; // Max scale to keep gaps around the image const MAX_SCALE = 0.95; @@ -167,7 +167,7 @@ export default class ImageView extends React.Component { return; } if (newZoom >= this.state.maxZoom) { - this.setState({ zoom: this.state.maxZoom }); + this.setState({zoom: this.state.maxZoom}); return; } @@ -256,11 +256,11 @@ export default class ImageView extends React.Component { // Zoom in if we are completely zoomed out if (this.state.zoom === this.state.minZoom) { - this.setState({ zoom: this.state.maxZoom }); + this.setState({zoom: this.state.maxZoom}); return; } - this.setState({ moving: true }); + this.setState({moving: true}); this.previousX = this.state.translationX; this.previousY = this.state.translationY; this.initX = ev.pageX - this.state.translationX; @@ -294,7 +294,7 @@ export default class ImageView extends React.Component { this.initX = 0; this.initY = 0; } - this.setState({ moving: false }); + this.setState({moving: false}); }; private renderContextMenu() { @@ -302,14 +302,14 @@ export default class ImageView extends React.Component { if (this.state.contextMenuDisplayed) { contextMenu = ( ); @@ -347,10 +347,10 @@ export default class ImageView extends React.Component { const style = { cursor: cursor, transition: this.state.moving ? null : "transform 200ms ease 0s", - transform: `translateX(${ translatePixelsX }) - translateY(${ translatePixelsY }) - scale(${ zoom }) - rotate(${ rotationDegrees })`, + transform: `translateX(${translatePixelsX}) + translateY(${translatePixelsY}) + scale(${zoom}) + rotate(${rotationDegrees})`, }; let info; @@ -365,38 +365,37 @@ export default class ImageView extends React.Component { const senderName = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); const sender = (
    - { senderName } + {senderName}
    ); const messageTimestamp = ( ); const avatar = ( ); info = (
    - { avatar } + {avatar}
    - { sender } - { messageTimestamp } + {sender} + {messageTimestamp}
    ); @@ -414,10 +413,10 @@ export default class ImageView extends React.Component { contextMenuButton = ( ); } @@ -428,14 +427,14 @@ export default class ImageView extends React.Component { zoomOutButton = ( + title={_t("Zoom out")} + onClick={this.onZoomOutClick}> ); zoomInButton = ( ); @@ -443,57 +442,57 @@ export default class ImageView extends React.Component { return (
    - { info } + {info}
    + title={_t("Rotate Right")} + onClick={this.onRotateClockwiseClick}> - { zoomOutButton } - { zoomInButton } + {zoomOutButton} + {zoomInButton} - { contextMenuButton } + {contextMenuButton} - { this.renderContextMenu() } + {this.renderContextMenu()}
    + ref={this.imageWrapper}>
    From 5880cb3b5f032f2a274fc1bcdd4a32b9ba31b8e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 7 Jun 2021 15:26:54 +0200 Subject: [PATCH 141/198] Better wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index c4ec3497e4..ebff886bb3 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -144,7 +144,7 @@ export default class ImageView extends React.Component { // image by default const minZoom = Math.min(zoomX, zoomY) * MAX_SCALE; - // If zoom is smaller than minZoom don't go beneath that value + // If zoom is smaller than minZoom don't go below that value const zoom = (this.state.zoom <= this.state.minZoom) ? minZoom : this.state.zoom; this.setState({ From 0918ebad0adf697a52354e8fc837a5b35ac56f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 7 Jun 2021 15:28:37 +0200 Subject: [PATCH 142/198] calculateZoomAndRotate -> setZoomAndRotation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index ebff886bb3..b5162c6084 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -104,18 +104,18 @@ export default class ImageView extends React.Component { // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); // We want to recalculate zoom whenever the window's size changes - window.addEventListener("resize", this.calculateZoomAndRotate); + window.addEventListener("resize", this.setZoomAndRotation); // After the image loads for the first time we want to calculate the zoom - this.image.current.addEventListener("load", this.calculateZoomAndRotate); + this.image.current.addEventListener("load", this.setZoomAndRotation); } componentWillUnmount() { this.focusLock.current.removeEventListener('wheel', this.onWheel); - window.removeEventListener("resize", this.calculateZoomAndRotate); - this.image.current.removeEventListener("load", this.calculateZoomAndRotate); + window.removeEventListener("resize", this.setZoomAndRotation); + this.image.current.removeEventListener("load", this.setZoomAndRotation); } - private calculateZoomAndRotate = () => { + private setZoomAndRotation = () => { const image = this.image.current; const imageWrapper = this.imageWrapper.current; @@ -203,13 +203,13 @@ export default class ImageView extends React.Component { private onRotateCounterClockwiseClick = () => { const cur = this.state.rotation; this.rotation = cur - 90; - this.calculateZoomAndRotate(); + this.setZoomAndRotation(); }; private onRotateClockwiseClick = () => { const cur = this.state.rotation; this.rotation = cur + 90; - this.calculateZoomAndRotate(); + this.setZoomAndRotation(); }; private onDownloadClick = () => { From 2e47a1176f6dd883d2b68b6d35cc13d590c52fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 7 Jun 2021 15:39:11 +0200 Subject: [PATCH 143/198] Get rid of weird rotation prop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 29 ++++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index b5162c6084..e0991a1023 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -97,29 +97,34 @@ export default class ImageView extends React.Component { private initY = 0; private previousX = 0; private previousY = 0; - private rotation = 0; componentDidMount() { // We have to use addEventListener() because the listener // needs to be passive in order to work with Chromium this.focusLock.current.addEventListener('wheel', this.onWheel, { passive: false }); // We want to recalculate zoom whenever the window's size changes - window.addEventListener("resize", this.setZoomAndRotation); + window.addEventListener("resize", this.recalculateZoom); // After the image loads for the first time we want to calculate the zoom - this.image.current.addEventListener("load", this.setZoomAndRotation); + this.image.current.addEventListener("load", this.recalculateZoom); } componentWillUnmount() { this.focusLock.current.removeEventListener('wheel', this.onWheel); - window.removeEventListener("resize", this.setZoomAndRotation); - this.image.current.removeEventListener("load", this.setZoomAndRotation); + window.removeEventListener("resize", this.recalculateZoom); + this.image.current.removeEventListener("load", this.recalculateZoom); } - private setZoomAndRotation = () => { + private recalculateZoom = () => { + this.setZoomAndRotation(); + } + + private setZoomAndRotation = (inputRotation?: number) => { const image = this.image.current; const imageWrapper = this.imageWrapper.current; - const landscape = this.rotation % 180 === 0; + const rotation = inputRotation || this.state.rotation; + + const landscape = rotation % 180 === 0; // If the image is rotated take it into account const width = landscape ? image.naturalWidth : image.naturalHeight; @@ -135,7 +140,7 @@ export default class ImageView extends React.Component { zoom: 1, minZoom: 1, maxZoom: 1, - rotation: this.rotation, + rotation: rotation, }); return; } @@ -150,7 +155,7 @@ export default class ImageView extends React.Component { this.setState({ minZoom: minZoom, maxZoom: 1, - rotation: this.rotation, + rotation: rotation, zoom: zoom, }); } @@ -202,14 +207,12 @@ export default class ImageView extends React.Component { private onRotateCounterClockwiseClick = () => { const cur = this.state.rotation; - this.rotation = cur - 90; - this.setZoomAndRotation(); + this.setZoomAndRotation(cur - 90); }; private onRotateClockwiseClick = () => { const cur = this.state.rotation; - this.rotation = cur + 90; - this.setZoomAndRotation(); + this.setZoomAndRotation(cur + 90); }; private onDownloadClick = () => { From b5d271759f227a03f1905759dcab4c92149cc15e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 7 Jun 2021 15:43:43 +0200 Subject: [PATCH 144/198] Why would I format this myself if eslint can do it for me? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index e0991a1023..d0dcb1fa00 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -19,20 +19,20 @@ limitations under the License. import React, { createRef } from 'react'; import { _t } from '../../../languageHandler'; import AccessibleTooltipButton from "./AccessibleTooltipButton"; -import {Key} from "../../../Keyboard"; +import { Key } from "../../../Keyboard"; import FocusLock from "react-focus-lock"; import MemberAvatar from "../avatars/MemberAvatar"; -import {ContextMenuTooltipButton} from "../../../accessibility/context_menu/ContextMenuTooltipButton"; +import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton"; import MessageContextMenu from "../context_menus/MessageContextMenu"; -import {aboveLeftOf, ContextMenu} from '../../structures/ContextMenu'; +import { aboveLeftOf, ContextMenu } from '../../structures/ContextMenu'; import MessageTimestamp from "../messages/MessageTimestamp"; import SettingsStore from "../../../settings/SettingsStore"; -import {formatFullDate} from "../../../DateUtils"; +import { formatFullDate } from "../../../DateUtils"; import dis from '../../../dispatcher/dispatcher'; -import {replaceableComponent} from "../../../utils/replaceableComponent"; -import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks" -import {MatrixEvent} from "matrix-js-sdk/src/models/event"; -import {normalizeWheelEvent} from "../../../utils/Mouse"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks" +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { normalizeWheelEvent } from "../../../utils/Mouse"; // Max scale to keep gaps around the image const MAX_SCALE = 0.95; @@ -172,7 +172,7 @@ export default class ImageView extends React.Component { return; } if (newZoom >= this.state.maxZoom) { - this.setState({zoom: this.state.maxZoom}); + this.setState({ zoom: this.state.maxZoom }); return; } @@ -185,7 +185,7 @@ export default class ImageView extends React.Component { ev.stopPropagation(); ev.preventDefault(); - const {deltaY} = normalizeWheelEvent(ev); + const { deltaY } = normalizeWheelEvent(ev); this.zoom(-(deltaY * ZOOM_COEFFICIENT)); }; @@ -259,11 +259,11 @@ export default class ImageView extends React.Component { // Zoom in if we are completely zoomed out if (this.state.zoom === this.state.minZoom) { - this.setState({zoom: this.state.maxZoom}); + this.setState({ zoom: this.state.maxZoom }); return; } - this.setState({moving: true}); + this.setState({ moving: true }); this.previousX = this.state.translationX; this.previousY = this.state.translationY; this.initX = ev.pageX - this.state.translationX; @@ -297,7 +297,7 @@ export default class ImageView extends React.Component { this.initX = 0; this.initY = 0; } - this.setState({moving: false}); + this.setState({ moving: false }); }; private renderContextMenu() { From 255f9967109ae6024dbfbab0dd5581968691dbac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 7 Jun 2021 15:46:03 +0200 Subject: [PATCH 145/198] landscape -> imageIsNotFlipped MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index d0dcb1fa00..298951428f 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -124,11 +124,11 @@ export default class ImageView extends React.Component { const rotation = inputRotation || this.state.rotation; - const landscape = rotation % 180 === 0; + const imageIsNotFlipped = rotation % 180 === 0; // If the image is rotated take it into account - const width = landscape ? image.naturalWidth : image.naturalHeight; - const height = landscape ? image.naturalHeight : image.naturalWidth; + const width = imageIsNotFlipped ? image.naturalWidth : image.naturalHeight; + const height = imageIsNotFlipped ? image.naturalHeight : image.naturalWidth; const zoomX = imageWrapper.clientWidth / width; const zoomY = imageWrapper.clientHeight / height; From 20a3dd67d6b3029c75adac9da58f162aed5aa1bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 7 Jun 2021 16:12:06 +0200 Subject: [PATCH 146/198] Some more styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/ImageView.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 298951428f..630280120c 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -368,7 +368,7 @@ export default class ImageView extends React.Component { const senderName = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); const sender = (
    - {senderName} + { senderName }
    ); const messageTimestamp = ( @@ -395,10 +395,10 @@ export default class ImageView extends React.Component { info = (
    - {avatar} + { avatar }
    - {sender} - {messageTimestamp} + { sender } + { messageTimestamp }
    ); @@ -438,7 +438,7 @@ export default class ImageView extends React.Component { + onClick={this.onZoomInClick}> ); } @@ -454,7 +454,7 @@ export default class ImageView extends React.Component { ref={this.focusLock} >
    - {info} + { info }
    { title={_t("Rotate Right")} onClick={this.onRotateClockwiseClick}> - {zoomOutButton} - {zoomInButton} + { zoomOutButton } + { zoomInButton } - {contextMenuButton} + { contextMenuButton } - {this.renderContextMenu()} + { this.renderContextMenu() }
    Date: Mon, 7 Jun 2021 16:21:53 +0100 Subject: [PATCH 147/198] Fix notif panel timestamp padding This restores some space around the timestamp in the notif panel. We were previously relying somewhat randomly on the presence on empty flair elements to create space. Fixes https://github.com/vector-im/element-web/issues/17580 --- res/css/structures/_NotificationPanel.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss index 1258ace069..e54feca175 100644 --- a/res/css/structures/_NotificationPanel.scss +++ b/res/css/structures/_NotificationPanel.scss @@ -82,7 +82,6 @@ limitations under the License. color: $primary-fg-color; font-size: $font-12px; display: inline; - padding-left: 0px; } .mx_NotificationPanel .mx_EventTile_senderDetails { @@ -103,6 +102,7 @@ limitations under the License. visibility: visible; position: initial; display: inline; + padding-left: 5px; } .mx_NotificationPanel .mx_EventTile_line { From 296186b844d286062e0fe2e401320d267bb2717b Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Mon, 7 Jun 2021 16:21:53 +0100 Subject: [PATCH 148/198] Fix notif panel timestamp padding This restores some space around the timestamp in the notif panel. We were previously relying somewhat randomly on the presence on empty flair elements to create space. Fixes https://github.com/vector-im/element-web/issues/17580 --- res/css/structures/_NotificationPanel.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/structures/_NotificationPanel.scss b/res/css/structures/_NotificationPanel.scss index 1258ace069..e54feca175 100644 --- a/res/css/structures/_NotificationPanel.scss +++ b/res/css/structures/_NotificationPanel.scss @@ -82,7 +82,6 @@ limitations under the License. color: $primary-fg-color; font-size: $font-12px; display: inline; - padding-left: 0px; } .mx_NotificationPanel .mx_EventTile_senderDetails { @@ -103,6 +102,7 @@ limitations under the License. visibility: visible; position: initial; display: inline; + padding-left: 5px; } .mx_NotificationPanel .mx_EventTile_line { From dd089bf9696a7c5ad0b765bc41e5bf1d9a351466 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 7 Jun 2021 17:03:05 +0100 Subject: [PATCH 149/198] Upgrade matrix-js-sdk to 11.2.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4f6a023383..5ee56ec8cf 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "11.2.0-rc.1", + "matrix-js-sdk": "11.2.0", "matrix-widget-api": "^0.1.0-beta.14", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 1b07d4502b..5e324b7ad2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5673,10 +5673,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@11.2.0-rc.1: - version "11.2.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-11.2.0-rc.1.tgz#339259d892ce08195864c1130780dd0b3d996af3" - integrity sha512-ACFONqdRqiPIj7aWf7G4f/2d0S/hfouIH6tdkFGDbizbWJCmqXRJ8EN2gmu0F3A6GGDo0+k9cqaeMf3Y1KIM8w== +matrix-js-sdk@11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-11.2.0.tgz#b49f1ebbb671f5a8ec76eec3b21eeda97f10fb31" + integrity sha512-YsdAQNPsvoWL3fKI2fb6evO81UKfSt+hYDkBOIylzb3AA/RtUsjtSNXcT4f6BsQIVTh6xAhEjPVoMfKORchf1Q== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From 187a3671110f181c8129b1f1dd47e110621cd8a0 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 7 Jun 2021 17:11:14 +0100 Subject: [PATCH 150/198] Add prop to alwaysShowTimestamps on TimelinePanel --- src/components/structures/NotificationPanel.tsx | 1 + src/components/structures/TimelinePanel.js | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/structures/NotificationPanel.tsx b/src/components/structures/NotificationPanel.tsx index b4f13f6b37..6c22835447 100644 --- a/src/components/structures/NotificationPanel.tsx +++ b/src/components/structures/NotificationPanel.tsx @@ -50,6 +50,7 @@ export default class NotificationPanel extends React.PureComponent { showUrlPreview={false} tileShape="notif" empty={emptyState} + alwaysShowTimestamps={true} /> ); } else { diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js index 6300c7532e..a8b0dc88dd 100644 --- a/src/components/structures/TimelinePanel.js +++ b/src/components/structures/TimelinePanel.js @@ -120,6 +120,9 @@ class TimelinePanel extends React.Component { // which layout to use layout: LayoutPropType, + + // whether to always show timestamps for an event + alwaysShowTimestamps: PropTypes.bool, } // a map from room id to read marker event timestamp @@ -1440,7 +1443,7 @@ class TimelinePanel extends React.Component { onFillRequest={this.onMessageListFillRequest} onUnfillRequest={this.onMessageListUnfillRequest} isTwelveHour={this.state.isTwelveHour} - alwaysShowTimestamps={this.state.alwaysShowTimestamps} + alwaysShowTimestamps={this.props.alwaysShowTimestamps || this.state.alwaysShowTimestamps} className={this.props.className} tileShape={this.props.tileShape} resizeNotifier={this.props.resizeNotifier} From 2983b3ace21f55cf36efddecea9851918d336fd9 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 7 Jun 2021 17:41:18 +0100 Subject: [PATCH 151/198] Prepare changelog for v3.23.0 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3abd1f32b..94c9530941 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +Changes in [3.23.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.23.0) (2021-06-07) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.23.0-rc.1...v3.23.0) + + * Upgrade to JS SDK 11.2.0 + * [Release] Fix notif panel timestamp padding + [\#6158](https://github.com/matrix-org/matrix-react-sdk/pull/6158) + Changes in [3.23.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.23.0-rc.1) (2021-06-01) =============================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.22.0...v3.23.0-rc.1) From a3dac02e4c1b4a5d632c34ad35e634cd23e59813 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 7 Jun 2021 17:41:18 +0100 Subject: [PATCH 152/198] v3.23.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ee56ec8cf..17067df933 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.23.0-rc.1", + "version": "3.23.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From bcd0ba8d456f93c45ab57f8b769ece0815227571 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 7 Jun 2021 17:42:46 +0100 Subject: [PATCH 153/198] Resetting package fields for development --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 17067df933..dee4013006 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", @@ -200,6 +200,5 @@ "coverageReporters": [ "text" ] - }, - "typings": "./lib/index.d.ts" + } } From 8ccdf9a3e8a038def7c6be8e920839f26dae2f83 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 7 Jun 2021 17:43:27 +0100 Subject: [PATCH 154/198] 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 dee4013006..2bff2a7b13 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "11.2.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^0.1.0-beta.14", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 5e324b7ad2..97179550c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5673,10 +5673,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@11.2.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "11.2.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-11.2.0.tgz#b49f1ebbb671f5a8ec76eec3b21eeda97f10fb31" - integrity sha512-YsdAQNPsvoWL3fKI2fb6evO81UKfSt+hYDkBOIylzb3AA/RtUsjtSNXcT4f6BsQIVTh6xAhEjPVoMfKORchf1Q== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/35ecbed29d16982deff27a8c37b05167738225a2" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From 4185185cdc9e97231ca8f25ca4c9a5041429225c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 7 Jun 2021 19:42:48 +0200 Subject: [PATCH 155/198] Reduce number of DOM elements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/SenderProfile.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx index a51f50525d..df75de34ba 100644 --- a/src/components/views/messages/SenderProfile.tsx +++ b/src/components/views/messages/SenderProfile.tsx @@ -146,12 +146,10 @@ export default class SenderProfile extends React.Component { } return ( -
    -
    - { displayNameElement } - { mxidElement } - { flair } -
    +
    + { displayNameElement } + { mxidElement } + { flair }
    ); } From b39c615abc6f2787decfcd5ed4c38ae6ea01d072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 7 Jun 2021 19:44:58 +0200 Subject: [PATCH 156/198] Reorder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/messages/SenderProfile.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx index df75de34ba..872dc132b9 100644 --- a/src/components/views/messages/SenderProfile.tsx +++ b/src/components/views/messages/SenderProfile.tsx @@ -118,18 +118,6 @@ export default class SenderProfile extends React.Component { return null; // emote message must include the name so don't duplicate it } - let flair; - if (this.props.enableFlair) { - const displayedGroups = this._getDisplayedGroups( - this.state.userGroups, this.state.relatedGroups, - ); - - flair = ; - } - const displayNameElement = ( { displayName } @@ -145,6 +133,18 @@ export default class SenderProfile extends React.Component { ); } + let flair; + if (this.props.enableFlair) { + const displayedGroups = this._getDisplayedGroups( + this.state.userGroups, this.state.relatedGroups, + ); + + flair = ; + } + return (
    { displayNameElement } From a5bf17ff659eeb8f3d915163217d9c8574d53f54 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Mon, 7 Jun 2021 16:54:36 -0500 Subject: [PATCH 157/198] Revert incorrect change to forShareableRoom Signed-off-by: Aaron Raimist --- src/utils/permalinks/Permalinks.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/permalinks/Permalinks.ts b/src/utils/permalinks/Permalinks.ts index 2f268aaa0c..c2f95defd6 100644 --- a/src/utils/permalinks/Permalinks.ts +++ b/src/utils/permalinks/Permalinks.ts @@ -293,12 +293,12 @@ export function makeRoomPermalink(roomId: string): string { // If the roomId isn't actually a room ID, don't try to list the servers. // Aliases are already routable, and don't need extra information. - if (roomId[0] !== '!') return getPermalinkConstructor().forShareableRoom(roomId, []); + if (roomId[0] !== '!') return getPermalinkConstructor().forRoom(roomId, []); const client = MatrixClientPeg.get(); const room = client.getRoom(roomId); if (!room) { - return getPermalinkConstructor().forShareableRoom(roomId, []); + return getPermalinkConstructor().forRoom(roomId, []); } const permalinkCreator = new RoomPermalinkCreator(room); permalinkCreator.load(); From 773af6c7be6244775b3c2da79e38921381b401a9 Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Mon, 7 Jun 2021 16:54:50 -0500 Subject: [PATCH 158/198] Fix test Signed-off-by: Aaron Raimist --- test/utils/permalinks/Permalinks-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils/permalinks/Permalinks-test.js b/test/utils/permalinks/Permalinks-test.js index 0bd4466d97..3c4982b465 100644 --- a/test/utils/permalinks/Permalinks-test.js +++ b/test/utils/permalinks/Permalinks-test.js @@ -34,7 +34,7 @@ function mockRoom(roomId, members, serverACL) { return { roomId, - getCanonicalAlias: () => roomId, + getCanonicalAlias: () => null, getJoinedMembers: () => members, getMember: (userId) => members.find(m => m.userId === userId), currentState: { From 946416cf8756ac94d0229f038e561f9215ca695f Mon Sep 17 00:00:00 2001 From: Aaron Raimist Date: Mon, 7 Jun 2021 17:09:43 -0500 Subject: [PATCH 159/198] Make serverCandidates optional Signed-off-by: Aaron Raimist --- src/utils/permalinks/PermalinkConstructor.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/utils/permalinks/PermalinkConstructor.ts b/src/utils/permalinks/PermalinkConstructor.ts index 25855eb024..986d20d4d1 100644 --- a/src/utils/permalinks/PermalinkConstructor.ts +++ b/src/utils/permalinks/PermalinkConstructor.ts @@ -19,11 +19,11 @@ limitations under the License. * TODO: Convert this to a real TypeScript interface */ export default class PermalinkConstructor { - forEvent(roomId: string, eventId: string, serverCandidates: string[]): string { + forEvent(roomId: string, eventId: string, serverCandidates: string[] = []): string { throw new Error("Not implemented"); } - forRoom(roomIdOrAlias: string, serverCandidates: string[]): string { + forRoom(roomIdOrAlias: string, serverCandidates: string[] = []): string { throw new Error("Not implemented"); } @@ -73,12 +73,12 @@ export class PermalinkParts { return new PermalinkParts(null, null, null, groupId, null); } - static forRoom(roomIdOrAlias: string, viaServers: string[]): PermalinkParts { - return new PermalinkParts(roomIdOrAlias, null, null, null, viaServers || []); + static forRoom(roomIdOrAlias: string, viaServers: string[] = []): PermalinkParts { + return new PermalinkParts(roomIdOrAlias, null, null, null, viaServers); } - static forEvent(roomId: string, eventId: string, viaServers: string[]): PermalinkParts { - return new PermalinkParts(roomId, eventId, null, null, viaServers || []); + static forEvent(roomId: string, eventId: string, viaServers: string[] = []): PermalinkParts { + return new PermalinkParts(roomId, eventId, null, null, viaServers); } get primaryEntityId(): string { From a8dab04d5eed882f90244f0a5b1ec54cbfaaea58 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 7 Jun 2021 19:10:45 -0400 Subject: [PATCH 160/198] Remove mystery dot from forward dialog preview It was a bullet point, since EventTiles now get created as li by default :P Signed-off-by: Robin Townsend --- src/components/views/dialogs/ForwardDialog.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index e97958973b..b89485943a 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -211,6 +211,7 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr layout={previewLayout} enableFlair={flairEnabled} permalinkCreator={permalinkCreator} + as="div" />

    From b00ad63a35fa2aa18c0ca04b803b34e252fa270c Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 7 Jun 2021 19:16:00 -0400 Subject: [PATCH 161/198] Fix whitespace nitpick Signed-off-by: Robin Townsend --- src/components/views/dialogs/ForwardDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx index b89485943a..1c90dca432 100644 --- a/src/components/views/dialogs/ForwardDialog.tsx +++ b/src/components/views/dialogs/ForwardDialog.tsx @@ -201,7 +201,7 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr onFinished={onFinished} fixedWidth={false} > -

    {_t("Message preview")}

    +

    { _t("Message preview") }

    Date: Mon, 7 Jun 2021 19:19:14 -0400 Subject: [PATCH 162/198] Hide download links from forward dialog preview since trying to interact with them is pointless. Signed-off-by: Robin Townsend --- res/css/views/dialogs/_ForwardDialog.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/res/css/views/dialogs/_ForwardDialog.scss b/res/css/views/dialogs/_ForwardDialog.scss index 7422d39626..1976a43ab9 100644 --- a/res/css/views/dialogs/_ForwardDialog.scss +++ b/res/css/views/dialogs/_ForwardDialog.scss @@ -49,6 +49,11 @@ limitations under the License. .mx_EventTile_e2eIcon_unencrypted { display: none; } + + // We also hide download links to not encourage users to try interacting + .mx_MFileBody_download { + display: none; + } } > hr { From 26a030abcd76daf27c9f360000e20d54fc65cb19 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 8 Jun 2021 12:40:38 +0100 Subject: [PATCH 163/198] Better handling for widgets that fail to load --- src/components/views/elements/AppTile.js | 40 +++++++++++++++++------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index b898ad2ebc..91ffbf2c27 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -47,9 +47,14 @@ export default class AppTile extends React.Component { // The key used for PersistedElement this._persistKey = getPersistKey(this.props.app.id); - this._sgWidget = new StopGapWidget(this.props); - this._sgWidget.on("preparing", this._onWidgetPrepared); - this._sgWidget.on("ready", this._onWidgetReady); + try { + this._sgWidget = new StopGapWidget(this.props); + this._sgWidget.on("preparing", this._onWidgetPrepared); + this._sgWidget.on("ready", this._onWidgetReady); + } catch (e) { + console.log("Failed to construct widget", e); + this._sgWidget = null; + } this.iframe = null; // ref to the iframe (callback style) this.state = this._getNewState(props); @@ -97,7 +102,7 @@ export default class AppTile extends React.Component { // Force the widget to be non-persistent (able to be deleted/forgotten) ActiveWidgetStore.destroyPersistentWidget(this.props.app.id); PersistedElement.destroyElement(this._persistKey); - this._sgWidget.stop(); + if (this._sgWidget) this._sgWidget.stop(); } this.setState({ hasPermissionToLoad }); @@ -117,7 +122,7 @@ export default class AppTile extends React.Component { componentDidMount() { // Only fetch IM token on mount if we're showing and have permission to load - if (this.state.hasPermissionToLoad) { + if (this._sgWidget && this.state.hasPermissionToLoad) { this._startWidget(); } @@ -146,10 +151,15 @@ export default class AppTile extends React.Component { if (this._sgWidget) { this._sgWidget.stop(); } - this._sgWidget = new StopGapWidget(newProps); - this._sgWidget.on("preparing", this._onWidgetPrepared); - this._sgWidget.on("ready", this._onWidgetReady); - this._startWidget(); + try { + this._sgWidget = new StopGapWidget(newProps); + this._sgWidget.on("preparing", this._onWidgetPrepared); + this._sgWidget.on("ready", this._onWidgetReady); + this._startWidget(); + } catch (e) { + console.log("Failed to construct widget", e); + this._sgWidget = null; + } } _startWidget() { @@ -161,7 +171,7 @@ export default class AppTile extends React.Component { _iframeRefChange = (ref) => { this.iframe = ref; if (ref) { - this._sgWidget.start(ref); + if (this._sgWidget) this._sgWidget.start(ref); } else { this._resetWidget(this.props); } @@ -209,7 +219,7 @@ export default class AppTile extends React.Component { // Delete the widget from the persisted store for good measure. PersistedElement.destroyElement(this._persistKey); - this._sgWidget.stop({forceDestroy: true}); + if (this._sgWidget) this._sgWidget.stop({forceDestroy: true}); } _onWidgetPrepared = () => { @@ -340,7 +350,13 @@ export default class AppTile extends React.Component {
    ); - if (!this.state.hasPermissionToLoad) { + if (this._sgWidget === null) { + appTileBody = ( +
    + +
    + ); + } else if (!this.state.hasPermissionToLoad) { // only possible for room widgets, can assert this.props.room here const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId); appTileBody = ( From 5c62728d13ac23721c7747c10929e38fa4f2458b Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 8 Jun 2021 12:52:32 +0100 Subject: [PATCH 164/198] Migrate end to end tests pipeline to GitHub Actions --- .github/workflows/end-to-end-tests.yml | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/end-to-end-tests.yml diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/end-to-end-tests.yml new file mode 100644 index 0000000000..271ea06529 --- /dev/null +++ b/.github/workflows/end-to-end-tests.yml @@ -0,0 +1,32 @@ +name: "end-to-end_tests" +on: [push] +jobs: + "end-to-end_tests": + runs-on: ubuntu-latest + defaults: + run: + shell: bash + working-directory: ${{ github.workspace }} + container: + image: vectorim/element-web-ci-e2etests-env:latest + env: + CI_PACKAGE: true + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_BASE_REF: ${{ github.base_ref }} + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: End-to-End tests + run: ./scripts/ci/end-to-end-tests.sh + - name: Archive logs + uses: actions/upload-artifact@v2 + with: + path: | + test/end-to-end-tests/logs/**/* + test/end-to-end-tests/synapse/installations/consent/homeserver.log + retention-days: 14 + - name: Archive performance benchmark + uses: actions/upload-artifact@v2 + with: + name: performance-entries.json + path: test/end-to-end-tests/performance-entries.json From 90a58380fd3732f250f63de65a4d542b0a0a6837 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 8 Jun 2021 16:15:20 +0100 Subject: [PATCH 165/198] Make it translateable And also the other one that I copied from --- src/components/views/elements/AppTile.js | 4 ++-- src/i18n/strings/en_EN.json | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index 91ffbf2c27..4028909167 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -353,7 +353,7 @@ export default class AppTile extends React.Component { if (this._sgWidget === null) { appTileBody = (
    - +
    ); } else if (!this.state.hasPermissionToLoad) { @@ -380,7 +380,7 @@ export default class AppTile extends React.Component { if (this.isMixedContent()) { appTileBody = (
    - +
    ); } else { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9e85ea28c8..64613d0d88 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1926,6 +1926,8 @@ "Widgets do not use message encryption.": "Widgets do not use message encryption.", "Widget added by": "Widget added by", "This widget may use cookies.": "This widget may use cookies.", + "Error loading Widget": "Error loading Widget", + "Error - Mixed content": "Error - Mixed content", "Popout widget": "Popout widget", "Use the Desktop app to see all encrypted files": "Use the Desktop app to see all encrypted files", "Use the Desktop app to search encrypted messages": "Use the Desktop app to search encrypted messages", From f84ee2276995a5abb42225fa443e8d777dddee65 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Jun 2021 16:36:53 +0100 Subject: [PATCH 166/198] Add comment --- src/components/views/right_panel/PinnedMessagesCard.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index 3a4dc9cc92..a72731522f 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -160,6 +160,7 @@ const PinnedMessagesCard = ({ room, onClose }: IProps) => { } else { content =
    + { /* XXX: We reuse the classes for simplicity, but deliberately not the components for non-interactivity. */ }
    From 90982f9b3fd1345c5af5e66a46986dd11e12dd2a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 8 Jun 2021 17:31:08 +0100 Subject: [PATCH 167/198] Fix upgrade to element home button in top left menu --- src/components/structures/HostSignupAction.tsx | 5 ++++- src/components/structures/UserMenu.tsx | 4 +--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/structures/HostSignupAction.tsx b/src/components/structures/HostSignupAction.tsx index 769775d549..46e158d6c8 100644 --- a/src/components/structures/HostSignupAction.tsx +++ b/src/components/structures/HostSignupAction.tsx @@ -24,13 +24,16 @@ import { HostSignupStore } from "../../stores/HostSignupStore"; import SdkConfig from "../../SdkConfig"; import {replaceableComponent} from "../../utils/replaceableComponent"; -interface IProps {} +interface IProps { + onClick?(): void; +} interface IState {} @replaceableComponent("structures.HostSignupAction") export default class HostSignupAction extends React.PureComponent { private openDialog = async () => { + this.props.onClick?.(); await HostSignupStore.instance.setHostSignupActive(true); } diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index fb4829f879..6a449cf1a2 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -366,9 +366,7 @@ export default class UserMenu extends React.Component { const mxDomain = MatrixClientPeg.get().getDomain(); const validDomains = hostSignupDomains.filter(d => (d === mxDomain || mxDomain.endsWith(`.${d}`))); if (!hostSignupConfig.domains || validDomains.length > 0) { - topSection =
    - -
    ; + topSection = ; } } } From 7155c033f23048acab1c864e758bfed2e3344b93 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Jun 2021 19:20:10 +0000 Subject: [PATCH 168/198] Bump trim-newlines from 3.0.0 to 3.0.1 Bumps [trim-newlines](https://github.com/sindresorhus/trim-newlines) from 3.0.0 to 3.0.1. - [Release notes](https://github.com/sindresorhus/trim-newlines/releases) - [Commits](https://github.com/sindresorhus/trim-newlines/commits) --- updated-dependencies: - dependency-name: trim-newlines dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 97179550c9..21243f4f58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7969,9 +7969,9 @@ tree-kill@^1.2.2: integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== trim-newlines@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30" - integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA== + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" + integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== trough@^1.0.0: version "1.0.5" From dbb8aa47dcbaf0558607e705727c9c8e86bc12a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Jun 2021 20:08:39 +0000 Subject: [PATCH 169/198] Bump css-what from 5.0.0 to 5.0.1 Bumps [css-what](https://github.com/fb55/css-what) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/fb55/css-what/releases) - [Commits](https://github.com/fb55/css-what/compare/v5.0.0...v5.0.1) --- updated-dependencies: - dependency-name: css-what dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 21243f4f58..7c12ac51d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2721,9 +2721,9 @@ css-select@^4.1.2: nth-check "^2.0.0" css-what@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.0.tgz#f0bf4f8bac07582722346ab243f6a35b512cfc47" - integrity sha512-qxyKHQvgKwzwDWC/rGbT821eJalfupxYW2qbSJSAtdSTimsr/MlaGONoNLllaUPZWf8QnbcKM/kPVYUQuEKAFA== + version "5.0.1" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.1.tgz#3efa820131f4669a8ac2408f9c32e7c7de9f4cad" + integrity sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg== cssesc@^3.0.0: version "3.0.0" From a28f5754c7cd86d7b6d795c8fcfc6a7468c8e1e6 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 8 Jun 2021 21:58:48 -0400 Subject: [PATCH 170/198] Convert TextForEvent to TypeScript Signed-off-by: Robin Townsend --- src/{TextForEvent.js => TextForEvent.ts} | 54 +++++++++++++----------- 1 file changed, 29 insertions(+), 25 deletions(-) rename src/{TextForEvent.js => TextForEvent.ts} (94%) diff --git a/src/TextForEvent.js b/src/TextForEvent.ts similarity index 94% rename from src/TextForEvent.js rename to src/TextForEvent.ts index 22ce0dd9cf..649c53664e 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.ts @@ -25,7 +25,7 @@ import {WIDGET_LAYOUT_EVENT_TYPE} from "./stores/widgets/WidgetLayoutStore"; // any text to display at all. For this reason they return deferred values // to avoid the expense of looking up translations when they're not needed. -function textForMemberEvent(ev) { +function textForMemberEvent(ev): () => string | null { // XXX: SYJS-16 "sender is sometimes null for join messages" const senderName = ev.sender ? ev.sender.name : ev.getSender(); const targetName = ev.target ? ev.target.name : ev.getStateKey(); @@ -107,7 +107,7 @@ function textForMemberEvent(ev) { } } -function textForTopicEvent(ev) { +function textForTopicEvent(ev): () => string | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); return () => _t('%(senderDisplayName)s changed the topic to "%(topic)s".', { senderDisplayName, @@ -115,7 +115,7 @@ function textForTopicEvent(ev) { }); } -function textForRoomNameEvent(ev) { +function textForRoomNameEvent(ev): () => string | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); if (!ev.getContent().name || ev.getContent().name.trim().length === 0) { @@ -134,12 +134,12 @@ function textForRoomNameEvent(ev) { }); } -function textForTombstoneEvent(ev) { +function textForTombstoneEvent(ev): () => string | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); return () => _t('%(senderDisplayName)s upgraded this room.', {senderDisplayName}); } -function textForJoinRulesEvent(ev) { +function textForJoinRulesEvent(ev): () => string | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); switch (ev.getContent().join_rule) { case "public": @@ -159,7 +159,7 @@ function textForJoinRulesEvent(ev) { } } -function textForGuestAccessEvent(ev) { +function textForGuestAccessEvent(ev): () => string | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); switch (ev.getContent().guest_access) { case "can_join": @@ -175,7 +175,7 @@ function textForGuestAccessEvent(ev) { } } -function textForRelatedGroupsEvent(ev) { +function textForRelatedGroupsEvent(ev): () => string | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const groups = ev.getContent().groups || []; const prevGroups = ev.getPrevContent().groups || []; @@ -205,7 +205,7 @@ function textForRelatedGroupsEvent(ev) { } } -function textForServerACLEvent(ev) { +function textForServerACLEvent(ev): () => string | null { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const prevContent = ev.getPrevContent(); const current = ev.getContent(); @@ -235,7 +235,7 @@ function textForServerACLEvent(ev) { return getText; } -function textForMessageEvent(ev) { +function textForMessageEvent(ev): () => string | null { return () => { const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); let message = senderDisplayName + ': ' + ev.getContent().body; @@ -248,7 +248,7 @@ function textForMessageEvent(ev) { }; } -function textForCanonicalAliasEvent(ev) { +function textForCanonicalAliasEvent(ev): () => string | null { const senderName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const oldAlias = ev.getPrevContent().alias; const oldAltAliases = ev.getPrevContent().alt_aliases || []; @@ -299,7 +299,7 @@ function textForCanonicalAliasEvent(ev) { }); } -function textForCallAnswerEvent(event) { +function textForCallAnswerEvent(event): () => string | null { return () => { const senderName = event.sender ? event.sender.name : _t('Someone'); const supported = MatrixClientPeg.get().supportsVoip() ? '' : _t('(not supported by this browser)'); @@ -307,7 +307,7 @@ function textForCallAnswerEvent(event) { }; } -function textForCallHangupEvent(event) { +function textForCallHangupEvent(event): () => string | null { const getSenderName = () => event.sender ? event.sender.name : _t('Someone'); const eventContent = event.getContent(); let getReason = () => ""; @@ -344,14 +344,14 @@ function textForCallHangupEvent(event) { return () => _t('%(senderName)s ended the call.', {senderName: getSenderName()}) + ' ' + getReason(); } -function textForCallRejectEvent(event) { +function textForCallRejectEvent(event): () => string | null { return () => { const senderName = event.sender ? event.sender.name : _t('Someone'); return _t('%(senderName)s declined the call.', {senderName}); }; } -function textForCallInviteEvent(event) { +function textForCallInviteEvent(event): () => string | null { const getSenderName = () => event.sender ? event.sender.name : _t('Someone'); // FIXME: Find a better way to determine this from the event? let isVoice = true; @@ -383,7 +383,7 @@ function textForCallInviteEvent(event) { } } -function textForThreePidInviteEvent(event) { +function textForThreePidInviteEvent(event): () => string | null { const senderName = event.sender ? event.sender.name : event.getSender(); if (!isValid3pidInvite(event)) { @@ -399,7 +399,7 @@ function textForThreePidInviteEvent(event) { }); } -function textForHistoryVisibilityEvent(event) { +function textForHistoryVisibilityEvent(event): () => string | null { const senderName = event.sender ? event.sender.name : event.getSender(); switch (event.getContent().history_visibility) { case 'invited': @@ -421,7 +421,7 @@ function textForHistoryVisibilityEvent(event) { } // Currently will only display a change if a user's power level is changed -function textForPowerEvent(event) { +function textForPowerEvent(event): () => string | null { const senderName = event.sender ? event.sender.name : event.getSender(); if (!event.getPrevContent() || !event.getPrevContent().users || !event.getContent() || !event.getContent().users) { @@ -466,12 +466,12 @@ function textForPowerEvent(event) { }); } -function textForPinnedEvent(event) { +function textForPinnedEvent(event): () => string | null { const senderName = event.sender ? event.sender.name : event.getSender(); return () => _t("%(senderName)s changed the pinned messages for the room.", {senderName}); } -function textForWidgetEvent(event) { +function textForWidgetEvent(event): () => string | null { const senderName = event.getSender(); const {name: prevName, type: prevType, url: prevUrl} = event.getPrevContent(); const {name, type, url} = event.getContent() || {}; @@ -501,12 +501,12 @@ function textForWidgetEvent(event) { } } -function textForWidgetLayoutEvent(event) { +function textForWidgetLayoutEvent(event): () => string | null { const senderName = event.sender?.name || event.getSender(); return () => _t("%(senderName)s has updated the widget layout", {senderName}); } -function textForMjolnirEvent(event) { +function textForMjolnirEvent(event): () => string | null { const senderName = event.getSender(); const {entity: prevEntity} = event.getPrevContent(); const {entity, recommendation, reason} = event.getContent(); @@ -593,7 +593,11 @@ function textForMjolnirEvent(event) { "for %(reason)s", {senderName, oldGlob: prevEntity, newGlob: entity, reason}); } -const handlers = { +interface IHandlers { + [type: string]: (ev: any) => (() => string | null); +} + +const handlers: IHandlers = { 'm.room.message': textForMessageEvent, 'm.call.invite': textForCallInviteEvent, 'm.call.answer': textForCallAnswerEvent, @@ -601,7 +605,7 @@ const handlers = { 'm.call.reject': textForCallRejectEvent, }; -const stateHandlers = { +const stateHandlers: IHandlers = { 'm.room.canonical_alias': textForCanonicalAliasEvent, 'm.room.name': textForRoomNameEvent, 'm.room.topic': textForTopicEvent, @@ -626,12 +630,12 @@ for (const evType of ALL_RULE_TYPES) { stateHandlers[evType] = textForMjolnirEvent; } -export function hasText(ev) { +export function hasText(ev): boolean { const handler = (ev.isState() ? stateHandlers : handlers)[ev.getType()]; return Boolean(handler?.(ev)); } -export function textForEvent(ev) { +export function textForEvent(ev): string { const handler = (ev.isState() ? stateHandlers : handlers)[ev.getType()]; return handler?.(ev)?.() || ''; } From fccd71f82e9101ebbcab23d3ec04e6521958359f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 9 Jun 2021 06:23:51 +0200 Subject: [PATCH 171/198] Update styling based on review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/messages/_SenderProfile.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/res/css/views/messages/_SenderProfile.scss b/res/css/views/messages/_SenderProfile.scss index 378d9e6f1a..7aaafa6ac3 100644 --- a/res/css/views/messages/_SenderProfile.scss +++ b/res/css/views/messages/_SenderProfile.scss @@ -20,8 +20,7 @@ limitations under the License. .mx_SenderProfile_mxid { font-weight: 600; - font-family: monospace; - font-size: 1rem; + font-size: 1.1rem; color: $event-timestamp-color; margin-left: 5px; } From c9b1d1aef06ece68109c46871bf633f823d41c7c Mon Sep 17 00:00:00 2001 From: Germain Date: Wed, 9 Jun 2021 10:38:28 +0100 Subject: [PATCH 172/198] Only run e2e tests on develop and PR targetting develop Co-authored-by: J. Ryan Stinnett --- .github/workflows/end-to-end-tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/end-to-end-tests.yml index 271ea06529..abf7813bbc 100644 --- a/.github/workflows/end-to-end-tests.yml +++ b/.github/workflows/end-to-end-tests.yml @@ -1,5 +1,9 @@ name: "end-to-end_tests" -on: [push] +on: + push: + branches: [develop] + pull_request: + branches: [develop] jobs: "end-to-end_tests": runs-on: ubuntu-latest From 85d1bb65c6046330c2fd732621d9e94ced82a072 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 9 Jun 2021 11:40:20 +0100 Subject: [PATCH 173/198] Upgrade to React@17 --- package.json | 8 +- yarn.lock | 288 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 193 insertions(+), 103 deletions(-) diff --git a/package.json b/package.json index 13047b69cf..ab855b84be 100644 --- a/package.json +++ b/package.json @@ -90,9 +90,9 @@ "qrcode": "^1.4.4", "qs": "^6.9.6", "re-resizable": "^6.9.0", - "react": "^16.14.0", + "react": "^17.0.2", "react-beautiful-dnd": "^4.0.1", - "react-dom": "^16.14.0", + "react-dom": "^17.0.2", "react-focus-lock": "^2.5.0", "react-transition-group": "^4.4.1", "resize-observer-polyfill": "^1.5.1", @@ -147,7 +147,7 @@ "chokidar": "^3.5.1", "concurrently": "^5.3.0", "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.6", + "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1", "eslint": "7.18.0", "eslint-config-matrix-org": "^0.2.0", "eslint-plugin-babel": "^5.3.1", @@ -162,7 +162,7 @@ "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.2", "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", - "react-test-renderer": "^16.14.0", + "react-test-renderer": "^17.0.2", "rimraf": "^3.0.2", "stylelint": "^13.9.0", "stylelint-config-standard": "^20.0.0", diff --git a/yarn.lock b/yarn.lock index 0ff235a660..32480a156f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1752,6 +1752,31 @@ "@typescript-eslint/types" "4.14.0" eslint-visitor-keys "^2.0.0" +"@wojtekmaj/enzyme-adapter-react-17@^0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@wojtekmaj/enzyme-adapter-react-17/-/enzyme-adapter-react-17-0.6.1.tgz#28caa37118c183e5f13c4dfb68cc32cde828ecbc" + integrity sha512-xgPfzLVpN0epIHeZofahwr5qwpukEDNAbrufgeDWN6vZPtfblGCC+OZG5TlfK+A6ePVy8sBkD8S2X4tO17JKjg== + dependencies: + "@wojtekmaj/enzyme-adapter-utils" "^0.1.0" + enzyme-shallow-equal "^1.0.0" + has "^1.0.0" + object.assign "^4.1.0" + object.values "^1.1.0" + prop-types "^15.7.0" + react-is "^17.0.0" + react-test-renderer "^17.0.0" + +"@wojtekmaj/enzyme-adapter-utils@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@wojtekmaj/enzyme-adapter-utils/-/enzyme-adapter-utils-0.1.0.tgz#3a2a3db756111d53357e2f119a1612a969ab8c38" + integrity sha512-EYK/Vy0Y1ap0jH2UNQjOKtR/7HWkbEq8N+cwC5+yDf+Mwp5uu7j4Qg70RmWuzsA35DGGwgkop6m4pQsGwNOF2A== + dependencies: + function.prototype.name "^1.1.0" + has "^1.0.0" + object.assign "^4.1.0" + object.fromentries "^2.0.0" + prop-types "^15.7.0" + abab@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -1780,21 +1805,6 @@ acorn@^7.1.1, acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -airbnb-prop-types@^2.16.0: - version "2.16.0" - resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz#b96274cefa1abb14f623f804173ee97c13971dc2" - integrity sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg== - dependencies: - array.prototype.find "^2.1.1" - function.prototype.name "^1.1.2" - is-regex "^1.1.0" - object-is "^1.1.2" - object.assign "^4.1.0" - object.entries "^1.1.2" - prop-types "^15.7.2" - prop-types-exact "^1.2.0" - react-is "^16.13.1" - ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -1920,14 +1930,6 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.find@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.1.tgz#3baca26108ca7affb08db06bf0be6cb3115a969c" - integrity sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.4" - array.prototype.flat@^1.2.3: version "1.2.4" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123" @@ -3075,35 +3077,7 @@ entities@~2.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== -enzyme-adapter-react-16@^1.15.6: - version "1.15.6" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.6.tgz#fd677a658d62661ac5afd7f7f541f141f8085901" - integrity sha512-yFlVJCXh8T+mcQo8M6my9sPgeGzj85HSHi6Apgf1Cvq/7EL/J9+1JoJmJsRxZgyTvPMAqOEpRSu/Ii/ZpyOk0g== - dependencies: - enzyme-adapter-utils "^1.14.0" - enzyme-shallow-equal "^1.0.4" - has "^1.0.3" - object.assign "^4.1.2" - object.values "^1.1.2" - prop-types "^15.7.2" - react-is "^16.13.1" - react-test-renderer "^16.0.0-0" - semver "^5.7.0" - -enzyme-adapter-utils@^1.14.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.0.tgz#afbb0485e8033aa50c744efb5f5711e64fbf1ad0" - integrity sha512-F/z/7SeLt+reKFcb7597IThpDp0bmzcH1E9Oabqv+o01cID2/YInlqHbFl7HzWBl4h3OdZYedtwNDOmSKkk0bg== - dependencies: - airbnb-prop-types "^2.16.0" - function.prototype.name "^1.1.3" - has "^1.0.3" - object.assign "^4.1.2" - object.fromentries "^2.0.3" - prop-types "^15.7.2" - semver "^5.7.1" - -enzyme-shallow-equal@^1.0.1, enzyme-shallow-equal@^1.0.4: +enzyme-shallow-equal@^1.0.0, enzyme-shallow-equal@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz#b9256cb25a5f430f9bfe073a84808c1d74fced2e" integrity sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q== @@ -3146,7 +3120,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0-next.1, es-abstract@^1.17.4: +es-abstract@^1.17.0-next.1: version "1.17.7" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== @@ -3183,6 +3157,28 @@ es-abstract@^1.18.0-next.1: string.prototype.trimend "^1.0.3" string.prototype.trimstart "^1.0.3" +es-abstract@^1.18.0-next.2, es-abstract@^1.18.2: + version "1.18.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.3.tgz#25c4c3380a27aa203c44b2b685bba94da31b63e0" + integrity sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.2" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.3" + is-string "^1.0.6" + object-inspect "^1.10.3" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + es-get-iterator@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.1.tgz#b93ddd867af16d5118e00881396533c1c6647ad9" @@ -3965,7 +3961,17 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function.prototype.name@^1.1.2, function.prototype.name@^1.1.3: +function.prototype.name@^1.1.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.4.tgz#e4ea839b9d3672ae99d0efd9f38d9191c5eaac83" + integrity sha512-iqy1pIotY/RmhdFZygSSlW0wko2yxkSCKqsuv4pr8QESohpYyG/Z7B/XXvPRKTJS//960rgguE5mSRUsDdaJrQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + functions-have-names "^1.2.2" + +function.prototype.name@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.3.tgz#0bb034bb308e7682826f215eb6b2ae64918847fe" integrity sha512-H51qkbNSp8mtkJt+nyW1gyStBiKZxfRqySNUR99ylq6BPXHKI4SEvIlTKp4odLfjRKJV04DFWMU3G/YRlQOsag== @@ -3980,7 +3986,7 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -functions-have-names@^1.2.0, functions-have-names@^1.2.1: +functions-have-names@^1.2.0, functions-have-names@^1.2.1, functions-have-names@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.2.tgz#98d93991c39da9361f8e50b337c4f6e41f120e21" integrity sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA== @@ -4004,6 +4010,15 @@ get-intrinsic@^1.0.1, get-intrinsic@^1.0.2: has "^1.0.3" has-symbols "^1.0.1" +get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -4157,6 +4172,11 @@ hard-rejection@^2.1.0: resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -4172,6 +4192,11 @@ has-symbols@^1.0.1: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== +has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -4203,7 +4228,7 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.1, has@^1.0.3: +has@^1.0.0, has@^1.0.1, has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== @@ -4534,6 +4559,11 @@ is-callable@^1.0.4, is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.2: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== +is-callable@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== + is-ci@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -4736,13 +4766,21 @@ is-potential-custom-element-name@^1.0.0: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397" integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c= -is-regex@^1.0.3, is-regex@^1.0.4, is-regex@^1.0.5, is-regex@^1.1.0, is-regex@^1.1.1: +is-regex@^1.0.3, is-regex@^1.0.4, is-regex@^1.0.5, is-regex@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== dependencies: has-symbols "^1.0.1" +is-regex@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" + integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ== + dependencies: + call-bind "^1.0.2" + has-symbols "^1.0.2" + is-regexp@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d" @@ -4768,6 +4806,11 @@ is-string@^1.0.4, is-string@^1.0.5: resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== +is-string@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f" + integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w== + is-subset@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" @@ -6081,6 +6124,11 @@ object-inspect@^1.1.0, object-inspect@^1.7.0, object-inspect@^1.8.0, object-insp resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== +object-inspect@^1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" + integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw== + object-is@^1.0.2, object-is@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.4.tgz#63d6c83c00a43f4cbc9434eb9757c8a5b8565068" @@ -6121,7 +6169,17 @@ object.entries@^1.1.0, object.entries@^1.1.1, object.entries@^1.1.2: es-abstract "^1.18.0-next.1" has "^1.0.3" -object.fromentries@^2.0.2, object.fromentries@^2.0.3: +object.fromentries@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" + integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + has "^1.0.3" + +object.fromentries@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.3.tgz#13cefcffa702dc67750314a3305e8cb3fad1d072" integrity sha512-IDUSMXs6LOSJBWE++L0lzIbSqHl9KDCfff2x/JSEIDtEUavUnyMYC2ZGay/04Zq4UT8lvd4xNhU4/YHKibAOlw== @@ -6138,7 +6196,16 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -object.values@^1.1.1, object.values@^1.1.2: +object.values@^1.1.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.4.tgz#0d273762833e816b693a637d30073e7051535b30" + integrity sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.2" + +object.values@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.2.tgz#7a2015e06fcb0f546bd652486ce8583a4731c731" integrity sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag== @@ -6588,16 +6655,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types-exact@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/prop-types-exact/-/prop-types-exact-1.2.0.tgz#825d6be46094663848237e3925a98c6e944e9869" - integrity sha512-K+Tk3Kd9V0odiXFP9fwDHUYRyvK3Nun3GVyPapSIs5OBkITAm15W0CPFD/YKTkMUAbc0b9CUwRQp2ybiBIq+eA== - dependencies: - has "^1.0.3" - object.assign "^4.1.0" - reflect.ownkeys "^0.2.0" - -prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.0, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -6729,15 +6787,14 @@ react-clientside-effect@^1.2.2: dependencies: "@babel/runtime" "^7.0.0" -react-dom@^16.14.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.14.0.tgz#7ad838ec29a777fb3c75c3a190f661cf92ab8b89" - integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw== +react-dom@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.1" + scheduler "^0.20.2" react-focus-lock@^2.5.0: version "2.5.0" @@ -6751,7 +6808,12 @@ react-focus-lock@^2.5.0: use-callback-ref "^1.2.1" use-sidecar "^1.0.1" -react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6: +"react-is@^16.12.0 || ^17.0.0", react-is@^17.0.0, react-is@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -6788,15 +6850,23 @@ react-redux@^5.0.6: react-is "^16.6.0" react-lifecycles-compat "^3.0.0" -react-test-renderer@^16.0.0-0, react-test-renderer@^16.14.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz#e98360087348e260c56d4fe2315e970480c228ae" - integrity sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg== +react-shallow-renderer@^16.13.1: + version "16.14.1" + resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz#bf0d02df8a519a558fd9b8215442efa5c840e124" + integrity sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg== dependencies: object-assign "^4.1.1" - prop-types "^15.6.2" - react-is "^16.8.6" - scheduler "^0.19.1" + react-is "^16.12.0 || ^17.0.0" + +react-test-renderer@^17.0.0, react-test-renderer@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c" + integrity sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ== + dependencies: + object-assign "^4.1.1" + react-is "^17.0.2" + react-shallow-renderer "^16.13.1" + scheduler "^0.20.2" react-transition-group@^4.4.1: version "4.4.1" @@ -6808,14 +6878,13 @@ react-transition-group@^4.4.1: loose-envify "^1.4.0" prop-types "^15.6.2" -react@^16.14.0: - version "16.14.0" - resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" - integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== +react@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.2" read-pkg-up@^2.0.0: version "2.0.0" @@ -6923,11 +6992,6 @@ redux@^3.7.2: loose-envify "^1.1.0" symbol-observable "^1.0.3" -reflect.ownkeys@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" - integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA= - regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -7266,15 +7330,15 @@ saxes@^5.0.0: dependencies: xmlchars "^2.2.0" -scheduler@^0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" - integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -7622,6 +7686,14 @@ string.prototype.trimend@^1.0.1, string.prototype.trimend@^1.0.3: call-bind "^1.0.0" define-properties "^1.1.3" +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + string.prototype.trimstart@^1.0.1, string.prototype.trimstart@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz#9b4cb590e123bb36564401d59824298de50fd5aa" @@ -7630,6 +7702,14 @@ string.prototype.trimstart@^1.0.1, string.prototype.trimstart@^1.0.3: call-bind "^1.0.0" define-properties "^1.1.3" +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -8078,6 +8158,16 @@ ua-parser-js@^0.7.18: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== +unbox-primitive@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + unhomoglyph@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/unhomoglyph/-/unhomoglyph-1.0.6.tgz#ea41f926d0fcf598e3b8bb2980c2ddac66b081d3" @@ -8352,7 +8442,7 @@ whatwg-url@^8.0.0: tr46 "^2.0.2" webidl-conversions "^6.1.0" -which-boxed-primitive@^1.0.1: +which-boxed-primitive@^1.0.1, which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== From d492ee4d8acbf0156492cbd97e3f2f3e8153a234 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 9 Jun 2021 11:57:29 +0100 Subject: [PATCH 174/198] Update Enzyme adapter name --- test/accessibility/RovingTabIndex-test.js | 2 +- .../structures/MessagePanel-test.js | 32 +++++++++---------- .../views/messages/TextualBody-test.js | 2 +- .../views/rooms/SendMessageComposer-test.js | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/test/accessibility/RovingTabIndex-test.js b/test/accessibility/RovingTabIndex-test.js index 5aa93f99f3..7af28043e0 100644 --- a/test/accessibility/RovingTabIndex-test.js +++ b/test/accessibility/RovingTabIndex-test.js @@ -16,7 +16,7 @@ limitations under the License. import '../skinned-sdk'; // Must be first for skinning to work import React from "react"; -import Adapter from "enzyme-adapter-react-16"; +import Adapter from "@wojtekmaj/enzyme-adapter-react-17"; import { configure, mount } from "enzyme"; import { diff --git a/test/components/structures/MessagePanel-test.js b/test/components/structures/MessagePanel-test.js index 5b466b4bb0..9426998160 100644 --- a/test/components/structures/MessagePanel-test.js +++ b/test/components/structures/MessagePanel-test.js @@ -32,7 +32,7 @@ import Matrix from 'matrix-js-sdk'; const test_utils = require('../../test-utils'); const mockclock = require('../../mock-clock'); -import Adapter from "enzyme-adapter-react-16"; +import Adapter from "@wojtekmaj/enzyme-adapter-react-17"; import { configure, mount } from "enzyme"; import MatrixClientContext from "../../../src/contexts/MatrixClientContext"; @@ -77,7 +77,7 @@ describe('MessagePanel', function() { DMRoomMap.makeShared(); }); - afterEach(function () { + afterEach(function() { clock.uninstall(); }); @@ -270,7 +270,7 @@ describe('MessagePanel', function() { it('should show the events', function() { const res = TestUtils.renderIntoDocument( - , + , ); // just check we have the right number of tiles for now @@ -298,8 +298,8 @@ describe('MessagePanel', function() { it('should insert the read-marker in the right place', function() { const res = TestUtils.renderIntoDocument( - , + , ); const tiles = TestUtils.scryRenderedComponentsWithType( @@ -316,8 +316,8 @@ describe('MessagePanel', function() { it('should show the read-marker that fall in summarised events after the summary', function() { const melsEvents = mkMelsEvents(); const res = TestUtils.renderIntoDocument( - , + , ); const summary = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_EventListSummary'); @@ -334,8 +334,8 @@ describe('MessagePanel', function() { it('should hide the read-marker at the end of summarised events', function() { const melsEvents = mkMelsEventsOnly(); const res = TestUtils.renderIntoDocument( - , + , ); const summary = TestUtils.findRenderedDOMComponentWithClass(res, 'mx_EventListSummary'); @@ -358,9 +358,9 @@ describe('MessagePanel', function() { // first render with the RM in one place let mp = ReactDOM.render( - , parentDiv); + , parentDiv); const tiles = TestUtils.scryRenderedComponentsWithType( mp, sdk.getComponent('rooms.EventTile')); @@ -374,9 +374,9 @@ describe('MessagePanel', function() { // now move the RM mp = ReactDOM.render( - , parentDiv); + , parentDiv); // now there should be two RM containers const found = TestUtils.scryRenderedDOMComponentsWithClass(mp, 'mx_RoomView_myReadMarker_container'); @@ -451,7 +451,7 @@ describe('MessagePanel', function() { expect(isReadMarkerVisible(rm)).toBeFalsy(); }); - it('should render Date separators for the events', function () { + it('should render Date separators for the events', function() { const events = mkOneDayEvents(); const res = mount( Date: Wed, 9 Jun 2021 12:17:12 +0100 Subject: [PATCH 175/198] rename workflow --- .github/workflows/{end-to-end-tests.yml => develop.yml} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{end-to-end-tests.yml => develop.yml} (96%) diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/develop.yml similarity index 96% rename from .github/workflows/end-to-end-tests.yml rename to .github/workflows/develop.yml index abf7813bbc..346dcbaaba 100644 --- a/.github/workflows/end-to-end-tests.yml +++ b/.github/workflows/develop.yml @@ -1,11 +1,11 @@ -name: "end-to-end_tests" +name: Develop jobs on: push: branches: [develop] pull_request: branches: [develop] jobs: - "end-to-end_tests": + end-to-end: runs-on: ubuntu-latest defaults: run: From 4e14c612b78eec426ce88e4b9f880f9535d66365 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 9 Jun 2021 12:17:49 +0100 Subject: [PATCH 176/198] Remove extraenous instructions --- .github/workflows/develop.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index 346dcbaaba..3f82e61280 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -7,16 +7,7 @@ on: jobs: end-to-end: runs-on: ubuntu-latest - defaults: - run: - shell: bash - working-directory: ${{ github.workspace }} - container: - image: vectorim/element-web-ci-e2etests-env:latest - env: - CI_PACKAGE: true - GITHUB_HEAD_REF: ${{ github.head_ref }} - GITHUB_BASE_REF: ${{ github.base_ref }} + container: vectorim/element-web-ci-e2etests-env:latest steps: - name: Checkout code uses: actions/checkout@v2 From 15132074e2bd899e55da8be97650e129a05a5b64 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 9 Jun 2021 13:44:31 +0100 Subject: [PATCH 177/198] Restore copy button icon when sharing permalink --- res/css/views/dialogs/_ShareDialog.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/css/views/dialogs/_ShareDialog.scss b/res/css/views/dialogs/_ShareDialog.scss index ce3fdd021f..4d5e1409db 100644 --- a/res/css/views/dialogs/_ShareDialog.scss +++ b/res/css/views/dialogs/_ShareDialog.scss @@ -50,7 +50,8 @@ limitations under the License. margin-left: 20px; display: inherit; } -.mx_ShareDialog_matrixto_copy > div { +.mx_ShareDialog_matrixto_copy::after { + content: ""; mask-image: url($copy-button-url); background-color: $message-action-bar-fg-color; margin-left: 5px; From ceed6ecbe87f743ddd87252a673b16fd6a7bf96a Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 9 Jun 2021 14:03:31 +0100 Subject: [PATCH 178/198] Restore Page Up/Down key bindings when focusing the composer --- src/components/structures/RoomView.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 463b71922f..fe90d2f873 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1669,6 +1669,24 @@ export default class RoomView extends React.Component { }); }; + /** + * called by the parent component when PageUp/Down/etc is pressed. + * + * We pass it down to the scroll panel. + */ + private handleScrollKey = ev => { + let panel; + if (this.searchResultsPanel.current) { + panel = this.searchResultsPanel.current; + } else if (this.messagePanel) { + panel = this.messagePanel; + } + + if (panel) { + panel.handleScrollKey(ev); + } + }; + /** * get any current call for this room */ From 927de02a8e8e7a86eafeac59aecc779fecb9e986 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 9 Jun 2021 14:59:55 +0100 Subject: [PATCH 179/198] Revert refreshStickyHeaders optimisations --- src/components/structures/LeftPanel.tsx | 1 + src/components/views/rooms/RoomList.tsx | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 5b6b9c3717..af22db1350 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -439,6 +439,7 @@ export default class LeftPanel extends React.Component { onBlur={this.onBlur} isMinimized={this.props.isMinimized} activeSpace={this.state.activeSpace} + onResize={this.refreshStickyHeaders} onListCollapse={this.refreshStickyHeaders} />; diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 8b36341ed0..5a1c3a24b3 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -55,6 +55,7 @@ interface IProps { onKeyDown: (ev: React.KeyboardEvent) => void; onFocus: (ev: React.FocusEvent) => void; onBlur: (ev: React.FocusEvent) => void; + onResize: () => void; onListCollapse?: (isExpanded: boolean) => void; resizeNotifier: ResizeNotifier; isMinimized: boolean; @@ -404,7 +405,9 @@ export default class RoomList extends React.PureComponent { const newSublists = objectWithOnly(newLists, newListIds); const sublists = objectShallowClone(newSublists, (k, v) => arrayFastClone(v)); - this.setState({sublists, isNameFiltering}); + this.setState({sublists, isNameFiltering}, () => { + this.props.onResize(); + }); } }; From b43315c6c1c555ebce7a032dcc2f7950e999ed83 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 9 Jun 2021 15:46:10 +0100 Subject: [PATCH 180/198] Restore read receipt animation from event to event This restores expected read receipt animation by always including the positioned parent of read receipts. I imagine there's something smarter we could be doing, but for now, at least least get back to expected behaviour. Fixes https://github.com/vector-im/element-web/issues/17561 --- src/components/views/rooms/EventTile.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 9e9d7ef53d..354e84bcae 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -644,7 +644,18 @@ export default class EventTile extends React.Component { // return early if there are no read receipts if (!this.props.readReceipts || this.props.readReceipts.length === 0) { - return null; + // We currently must include `mx_EventTile_readAvatars` in the DOM + // of all events, as it is the positioned parent of the animated + // read receipts. We can't let it unmount when a receipt moves + // events, so for now we mount it for all events. With out it, the + // animation will start from the top of the timeline (because it + // lost its container). + // See also https://github.com/vector-im/element-web/issues/17561 + return ( +
    + +
    + ); } const ReadReceiptMarker = sdk.getComponent('rooms.ReadReceiptMarker'); From e566704bdf1baf5abc81601401d6f00422117923 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 9 Jun 2021 15:48:30 +0100 Subject: [PATCH 181/198] Remove redundant early return --- src/components/views/rooms/EventTile.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 354e84bcae..48118444a5 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -663,11 +663,7 @@ export default class EventTile extends React.Component { const receiptOffset = 15; let left = 0; - const receipts = this.props.readReceipts || []; - - if (receipts.length === 0) { - return null; - } + const receipts = this.props.readReceipts; for (let i = 0; i < receipts.length; ++i) { const receipt = receipts[i]; From c6972c4535c09e914a7aaa2c980c5e2f3fe78eb3 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 9 Jun 2021 15:53:38 +0100 Subject: [PATCH 182/198] Fix spelling --- src/components/views/rooms/EventTile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 48118444a5..0861f7fd5d 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -647,7 +647,7 @@ export default class EventTile extends React.Component { // We currently must include `mx_EventTile_readAvatars` in the DOM // of all events, as it is the positioned parent of the animated // read receipts. We can't let it unmount when a receipt moves - // events, so for now we mount it for all events. With out it, the + // events, so for now we mount it for all events. Without it, the // animation will start from the top of the timeline (because it // lost its container). // See also https://github.com/vector-im/element-web/issues/17561 From bd8fd246c67a4b79f7374e1cad18001080ff4741 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 9 Jun 2021 16:04:03 +0100 Subject: [PATCH 183/198] Add logging for which rooms calls are in --- src/CallHandler.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx index 0d87451b5f..bf7cb3473d 100644 --- a/src/CallHandler.tsx +++ b/src/CallHandler.tsx @@ -542,6 +542,7 @@ export default class CallHandler extends EventEmitter { if (newMappedRoomId !== mappedRoomId) { this.removeCallForRoom(mappedRoomId); mappedRoomId = newMappedRoomId; + console.log("Moving call to room " + mappedRoomId); this.calls.set(mappedRoomId, call); this.emit(CallHandlerEvent.CallChangeRoom, call); } @@ -607,6 +608,7 @@ export default class CallHandler extends EventEmitter { } private removeCallForRoom(roomId: string) { + console.log("Removing call for room ", roomId); this.calls.delete(roomId); this.emit(CallHandlerEvent.CallsChanged, this.calls); } @@ -680,6 +682,7 @@ export default class CallHandler extends EventEmitter { console.log("Current turn creds expire in " + timeUntilTurnCresExpire + " ms"); const call = MatrixClientPeg.get().createCall(mappedRoomId); + console.log("Adding call for room ", roomId); this.calls.set(roomId, call); this.emit(CallHandlerEvent.CallsChanged, this.calls); if (transferee) { @@ -812,6 +815,7 @@ export default class CallHandler extends EventEmitter { } Analytics.trackEvent('voip', 'receiveCall', 'type', call.type); + console.log("Adding call for room ", mappedRoomId); this.calls.set(mappedRoomId, call) this.emit(CallHandlerEvent.CallsChanged, this.calls); this.setCallListeners(call); From b4cb275ca1e3e5b5d7d068b8cacfed85ee375ba7 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 9 Jun 2021 17:59:41 +0100 Subject: [PATCH 184/198] Fix expanding last collapsed sticky session when zoomed in --- src/components/views/rooms/RoomSublist.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index 0bb7381dbc..ba8bbffbcc 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -454,8 +454,9 @@ export default class RoomSublist extends React.Component { const sublist = possibleSticky.parentElement.parentElement; const list = sublist.parentElement.parentElement; // the scrollTop is capped at the height of the header in LeftPanel, the top header is always sticky - const isAtTop = list.scrollTop <= HEADER_HEIGHT; - const isAtBottom = list.scrollTop >= list.scrollHeight - list.offsetHeight; + const listScrollTop = Math.round(list.scrollTop); + const isAtTop = listScrollTop <= Math.round(HEADER_HEIGHT); + const isAtBottom = listScrollTop >= Math.round(list.scrollHeight - list.offsetHeight); const isStickyTop = possibleSticky.classList.contains('mx_RoomSublist_headerContainer_stickyTop'); const isStickyBottom = possibleSticky.classList.contains('mx_RoomSublist_headerContainer_stickyBottom'); From 4f649f290cafbe49c9bc7ed8ac8c20e1ae77ab35 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 10 Jun 2021 10:46:21 +0100 Subject: [PATCH 185/198] Migrate RoomSettingsDialog to TypeScript --- ...ttingsDialog.js => RoomSettingsDialog.tsx} | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) rename src/components/views/dialogs/{RoomSettingsDialog.js => RoomSettingsDialog.tsx} (87%) diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.tsx similarity index 87% rename from src/components/views/dialogs/RoomSettingsDialog.js rename to src/components/views/dialogs/RoomSettingsDialog.tsx index b6c4d42243..9d1608c9e4 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.js +++ b/src/components/views/dialogs/RoomSettingsDialog.tsx @@ -16,7 +16,6 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import TabbedView, {Tab} from "../../structures/TabbedView"; import {_t, _td} from "../../../languageHandler"; import AdvancedRoomSettingsTab from "../settings/tabs/room/AdvancedRoomSettingsTab"; @@ -39,31 +38,35 @@ export const ROOM_NOTIFICATIONS_TAB = "ROOM_NOTIFICATIONS_TAB"; export const ROOM_BRIDGES_TAB = "ROOM_BRIDGES_TAB"; export const ROOM_ADVANCED_TAB = "ROOM_ADVANCED_TAB"; +interface IProps { + roomId: string; + onFinished: (success: boolean) => void; +} + @replaceableComponent("views.dialogs.RoomSettingsDialog") -export default class RoomSettingsDialog extends React.Component { - static propTypes = { - roomId: PropTypes.string.isRequired, - onFinished: PropTypes.func.isRequired, - }; +export default class RoomSettingsDialog extends React.Component { + private dispatcherRef: string; - componentDidMount() { - this._dispatcherRef = dis.register(this._onAction); + public componentDidMount() { + this.dispatcherRef = dis.register(this.onAction); } - componentWillUnmount() { - if (this._dispatcherRef) dis.unregister(this._dispatcherRef); + public componentWillUnmount() { + if (this.dispatcherRef) { + dis.unregister(this.dispatcherRef); + } } - _onAction = (payload) => { + private onAction = (payload): void => { // When view changes below us, close the room settings // whilst the modal is open this can only be triggered when someone hits Leave Room if (payload.action === 'view_home_page') { - this.props.onFinished(); + this.props.onFinished(true); } }; - _getTabs() { - const tabs = []; + private getTabs(): Tab[] { + const tabs: Tab[] = []; tabs.push(new Tab( ROOM_GENERAL_TAB, @@ -123,7 +126,7 @@ export default class RoomSettingsDialog extends React.Component { title={_t("Room Settings - %(roomName)s", {roomName})} >
    - +
    ); From 7f3173f17085ee598e78c20f94db916b7f297fa7 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 10 Jun 2021 11:14:43 +0100 Subject: [PATCH 186/198] Implement unencrypted warning slate in rooms --- .../views/dialogs/RoomSettingsDialog.tsx | 6 ++- .../views/messages/EventTileBubble.tsx | 3 +- src/components/views/rooms/NewRoomIntro.tsx | 37 +++++++++++++------ src/i18n/strings/en_EN.json | 3 +- src/stores/RoomViewStore.tsx | 1 + 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/components/views/dialogs/RoomSettingsDialog.tsx b/src/components/views/dialogs/RoomSettingsDialog.tsx index 9d1608c9e4..1a664951c5 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.tsx +++ b/src/components/views/dialogs/RoomSettingsDialog.tsx @@ -41,6 +41,7 @@ export const ROOM_ADVANCED_TAB = "ROOM_ADVANCED_TAB"; interface IProps { roomId: string; onFinished: (success: boolean) => void; + initialTabId?: string; } @replaceableComponent("views.dialogs.RoomSettingsDialog") @@ -126,7 +127,10 @@ export default class RoomSettingsDialog extends React.Component { title={_t("Room Settings - %(roomName)s", {roomName})} >
    - +
    ); diff --git a/src/components/views/messages/EventTileBubble.tsx b/src/components/views/messages/EventTileBubble.tsx index f797a97a3d..88913ac2d4 100644 --- a/src/components/views/messages/EventTileBubble.tsx +++ b/src/components/views/messages/EventTileBubble.tsx @@ -14,13 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {forwardRef, ReactNode} from "react"; +import React, {forwardRef, ReactNode, ReactChildren} from "react"; import classNames from "classnames"; interface IProps { className: string; title: string; subtitle?: ReactNode; + children?: ReactChildren; } const EventTileBubble = forwardRef(({ className, title, subtitle, children }, ref) => { diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index 9b003ade89..21282c415a 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -33,6 +33,9 @@ import {showSpaceInvite} from "../../../utils/space"; import { privateShouldBeEncrypted } from "../../../createRoom"; +import EventTileBubble from "../messages/EventTileBubble"; +import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog"; + function hasExpectedEncryptionSettings(room): boolean { const isEncrypted: boolean = room._client.isRoomEncrypted(room.roomId); const isPublic: boolean = room.getJoinRule() === "public"; @@ -174,20 +177,30 @@ const NewRoomIntro = () => { ; } + function openRoomSettings(event) { + event.preventDefault(); + dis.dispatch({ + action: "open_room_settings", + initial_tab_id: ROOM_SECURITY_TAB, + }); + } + + const sub2 = _t("Your private messages are normally encrypted, but this room isn't." + + "Usually this is because the room was created with a device or method that doesn't support " + + "encryption, like email invites. Enable encryption in settings.", {}, + { a: sub => {sub} }); + return
    + + { !hasExpectedEncryptionSettings(room) && ( + + )} + { body } - - { !hasExpectedEncryptionSettings(room) &&

    - {_t("Messages in this room are not end-to-end encrypted. " + - "Messages sent in an unencrypted room can be seen by the server and third parties. " + - "Learn more about encryption.", {}, - { - a: sub => {sub}, - })} - -

    }
    ; }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 8508d55eba..3ab69cdb11 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1509,7 +1509,8 @@ "Invite to just this room": "Invite to just this room", "Add a photo, so people can easily spot your room.": "Add a photo, so people can easily spot your room.", "This is the start of .": "This is the start of .", - "Messages in this room are not end-to-end encrypted. Messages sent in an unencrypted room can be seen by the server and third parties. Learn more about encryption.": "Messages in this room are not end-to-end encrypted. Messages sent in an unencrypted room can be seen by the server and third parties. Learn more about encryption.", + "Your private messages are normally encrypted, but this room isn't.Usually this is because the room was created with a device or method that doesn't support encryption, like email invites. Enable encryption in settings.": "Your private messages are normally encrypted, but this room isn't.Usually this is because the room was created with a device or method that doesn't support encryption, like email invites. Enable encryption in settings.", + "This room isn't end to end encrypted": "This room isn't end to end encrypted", "Unpin": "Unpin", "View message": "View message", "%(duration)ss": "%(duration)ss", diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index 8da565f603..cc3eafffcd 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -167,6 +167,7 @@ class RoomViewStore extends Store { const RoomSettingsDialog = sdk.getComponent("dialogs.RoomSettingsDialog"); Modal.createTrackedDialog('Room settings', '', RoomSettingsDialog, { roomId: payload.room_id || this.state.roomId, + initialTabId: payload.initial_tab_id, }, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true); break; } From 0e2e327c4650d835abf8b2b0d9cee39982383144 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 10 Jun 2021 11:19:25 +0100 Subject: [PATCH 187/198] Delete new room intro stale CSS --- res/css/views/rooms/_NewRoomIntro.scss | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/res/css/views/rooms/_NewRoomIntro.scss b/res/css/views/rooms/_NewRoomIntro.scss index 357273b597..e0cccfa885 100644 --- a/res/css/views/rooms/_NewRoomIntro.scss +++ b/res/css/views/rooms/_NewRoomIntro.scss @@ -69,24 +69,4 @@ limitations under the License. font-size: $font-15px; color: $secondary-fg-color; } - - .mx_NewRoomIntro_message:not(:first-child) { - padding-top: 1em; - margin-top: 1em; - border-top: 1px solid currentColor; - } - - .mx_NewRoomIntro_message[role=alert]::before { - --size: 25px; - content: "!"; - float: left; - border-radius: 50%; - width: var(--size); - height: var(--size); - line-height: var(--size); - text-align: center; - background: $button-danger-bg-color; - color: #fff; - margin-right: calc(var(--size) / 2); - } } From 6606a1142ded38fe85953d1cc881599491aa3054 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 10 Jun 2021 11:29:10 +0100 Subject: [PATCH 188/198] Update unencrypted room warning copy --- src/components/views/rooms/NewRoomIntro.tsx | 8 ++++---- src/i18n/strings/en_EN.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index 21282c415a..76240f6a2f 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -185,9 +185,9 @@ const NewRoomIntro = () => { }); } - const sub2 = _t("Your private messages are normally encrypted, but this room isn't." + - "Usually this is because the room was created with a device or method that doesn't support " + - "encryption, like email invites. Enable encryption in settings.", {}, + const sub2 = _t("Your private messages are normally encrypted, but this room isn't. "+ + "Usually this is due to an unsupported device or method being used, " + + "like email invites. Enable encryption in settings.", {}, { a: sub => {sub} }); return
    @@ -195,7 +195,7 @@ const NewRoomIntro = () => { { !hasExpectedEncryptionSettings(room) && ( )} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 3ab69cdb11..874dc11bd2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1509,8 +1509,8 @@ "Invite to just this room": "Invite to just this room", "Add a photo, so people can easily spot your room.": "Add a photo, so people can easily spot your room.", "This is the start of .": "This is the start of .", - "Your private messages are normally encrypted, but this room isn't.Usually this is because the room was created with a device or method that doesn't support encryption, like email invites. Enable encryption in settings.": "Your private messages are normally encrypted, but this room isn't.Usually this is because the room was created with a device or method that doesn't support encryption, like email invites. Enable encryption in settings.", - "This room isn't end to end encrypted": "This room isn't end to end encrypted", + "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.", + "End-to-end encryption isn't enabled": "End-to-end encryption isn't enabled", "Unpin": "Unpin", "View message": "View message", "%(duration)ss": "%(duration)ss", From 9731e275f8665b59b8a50746603ad4754392848d Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 10 Jun 2021 12:15:55 +0100 Subject: [PATCH 189/198] cater for an undefined MatrixClient on rooms --- src/components/views/rooms/NewRoomIntro.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index 76240f6a2f..390a570b26 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -37,7 +37,7 @@ import EventTileBubble from "../messages/EventTileBubble"; import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog"; function hasExpectedEncryptionSettings(room): boolean { - const isEncrypted: boolean = room._client.isRoomEncrypted(room.roomId); + const isEncrypted: boolean = room._client?.isRoomEncrypted(room.roomId); const isPublic: boolean = room.getJoinRule() === "public"; return isPublic || !privateShouldBeEncrypted() || isEncrypted; } From 79b8fbc2a909364972043ebe06019406247fb9b5 Mon Sep 17 00:00:00 2001 From: Germain Date: Thu, 10 Jun 2021 14:21:48 +0100 Subject: [PATCH 190/198] Add indentation for unencrypted warning text Co-authored-by: J. Ryan Stinnett --- src/components/views/rooms/NewRoomIntro.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index 390a570b26..3bf9a9db33 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -185,10 +185,12 @@ const NewRoomIntro = () => { }); } - const sub2 = _t("Your private messages are normally encrypted, but this room isn't. "+ - "Usually this is due to an unsupported device or method being used, " + - "like email invites. Enable encryption in settings.", {}, - { a: sub => {sub} }); + const sub2 = _t( + "Your private messages are normally encrypted, but this room isn't. "+ + "Usually this is due to an unsupported device or method being used, " + + "like email invites. Enable encryption in settings.", {}, + { a: sub => {sub} }, + ); return
    From 8174973cfed821249db3c3db61a489c79d776039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 10 Jun 2021 15:31:27 +0200 Subject: [PATCH 191/198] Use the same styling as for mx_TextualEvent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/messages/_SenderProfile.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/messages/_SenderProfile.scss b/res/css/views/messages/_SenderProfile.scss index 7aaafa6ac3..08644b14e3 100644 --- a/res/css/views/messages/_SenderProfile.scss +++ b/res/css/views/messages/_SenderProfile.scss @@ -21,6 +21,6 @@ limitations under the License. .mx_SenderProfile_mxid { font-weight: 600; font-size: 1.1rem; - color: $event-timestamp-color; margin-left: 5px; + opacity: 0.5; // Match mx_TextualEvent } From 5343be7814e9fd120c3779aa8a33951716cb2d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 10 Jun 2021 18:53:56 +0200 Subject: [PATCH 192/198] Fix buggy hovering/selecting of event tiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/views/rooms/_EventTile.scss | 18 +++++++++++------- res/css/views/rooms/_GroupLayout.scss | 8 -------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index c8b4138f27..3af266caee 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -16,6 +16,7 @@ limitations under the License. */ $left-gutter: 64px; +$hover-select-border: 4px; .mx_EventTile { max-width: 100%; @@ -142,8 +143,7 @@ $left-gutter: 64px; } .mx_EventTile_selected > div > a > .mx_MessageTimestamp { - left: 3px; - width: auto; + left: calc(-$hover-select-border); } .mx_EventTile:hover .mx_MessageActionBar, @@ -158,7 +158,7 @@ $left-gutter: 64px; */ .mx_EventTile_selected > .mx_EventTile_line { border-left: $accent-color 4px solid; - padding-left: 60px; + padding-left: calc($left-gutter - $hover-select-border); background-color: $event-selected-color; } @@ -171,8 +171,12 @@ $left-gutter: 64px; } } +.mx_EventTile_info .mx_EventTile_line { + padding-left: calc($left-gutter + 18px); +} + .mx_EventTile_selected.mx_EventTile_info .mx_EventTile_line { - padding-left: 78px; + padding-left: calc($left-gutter + 18px - $hover-select-border); } .mx_EventTile:hover .mx_EventTile_line, @@ -408,7 +412,7 @@ $left-gutter: 64px; .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line, .mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line, .mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line { - padding-left: 60px; + padding-left: calc($left-gutter - $hover-select-border); } .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line { @@ -426,7 +430,7 @@ $left-gutter: 64px; .mx_EventTile:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line, .mx_EventTile:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line, .mx_EventTile:hover.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line { - padding-left: 78px; + padding-left: calc($left-gutter + 18px - $hover-select-border); } /* End to end encryption stuff */ @@ -438,7 +442,7 @@ $left-gutter: 64px; .mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > a > .mx_MessageTimestamp, .mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp, .mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line > a > .mx_MessageTimestamp { - width: $MessageTimestamp_width_hover; + left: calc(-$hover-select-border); } // Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies) diff --git a/res/css/views/rooms/_GroupLayout.scss b/res/css/views/rooms/_GroupLayout.scss index 818509785b..ddee81a914 100644 --- a/res/css/views/rooms/_GroupLayout.scss +++ b/res/css/views/rooms/_GroupLayout.scss @@ -24,10 +24,6 @@ $left-gutter: 64px; margin-left: $left-gutter; } - > .mx_EventTile_line { - padding-left: $left-gutter; - } - > .mx_EventTile_avatar { position: absolute; } @@ -43,10 +39,6 @@ $left-gutter: 64px; line-height: $font-22px; } } - - .mx_EventTile_info .mx_EventTile_line { - padding-left: calc($left-gutter + 18px); - } } /* Compact layout overrides */ From b1824ce579c805c8c293aee739808b71bc030614 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 11 Jun 2021 11:34:53 +0100 Subject: [PATCH 193/198] fix HTML tag for Event Tile when not rendered in a list --- .../views/elements/EventTilePreview.tsx | 1 + src/components/views/rooms/EventTile.tsx | 150 ++++++++++-------- src/components/views/rooms/ReplyPreview.js | 1 + 3 files changed, 82 insertions(+), 70 deletions(-) diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx index b15fbbed2b..cf3b7a6e61 100644 --- a/src/components/views/elements/EventTilePreview.tsx +++ b/src/components/views/elements/EventTilePreview.tsx @@ -128,6 +128,7 @@ export default class EventTilePreview extends React.Component { mxEvent={event} layout={this.props.layout} enableFlair={SettingsStore.getValue(UIFeature.Flair)} + as="div" />
    ; } diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 0861f7fd5d..85b9cac2c4 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -1059,58 +1059,65 @@ export default class EventTile extends React.Component { switch (this.props.tileShape) { case 'notif': { const room = this.context.getRoom(this.props.mxEvent.getRoomId()); - return ( -
  • - - -
    - -
    -
  • - ); + return React.createElement(this.props.as || "li", { + "className": classes, + "aria-live": ariaLive, + "aria-atomic": true, + "data-scroll-tokens": scrollToken, + }, [ + , + , +
    + +
    , + ]); } case 'file_grid': { - return ( -
  • -
    - + return React.createElement(this.props.as || "li", { + "className": classes, + "aria-live": ariaLive, + "aria-atomic": true, + "data-scroll-tokens": scrollToken, + }, [ +
    + +
    , + +
    + { sender } + { timestamp }
    -
    -
    - { sender } - { timestamp } -
    -
    -
  • - ); + , + ]); } case 'reply': @@ -1126,27 +1133,30 @@ export default class EventTile extends React.Component { this.props.alwaysShowTimestamps || this.state.hover, ); } - return ( -
  • - { ircTimestamp } - { avatar } - { sender } - { ircPadlock } -
    - { groupTimestamp } - { groupPadlock } - { thread } - -
    -
  • - ); + return React.createElement(this.props.as || "li", { + "className": classes, + "aria-live": ariaLive, + "aria-atomic": true, + "data-scroll-tokens": scrollToken, + }, [ + ircTimestamp, + avatar, + sender, + ircPadlock, +
    + { groupTimestamp } + { groupPadlock } + { thread } + +
    , + ]); } default: { const thread = ReplyThread.makeThread( diff --git a/src/components/views/rooms/ReplyPreview.js b/src/components/views/rooms/ReplyPreview.js index 5835eb9f36..c61424a059 100644 --- a/src/components/views/rooms/ReplyPreview.js +++ b/src/components/views/rooms/ReplyPreview.js @@ -95,6 +95,7 @@ export default class ReplyPreview extends React.Component { permalinkCreator={this.props.permalinkCreator} isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")} enableFlair={SettingsStore.getValue(UIFeature.Flair)} + as="div" />
    ; From 84679cf8ec5bfe764cda2743f69a9099fe79c38a Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 11 Jun 2021 12:19:14 +0100 Subject: [PATCH 194/198] remove legacy polyfills and unused dependencies --- package.json | 3 --- src/ContentMessages.tsx | 2 -- src/CountlyAnalytics.ts | 7 ------- src/rageshake/submit-rageshake.ts | 6 ------ src/utils/MegolmExportEncryption.js | 11 ----------- yarn.lock | 10 ---------- 6 files changed, 39 deletions(-) diff --git a/package.json b/package.json index dbb6b29b53..4dc4bda3b2 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "dependencies": { "@babel/runtime": "^7.12.5", "await-lock": "^2.1.0", - "blueimp-canvas-to-blob": "^3.28.0", "browser-encrypt-attachment": "^0.3.0", "browser-request": "^0.3.3", "cheerio": "^1.0.0-rc.9", @@ -88,7 +87,6 @@ "png-chunks-extract": "^1.0.0", "prop-types": "^15.7.2", "qrcode": "^1.4.4", - "qs": "^6.9.6", "re-resizable": "^6.9.0", "react": "^17.0.2", "react-beautiful-dnd": "^4.0.1", @@ -99,7 +97,6 @@ "rfc4648": "^1.4.0", "sanitize-html": "^2.3.2", "tar-js": "^0.3.0", - "text-encoding-utf-8": "^1.0.2", "url": "^0.11.0", "what-input": "^5.2.10", "zxcvbn": "^4.4.2" diff --git a/src/ContentMessages.tsx b/src/ContentMessages.tsx index b21829ac63..9042d47243 100644 --- a/src/ContentMessages.tsx +++ b/src/ContentMessages.tsx @@ -28,8 +28,6 @@ import encrypt from "browser-encrypt-attachment"; import extractPngChunks from "png-chunks-extract"; import Spinner from "./components/views/elements/Spinner"; -// Polyfill for Canvas.toBlob API using Canvas.toDataURL -import "blueimp-canvas-to-blob"; import { Action } from "./dispatcher/actions"; import CountlyAnalytics from "./CountlyAnalytics"; import { diff --git a/src/CountlyAnalytics.ts b/src/CountlyAnalytics.ts index 5545ed8483..f494c1eb22 100644 --- a/src/CountlyAnalytics.ts +++ b/src/CountlyAnalytics.ts @@ -24,13 +24,6 @@ import {sleep} from "./utils/promise"; import RoomViewStore from "./stores/RoomViewStore"; import { Action } from "./dispatcher/actions"; -// polyfill textencoder if necessary -import * as TextEncodingUtf8 from 'text-encoding-utf-8'; -let TextEncoder = window.TextEncoder; -if (!TextEncoder) { - TextEncoder = TextEncodingUtf8.TextEncoder; -} - const INACTIVITY_TIME = 20; // seconds const HEARTBEAT_INTERVAL = 5_000; // ms const SESSION_UPDATE_INTERVAL = 60; // seconds diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts index b2ad9fe6f6..08d8ccfd13 100644 --- a/src/rageshake/submit-rageshake.ts +++ b/src/rageshake/submit-rageshake.ts @@ -25,14 +25,8 @@ import Tar from "tar-js"; import * as rageshake from './rageshake'; -// polyfill textencoder if necessary -import * as TextEncodingUtf8 from 'text-encoding-utf-8'; import SettingsStore from "../settings/SettingsStore"; import SdkConfig from "../SdkConfig"; -let TextEncoder = window.TextEncoder; -if (!TextEncoder) { - TextEncoder = TextEncodingUtf8.TextEncoder; -} interface IOpts { label?: string; diff --git a/src/utils/MegolmExportEncryption.js b/src/utils/MegolmExportEncryption.js index 6f5c7104b1..ae6b2999fa 100644 --- a/src/utils/MegolmExportEncryption.js +++ b/src/utils/MegolmExportEncryption.js @@ -15,17 +15,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// polyfill textencoder if necessary -import * as TextEncodingUtf8 from 'text-encoding-utf-8'; -let TextEncoder = window.TextEncoder; -if (!TextEncoder) { - TextEncoder = TextEncodingUtf8.TextEncoder; -} -let TextDecoder = window.TextDecoder; -if (!TextDecoder) { - TextDecoder = TextEncodingUtf8.TextDecoder; -} - import { _t } from '../languageHandler'; import SdkConfig from '../SdkConfig'; diff --git a/yarn.lock b/yarn.lock index 2b8667c7a6..f98cb5845e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2181,11 +2181,6 @@ bluebird@^3.5.0: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -blueimp-canvas-to-blob@^3.28.0: - version "3.28.0" - resolved "https://registry.yarnpkg.com/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.28.0.tgz#c8ab4dc6bb08774a7f273798cdf94b0776adf6c8" - integrity sha512-5q+YHzgGsuHQ01iouGgJaPJXod2AzTxJXmVv90PpGrRxU7G7IqgPqWXz+PBmt3520jKKi6irWbNV87DicEa7wg== - boolbase@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -7945,11 +7940,6 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" -text-encoding-utf-8@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" - integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== - text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" From 37484e3fc465ef5a05a5001580facbea37d10bb5 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Fri, 11 Jun 2021 12:33:02 +0100 Subject: [PATCH 195/198] Add TextEncoder polyfill for test run --- test/setupTests.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/setupTests.js b/test/setupTests.js index 6d37d48987..9da412a7e8 100644 --- a/test/setupTests.js +++ b/test/setupTests.js @@ -1,6 +1,12 @@ import * as languageHandler from "../src/languageHandler"; +import { TextEncoder, TextDecoder } from 'util'; languageHandler.setLanguage('en'); languageHandler.setMissingEntryGenerator(key => key.split("|", 2)[1]); require('jest-fetch-mock').enableMocks(); + +// polyfilling TextEncoder as it is not available on JSDOM +// view https://github.com/facebook/jest/issues/9983 +global.TextEncoder = TextEncoder; +global.TextDecoder = TextDecoder; From e260e8671fab08da34f8c9abc30ec39e1aa486d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 11 Jun 2021 15:14:16 +0200 Subject: [PATCH 196/198] Inline display name into return MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/SenderProfile.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx index 872dc132b9..805f842fbc 100644 --- a/src/components/views/messages/SenderProfile.tsx +++ b/src/components/views/messages/SenderProfile.tsx @@ -118,12 +118,6 @@ export default class SenderProfile extends React.Component { return null; // emote message must include the name so don't duplicate it } - const displayNameElement = ( - - { displayName } - - ); - let mxidElement; if (disambiguate) { mxidElement = ( @@ -147,7 +141,9 @@ export default class SenderProfile extends React.Component { return (
    - { displayNameElement } + + { displayName } + { mxidElement } { flair }
    From 2eafe132b8b0650b058faa4be3bbdc4e5167ff38 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 14 Jun 2021 13:31:58 -0600 Subject: [PATCH 197/198] Move various createRoom types to the js-sdk --- src/components/structures/SpaceRoomView.tsx | 3 +- .../views/dialogs/CreateRoomDialog.tsx | 21 ++++---- .../views/spaces/SpaceCreateMenu.tsx | 6 ++- src/createRoom.ts | 52 ++----------------- src/mjolnir/Mjolnir.ts | 3 +- 5 files changed, 23 insertions(+), 62 deletions(-) diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index 276f4ae6ca..3ccf2e5424 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -28,7 +28,7 @@ import RoomTopic from "../views/elements/RoomTopic"; import InlineSpinner from "../views/elements/InlineSpinner"; import {inviteMultipleToRoom, showRoomInviteDialog} from "../../RoomInvite"; import {useRoomMembers} from "../../hooks/useRoomMembers"; -import createRoom, {IOpts, Preset} from "../../createRoom"; +import createRoom, {IOpts} from "../../createRoom"; import Field from "../views/elements/Field"; import {useEventEmitter} from "../../hooks/useEventEmitter"; import withValidation from "../views/elements/Validation"; @@ -65,6 +65,7 @@ import dis from "../../dispatcher/dispatcher"; import Modal from "../../Modal"; import BetaFeedbackDialog from "../views/dialogs/BetaFeedbackDialog"; import SdkConfig from "../../SdkConfig"; +import { Preset } from "matrix-js-sdk/src/@types/partials"; interface IProps { space: Room; diff --git a/src/components/views/dialogs/CreateRoomDialog.tsx b/src/components/views/dialogs/CreateRoomDialog.tsx index cce6b6c34c..544f593708 100644 --- a/src/components/views/dialogs/CreateRoomDialog.tsx +++ b/src/components/views/dialogs/CreateRoomDialog.tsx @@ -15,22 +15,23 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {ChangeEvent, createRef, KeyboardEvent, SyntheticEvent} from "react"; -import {Room} from "matrix-js-sdk/src/models/room"; +import React, { ChangeEvent, createRef, KeyboardEvent, SyntheticEvent } from "react"; +import { Room } from "matrix-js-sdk/src/models/room"; import SdkConfig from '../../../SdkConfig'; -import withValidation, {IFieldState} from '../elements/Validation'; -import {_t} from '../../../languageHandler'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; -import {Key} from "../../../Keyboard"; -import {IOpts, Preset, privateShouldBeEncrypted, Visibility} from "../../../createRoom"; -import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore"; -import {replaceableComponent} from "../../../utils/replaceableComponent"; +import withValidation, { IFieldState } from '../elements/Validation'; +import { _t } from '../../../languageHandler'; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; +import { Key } from "../../../Keyboard"; +import { IOpts, privateShouldBeEncrypted } from "../../../createRoom"; +import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; import Field from "../elements/Field"; import RoomAliasField from "../elements/RoomAliasField"; import LabelledToggleSwitch from "../elements/LabelledToggleSwitch"; import DialogButtons from "../elements/DialogButtons"; import BaseDialog from "../dialogs/BaseDialog"; +import { Preset, Visibility } from "matrix-js-sdk/src/@types/partials"; interface IProps { defaultPublic?: boolean; @@ -72,7 +73,7 @@ export default class CreateRoomDialog extends React.Component { canChangeEncryption: true, }; - MatrixClientPeg.get().doesServerForceEncryptionForPreset("private") + MatrixClientPeg.get().doesServerForceEncryptionForPreset(Preset.PrivateChat) .then(isForced => this.setState({ canChangeEncryption: !isForced })); } diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx index 0ebf511018..6a935ab276 100644 --- a/src/components/views/spaces/SpaceCreateMenu.tsx +++ b/src/components/views/spaces/SpaceCreateMenu.tsx @@ -22,7 +22,7 @@ import FocusLock from "react-focus-lock"; import {_t} from "../../../languageHandler"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import {ChevronFace, ContextMenu} from "../../structures/ContextMenu"; -import createRoom, {IStateEvent, Preset} from "../../../createRoom"; +import createRoom from "../../../createRoom"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import {SpaceAvatar} from "./SpaceBasicSettings"; import AccessibleButton from "../elements/AccessibleButton"; @@ -33,6 +33,8 @@ import {USER_LABS_TAB} from "../dialogs/UserSettingsDialog"; import Field from "../elements/Field"; import withValidation from "../elements/Validation"; import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView"; +import { Preset } from "matrix-js-sdk/src/@types/partials"; +import { ICreateRoomStateEvent } from "matrix-js-sdk/src/@types/requests"; const SpaceCreateMenuType = ({ title, description, className, onClick }) => { return ( @@ -81,7 +83,7 @@ const SpaceCreateMenu = ({ onFinished }) => { return; } - const initialState: IStateEvent[] = [ + const initialState: ICreateRoomStateEvent[] = [ { type: EventType.RoomHistoryVisibility, content: { diff --git a/src/createRoom.ts b/src/createRoom.ts index c6507b1380..2641492588 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -35,53 +35,15 @@ import { VIRTUAL_ROOM_EVENT_TYPE } from "./CallHandler"; import SpaceStore from "./stores/SpaceStore"; import { makeSpaceParentEvent } from "./utils/space"; import { Action } from "./dispatcher/actions" +import { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests"; +import { Preset, Visibility } from "matrix-js-sdk/src/@types/partials"; // we define a number of interfaces which take their names from the js-sdk /* eslint-disable camelcase */ -// TODO move these interfaces over to js-sdk once it has been typescripted enough to accept them -export enum Visibility { - Public = "public", - Private = "private", -} - -export enum Preset { - PrivateChat = "private_chat", - TrustedPrivateChat = "trusted_private_chat", - PublicChat = "public_chat", -} - -interface Invite3PID { - id_server: string; - id_access_token?: string; // this gets injected by the js-sdk - medium: string; - address: string; -} - -export interface IStateEvent { - type: string; - state_key?: string; // defaults to an empty string - content: object; -} - -interface ICreateOpts { - visibility?: Visibility; - room_alias_name?: string; - name?: string; - topic?: string; - invite?: string[]; - invite_3pid?: Invite3PID[]; - room_version?: string; - creation_content?: object; - initial_state?: IStateEvent[]; - preset?: Preset; - is_direct?: boolean; - power_level_content_override?: object; -} - export interface IOpts { dmUserId?: string; - createOpts?: ICreateOpts; + createOpts?: ICreateRoomOpts; spinner?: boolean; guestAccess?: boolean; encryption?: boolean; @@ -91,12 +53,6 @@ export interface IOpts { parentSpace?: Room; } -export interface IInvite3PID { - id_server: string, - medium: 'email', - address: string, -} - /** * Create a new room, and switch to it. * @@ -136,7 +92,7 @@ export default function createRoom(opts: IOpts): Promise { const defaultPreset = opts.dmUserId ? Preset.TrustedPrivateChat : Preset.PrivateChat; // set some defaults for the creation - const createOpts = opts.createOpts || {}; + const createOpts: ICreateRoomOpts = opts.createOpts || {}; createOpts.preset = createOpts.preset || defaultPreset; createOpts.visibility = createOpts.visibility || Visibility.Private; if (opts.dmUserId && createOpts.invite === undefined) { diff --git a/src/mjolnir/Mjolnir.ts b/src/mjolnir/Mjolnir.ts index 891438bbb9..f1fe816642 100644 --- a/src/mjolnir/Mjolnir.ts +++ b/src/mjolnir/Mjolnir.ts @@ -20,6 +20,7 @@ import SettingsStore from "../settings/SettingsStore"; import {_t} from "../languageHandler"; import dis from "../dispatcher/dispatcher"; import {SettingLevel} from "../settings/SettingLevel"; +import { Preset } from "matrix-js-sdk/src/@types/partials"; // TODO: Move this and related files to the js-sdk or something once finalized. @@ -86,7 +87,7 @@ export class Mjolnir { const resp = await MatrixClientPeg.get().createRoom({ name: _t("My Ban List"), topic: _t("This is your list of users/servers you have blocked - don't leave the room!"), - preset: "private_chat", + preset: Preset.PrivateChat, }); personalRoomId = resp['room_id']; await SettingsStore.setValue( From 951fbf4a1a06f52d40b2eefc21937a3383e983ea Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 14 Jun 2021 13:38:43 -0600 Subject: [PATCH 198/198] fix import --- src/components/views/dialogs/InviteDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index b006205f11..d90214110f 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -31,7 +31,6 @@ import Modal from "../../../Modal"; import {humanizeTime} from "../../../utils/humanize"; import createRoom, { canEncryptToAllUsers, ensureDMExists, findDMForUser, privateShouldBeEncrypted, - IInvite3PID, } from "../../../createRoom"; import {inviteMultipleToRoom, showCommunityInviteDialog} from "../../../RoomInvite"; import {Key} from "../../../Keyboard"; @@ -50,6 +49,7 @@ import {getAddressType} from "../../../UserAddress"; import BaseAvatar from '../avatars/BaseAvatar'; import AccessibleButton from '../elements/AccessibleButton'; import { compare } from '../../../utils/strings'; +import { IInvite3PID } from "matrix-js-sdk/src/@types/requests"; // we have a number of types defined from the Matrix spec which can't reasonably be altered here. /* eslint-disable camelcase */